diff --git a/DiskState.cpp b/DiskState.cpp index d8d5363c..476bd3ac 100644 --- a/DiskState.cpp +++ b/DiskState.cpp @@ -82,7 +82,7 @@ bool DiskState::SaveDownloadQueue(DownloadQueue* pDownloadQueue) return false; } - fprintf(outfile, "%s%i\n", FORMATVERSION_SIGNATURE, 17); + fprintf(outfile, "%s%i\n", FORMATVERSION_SIGNATURE, 18); // save nzb-infos SaveNZBList(pDownloadQueue, outfile); @@ -136,7 +136,7 @@ bool DiskState::LoadDownloadQueue(DownloadQueue* pDownloadQueue) char FileSignatur[128]; fgets(FileSignatur, sizeof(FileSignatur), infile); int iFormatVersion = ParseFormatVersion(FileSignatur); - if (iFormatVersion < 3 || iFormatVersion > 17) + if (iFormatVersion < 3 || iFormatVersion > 18) { error("Could not load diskstate due to file version mismatch"); fclose(infile); @@ -152,7 +152,7 @@ bool DiskState::LoadDownloadQueue(DownloadQueue* pDownloadQueue) if (iFormatVersion >= 7) { // load post-queue - if (!LoadPostQueue(pDownloadQueue, infile)) goto error; + if (!LoadPostQueue(pDownloadQueue, infile, iFormatVersion)) goto error; } else if (iFormatVersion < 7 && g_pOptions->GetReloadPostQueue()) { @@ -204,8 +204,7 @@ void DiskState::SaveNZBList(DownloadQueue* pDownloadQueue, FILE* outfile) fprintf(outfile, "%s\n", pNZBInfo->GetName()); fprintf(outfile, "%s\n", pNZBInfo->GetCategory()); fprintf(outfile, "%i\n", pNZBInfo->GetPostProcess() ? 1 : 0); - fprintf(outfile, "%i\n", (int)pNZBInfo->GetParStatus()); - fprintf(outfile, "%i\n", (int)pNZBInfo->GetScriptStatus()); + fprintf(outfile, "%i,%i,%i\n", (int)pNZBInfo->GetParStatus(), (int)pNZBInfo->GetUnpackStatus(), (int)pNZBInfo->GetScriptStatus()); fprintf(outfile, "%i\n", pNZBInfo->GetFileCount()); fprintf(outfile, "%i\n", pNZBInfo->GetParkedFileCount()); @@ -301,20 +300,29 @@ bool DiskState::LoadNZBList(DownloadQueue* pDownloadQueue, FILE* infile, int iFo pNZBInfo->SetPostProcess(iPostProcess == 1); } - if (iFormatVersion >= 8) + if (iFormatVersion >= 8 && iFormatVersion < 18) { int iParStatus; if (fscanf(infile, "%i\n", &iParStatus) != 1) goto error; pNZBInfo->SetParStatus((NZBInfo::EParStatus)iParStatus); } - if (iFormatVersion >= 9) + if (iFormatVersion >= 9 && iFormatVersion < 18) { int iScriptStatus; if (fscanf(infile, "%i\n", &iScriptStatus) != 1) goto error; pNZBInfo->SetScriptStatus((NZBInfo::EScriptStatus)iScriptStatus); } + if (iFormatVersion >= 18) + { + int iParStatus, iUnpackStatus, iScriptStatus; + if (fscanf(infile, "%i,%i,%i\n", &iParStatus, &iUnpackStatus, &iScriptStatus) != 3) goto error; + pNZBInfo->SetParStatus((NZBInfo::EParStatus)iParStatus); + pNZBInfo->SetUnpackStatus((NZBInfo::EUnpackStatus)iUnpackStatus); + pNZBInfo->SetScriptStatus((NZBInfo::EScriptStatus)iScriptStatus); + } + int iFileCount; if (fscanf(infile, "%i\n", &iFileCount) != 1) goto error; pNZBInfo->SetFileCount(iFileCount); @@ -610,7 +618,7 @@ void DiskState::SavePostQueue(DownloadQueue* pDownloadQueue, FILE* outfile) } } -bool DiskState::LoadPostQueue(DownloadQueue* pDownloadQueue, FILE* infile) +bool DiskState::LoadPostQueue(DownloadQueue* pDownloadQueue, FILE* infile, int iFormatVersion) { debug("Loading post-queue from disk"); @@ -625,6 +633,7 @@ bool DiskState::LoadPostQueue(DownloadQueue* pDownloadQueue, FILE* infile) PostInfo* pPostInfo = NULL; unsigned int iNZBIndex, iParCheck, iParStatus, iStage; if (fscanf(infile, "%i,%i,%i,%i\n", &iNZBIndex, &iParCheck, &iParStatus, &iStage) != 4) goto error; + if (iFormatVersion < 18 && iStage > (int)PostInfo::ptVerifyingRepaired) iStage++; if (!bSkipPostQueue) { @@ -1023,7 +1032,7 @@ bool DiskState::DiscardDownloadQueue() char FileSignatur[128]; fgets(FileSignatur, sizeof(FileSignatur), infile); int iFormatVersion = ParseFormatVersion(FileSignatur); - if (3 <= iFormatVersion && iFormatVersion <= 17) + if (3 <= iFormatVersion && iFormatVersion <= 18) { // skip nzb-infos int size = 0; @@ -1046,14 +1055,18 @@ bool DiskState::DiscardDownloadQueue() if (!fgets(buf, sizeof(buf), infile)) break; // category if (!fgets(buf, sizeof(buf), infile)) break; // postprocess } - if (iFormatVersion >= 8) + if (iFormatVersion >= 8 && iFormatVersion < 18) { if (!fgets(buf, sizeof(buf), infile)) break; // ParStatus } - if (iFormatVersion >= 9) + if (iFormatVersion >= 9 && iFormatVersion < 18) { if (!fgets(buf, sizeof(buf), infile)) break; // ScriptStatus } + if (iFormatVersion >= 18) + { + if (!fgets(buf, sizeof(buf), infile)) break; // ParStatus, UnpackStatus, ScriptStatus + } if (!fgets(buf, sizeof(buf), infile)) break; // file count if (iFormatVersion >= 10) { diff --git a/DiskState.h b/DiskState.h index 6b7f24a9..5de6514c 100644 --- a/DiskState.h +++ b/DiskState.h @@ -1,7 +1,7 @@ /* * This file is part of nzbget * - * Copyright (C) 2007-2009 Andrey Prygunkov + * Copyright (C) 2007-2013 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 @@ -39,7 +39,7 @@ private: void SaveFileQueue(DownloadQueue* pDownloadQueue, FileQueue* pFileQueue, FILE* outfile); bool LoadFileQueue(DownloadQueue* pDownloadQueue, FileQueue* pFileQueue, FILE* infile, int iFormatVersion); void SavePostQueue(DownloadQueue* pDownloadQueue, FILE* outfile); - bool LoadPostQueue(DownloadQueue* pDownloadQueue, FILE* infile); + bool LoadPostQueue(DownloadQueue* pDownloadQueue, FILE* infile, int iFormatVersion); bool LoadOldPostQueue(DownloadQueue* pDownloadQueue); void SaveUrlQueue(DownloadQueue* pDownloadQueue, FILE* outfile); bool LoadUrlQueue(DownloadQueue* pDownloadQueue, FILE* infile, int iFormatVersion); diff --git a/DownloadInfo.cpp b/DownloadInfo.cpp index 379881cf..a0547ffa 100644 --- a/DownloadInfo.cpp +++ b/DownloadInfo.cpp @@ -2,7 +2,7 @@ * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel - * Copyright (C) 2007-2011 Andrey Prygunkov + * Copyright (C) 2007-2013 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 @@ -130,7 +130,8 @@ NZBInfo::NZBInfo() m_lSize = 0; m_iRefCount = 0; m_bPostProcess = false; - m_eParStatus = prNone; + m_eParStatus = psNone; + m_eUnpackStatus = usNone; m_eScriptStatus = srNone; m_bDeleted = false; m_bParCleanup = false; @@ -350,9 +351,8 @@ void NZBInfo::AppendMessage(Message::EKind eKind, time_t tTime, const char * szT tTime = time(NULL); } - Message* pMessage = new Message(++m_iIDMessageGen, eKind, tTime, szText); - m_mutexLog.Lock(); + Message* pMessage = new Message(++m_iIDMessageGen, eKind, tTime, szText); m_Messages.push_back(pMessage); m_mutexLog.Unlock(); } @@ -610,6 +610,7 @@ PostInfo::PostInfo() m_bDeleted = false; m_bParCheck = false; m_eParStatus = psNone; + m_eUnpackStatus = usNone; m_eRequestParCheck = rpNone; m_eScriptStatus = srNone; m_szProgressLabel = strdup(""); @@ -696,9 +697,8 @@ void PostInfo::UnlockMessages() void PostInfo::AppendMessage(Message::EKind eKind, const char * szText) { - Message* pMessage = new Message(++m_iIDMessageGen, eKind, time(NULL), szText); - m_mutexLog.Lock(); + Message* pMessage = new Message(++m_iIDMessageGen, eKind, time(NULL), szText); m_Messages.push_back(pMessage); while (m_Messages.size() > (unsigned int)g_pOptions->GetLogBufferSize()) diff --git a/DownloadInfo.h b/DownloadInfo.h index 75bb2331..28c80aa2 100644 --- a/DownloadInfo.h +++ b/DownloadInfo.h @@ -216,10 +216,18 @@ class NZBInfo public: enum EParStatus { - prNone, - prFailure, - prRepairPossible, - prSuccess + psNone, + psFailure, + psRepairPossible, + psSuccess + }; + + enum EUnpackStatus + { + usNone, + usSkipped, + usFailure, + usSuccess }; enum EScriptStatus @@ -246,6 +254,7 @@ private: Files m_completedFiles; bool m_bPostProcess; EParStatus m_eParStatus; + EUnpackStatus m_eUnpackStatus; EScriptStatus m_eScriptStatus; char* m_szQueuedFilename; bool m_bDeleted; @@ -289,6 +298,8 @@ public: void SetPostProcess(bool bPostProcess) { m_bPostProcess = bPostProcess; } EParStatus GetParStatus() { return m_eParStatus; } void SetParStatus(EParStatus eParStatus) { m_eParStatus = eParStatus; } + EUnpackStatus GetUnpackStatus() { return m_eUnpackStatus; } + void SetUnpackStatus(EUnpackStatus eUnpackStatus) { m_eUnpackStatus = eUnpackStatus; } EScriptStatus GetScriptStatus() { return m_eScriptStatus; } void SetScriptStatus(EScriptStatus eScriptStatus) { m_eScriptStatus = eScriptStatus; } const char* GetQueuedFilename() { return m_szQueuedFilename; } @@ -326,6 +337,7 @@ public: ptVerifyingSources, ptRepairing, ptVerifyingRepaired, + ptUnpacking, ptExecutingScript, ptFinished }; @@ -345,6 +357,14 @@ public: rpAll }; + enum EUnpackStatus + { + usNone, + usSkipped, + usFailure, + usSuccess + }; + enum EScriptStatus { srNone, @@ -364,6 +384,7 @@ private: bool m_bDeleted; bool m_bParCheck; EParStatus m_eParStatus; + EUnpackStatus m_eUnpackStatus; EScriptStatus m_eScriptStatus; ERequestParCheck m_eRequestParCheck; EStage m_eStage; @@ -410,6 +431,8 @@ public: void SetParCheck(bool bParCheck) { m_bParCheck = bParCheck; } EParStatus GetParStatus() { return m_eParStatus; } void SetParStatus(EParStatus eParStatus) { m_eParStatus = eParStatus; } + EUnpackStatus GetUnpackStatus() { return m_eUnpackStatus; } + void SetUnpackStatus(EUnpackStatus eUnpackStatus) { m_eUnpackStatus = eUnpackStatus; } ERequestParCheck GetRequestParCheck() { return m_eRequestParCheck; } void SetRequestParCheck(ERequestParCheck eRequestParCheck) { m_eRequestParCheck = eRequestParCheck; } EScriptStatus GetScriptStatus() { return m_eScriptStatus; } diff --git a/Makefile.am b/Makefile.am index 2f1c669b..09aebbf9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -34,7 +34,7 @@ nzbget_SOURCES = \ RemoteServer.cpp RemoteServer.h Scanner.cpp Scanner.h Scheduler.cpp Scheduler.h ScriptController.cpp \ ScriptController.h ServerPool.cpp ServerPool.h svn_version.cpp TLS.cpp TLS.h Thread.cpp Thread.h \ Util.cpp Util.h XmlRpc.cpp XmlRpc.h WebDownloader.cpp WebDownloader.h WebServer.cpp WebServer.h \ - UrlCoordinator.cpp UrlCoordinator.h nzbget.cpp nzbget.h + UrlCoordinator.cpp UrlCoordinator.h Unpack.cpp Unpack.h nzbget.cpp nzbget.h EXTRA_DIST = \ Makefile.cvs nzbgetd nzbget-postprocess.sh \ diff --git a/Makefile.in b/Makefile.in index 3084e98e..b31d01df 100644 --- a/Makefile.in +++ b/Makefile.in @@ -97,7 +97,8 @@ am_nzbget_OBJECTS = ArticleDownloader.$(OBJEXT) BinRpc.$(OBJEXT) \ ScriptController.$(OBJEXT) ServerPool.$(OBJEXT) \ svn_version.$(OBJEXT) TLS.$(OBJEXT) Thread.$(OBJEXT) \ Util.$(OBJEXT) XmlRpc.$(OBJEXT) WebDownloader.$(OBJEXT) \ - WebServer.$(OBJEXT) UrlCoordinator.$(OBJEXT) nzbget.$(OBJEXT) + WebServer.$(OBJEXT) UrlCoordinator.$(OBJEXT) Unpack.$(OBJEXT) \ + nzbget.$(OBJEXT) nzbget_OBJECTS = $(am_nzbget_OBJECTS) nzbget_LDADD = $(LDADD) binSCRIPT_INSTALL = $(INSTALL_SCRIPT) @@ -254,7 +255,7 @@ nzbget_SOURCES = \ RemoteServer.cpp RemoteServer.h Scanner.cpp Scanner.h Scheduler.cpp Scheduler.h ScriptController.cpp \ ScriptController.h ServerPool.cpp ServerPool.h svn_version.cpp TLS.cpp TLS.h Thread.cpp Thread.h \ Util.cpp Util.h XmlRpc.cpp XmlRpc.h WebDownloader.cpp WebDownloader.h WebServer.cpp WebServer.h \ - UrlCoordinator.cpp UrlCoordinator.h nzbget.cpp nzbget.h + UrlCoordinator.cpp UrlCoordinator.h Unpack.cpp Unpack.h nzbget.cpp nzbget.h EXTRA_DIST = \ Makefile.cvs nzbgetd nzbget-postprocess.sh \ @@ -456,6 +457,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ServerPool.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TLS.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Thread.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Unpack.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/UrlCoordinator.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Util.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/WebDownloader.Po@am__quote@ diff --git a/Options.cpp b/Options.cpp index d03c3f97..d8a4f145 100644 --- a/Options.cpp +++ b/Options.cpp @@ -171,6 +171,11 @@ static const char* OPTION_MERGENZB = "MergeNzb"; static const char* OPTION_PARTIMELIMIT = "ParTimeLimit"; static const char* OPTION_KEEPHISTORY = "KeepHistory"; static const char* OPTION_ACCURATERATE = "AccurateRate"; +static const char* OPTION_UNPACK = "Unpack"; +static const char* OPTION_UNPACKCLEANUPDISK = "UnpackCleanupDisk"; +static const char* OPTION_UNRARCMD = "UnrarCmd"; +static const char* OPTION_SEVENZIPCMD = "SevenZipCmd"; +static const char* OPTION_UNPACKPAUSEQUEUE = "UnpackPauseQueue"; // obsolete options static const char* OPTION_POSTLOGKIND = "PostLogKind"; @@ -431,6 +436,11 @@ Options::Options(int argc, char* argv[]) m_bAccurateRate = false; m_EMatchMode = mmID; m_tResumeTime = 0; + m_bUnpack = false; + m_bUnpackCleanupDisk = false; + m_szUnrarCmd = NULL; + m_szSevenZipCmd = NULL; + m_bUnpackPauseQueue = false; // Option "ConfigFile" will be initialized later, but we want // to see it at the top of option list, so we add it first @@ -603,6 +613,14 @@ Options::~Options() { free(m_szAddNZBFilename); } + if (m_szUnrarCmd) + { + free(m_szUnrarCmd); + } + if (m_szSevenZipCmd) + { + free(m_szSevenZipCmd); + } for (NameList::iterator it = m_EditQueueNameList.begin(); it != m_EditQueueNameList.end(); it++) { @@ -731,6 +749,16 @@ void Options::InitDefault() SetOption(OPTION_PARTIMELIMIT, "0"); SetOption(OPTION_KEEPHISTORY, "7"); SetOption(OPTION_ACCURATERATE, "no"); + SetOption(OPTION_UNPACK, "no"); + SetOption(OPTION_UNPACKCLEANUPDISK, "no"); +#ifdef WIN32 + SetOption(OPTION_UNRARCMD, "unrar.exe"); + SetOption(OPTION_SEVENZIPCMD, "7z.exe"); +#else + SetOption(OPTION_UNRARCMD, "unrar"); + SetOption(OPTION_SEVENZIPCMD, "7z"); +#endif + SetOption(OPTION_UNPACKPAUSEQUEUE, "no"); } void Options::InitOptFile() @@ -858,6 +886,8 @@ void Options::InitOptions() m_szLockFile = strdup(GetOption(OPTION_LOCKFILE)); m_szDaemonUserName = strdup(GetOption(OPTION_DAEMONUSERNAME)); m_szLogFile = strdup(GetOption(OPTION_LOGFILE)); + m_szUnrarCmd = strdup(GetOption(OPTION_UNRARCMD)); + m_szSevenZipCmd = strdup(GetOption(OPTION_SEVENZIPCMD)); m_iDownloadRate = (int)(ParseFloatValue(OPTION_DOWNLOADRATE) * 1024); m_iConnectionTimeout = ParseIntValue(OPTION_CONNECTIONTIMEOUT, 10); @@ -912,6 +942,9 @@ void Options::InitOptions() m_bMergeNzb = (bool)ParseEnumValue(OPTION_MERGENZB, BoolCount, BoolNames, BoolValues); m_bAccurateRate = (bool)ParseEnumValue(OPTION_ACCURATERATE, BoolCount, BoolNames, BoolValues); m_bSecureControl = (bool)ParseEnumValue(OPTION_SECURECONTROL, BoolCount, BoolNames, BoolValues); + m_bUnpack = (bool)ParseEnumValue(OPTION_UNPACK, BoolCount, BoolNames, BoolValues); + m_bUnpackCleanupDisk = (bool)ParseEnumValue(OPTION_UNPACKCLEANUPDISK, BoolCount, BoolNames, BoolValues); + m_bUnpackPauseQueue = (bool)ParseEnumValue(OPTION_UNPACKPAUSEQUEUE, BoolCount, BoolNames, BoolValues); const char* OutputModeNames[] = { "loggable", "logable", "log", "colored", "color", "ncurses", "curses" }; const int OutputModeValues[] = { omLoggable, omLoggable, omLoggable, omColored, omColored, omNCurses, omNCurses }; @@ -2350,6 +2383,13 @@ void Options::CheckOptions() { m_bDirectWrite = false; } + + if (m_bUnpack && m_bAllowReProcess) + { + LocateOptionSrcPos(OPTION_ALLOWREPROCESS); + ConfigError("Options \"%s\" and \"%s\" cannot be both active at the same time", OPTION_UNPACK, OPTION_ALLOWREPROCESS); + m_bAllowReProcess = false; + } } void Options::ParseFileIDList(int argc, char* argv[], int optind) diff --git a/Options.h b/Options.h index 51a8e9a6..40203680 100644 --- a/Options.h +++ b/Options.h @@ -255,6 +255,11 @@ private: int m_iParTimeLimit; int m_iKeepHistory; bool m_bAccurateRate; + bool m_bUnpack; + bool m_bUnpackCleanupDisk; + char* m_szUnrarCmd; + char* m_szSevenZipCmd; + bool m_bUnpackPauseQueue; // Parsed command-line parameters bool m_bServerMode; @@ -405,6 +410,12 @@ public: int GetParTimeLimit() { return m_iParTimeLimit; } int GetKeepHistory() { return m_iKeepHistory; } bool GetAccurateRate() { return m_bAccurateRate; } + bool GetUnpack() { return m_bUnpack; } + bool GetUnpackCleanupDisk() { return m_bUnpackCleanupDisk; } + const char* GetUnrarCmd() { return m_szUnrarCmd; } + const char* GetSevenZipCmd() { return m_szSevenZipCmd; } + bool GetUnpackPauseQueue() { return m_bUnpackPauseQueue; } + Category* FindCategory(const char* szName) { return m_Categories.FindCategory(szName); } // Parsed command-line parameters diff --git a/ParCoordinator.cpp b/ParCoordinator.cpp index b1cc33e7..7e801375 100644 --- a/ParCoordinator.cpp +++ b/ParCoordinator.cpp @@ -130,13 +130,22 @@ void ParCoordinator::PausePars(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo) bool ParCoordinator::FindMainPars(const char* szPath, FileList* pFileList) { - pFileList->clear(); + if (pFileList) + { + pFileList->clear(); + } + DirBrowser dir(szPath); while (const char* filename = dir.Next()) { int iBaseLen = 0; if (ParseParFilename(filename, &iBaseLen, NULL)) { + if (!pFileList) + { + return true; + } + // check if the base file already added to list bool exists = false; for (FileList::iterator it = pFileList->begin(); it != pFileList->end(); it++) @@ -154,7 +163,7 @@ bool ParCoordinator::FindMainPars(const char* szPath, FileList* pFileList) } } } - return !pFileList->empty(); + return pFileList && !pFileList->empty(); } bool ParCoordinator::SameParCollection(const char* szFilename1, const char* szFilename2) @@ -226,14 +235,6 @@ bool ParCoordinator::ParseParFilename(const char* szParFilename, int* iBaseNameL */ void ParCoordinator::StartParJob(PostInfo* pPostInfo) { - if (g_pOptions->GetParPauseQueue()) - { - if (PauseDownload()) - { - info("Pausing queue before par-check"); - } - } - info("Checking pars for %s", pPostInfo->GetInfoName()); m_ParChecker.SetPostInfo(pPostInfo); m_ParChecker.SetParFilename(pPostInfo->GetParFilename()); @@ -357,23 +358,23 @@ void ParCoordinator::ParCheckerUpdate(Subject* Caller, void* Aspect) if (m_ParChecker.GetStatus() == ParChecker::psFailed && !m_ParChecker.GetCancelled()) { pPostInfo->SetParStatus(PostInfo::psFailure); - pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::prFailure); + pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::psFailure); } else if (m_ParChecker.GetStatus() == ParChecker::psFinished && (g_pOptions->GetParRepair() || m_ParChecker.GetRepairNotNeeded())) { pPostInfo->SetParStatus(PostInfo::psSuccess); - if (pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::prNone) + if (pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psNone) { - pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::prSuccess); + pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::psSuccess); } } else { pPostInfo->SetParStatus(PostInfo::psRepairPossible); - if (pPostInfo->GetNZBInfo()->GetParStatus() != NZBInfo::prFailure) + if (pPostInfo->GetNZBInfo()->GetParStatus() != NZBInfo::psFailure) { - pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::prRepairPossible); + pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::psRepairPossible); } } @@ -383,14 +384,6 @@ void ParCoordinator::ParCheckerUpdate(Subject* Caller, void* Aspect) } g_pQueueCoordinator->UnlockQueue(); - - if (g_pOptions->GetParPauseQueue() && !(g_pOptions->GetPostPauseQueue() && m_bPostScript)) - { - if (UnpauseDownload()) - { - info("Unpausing queue after par-check"); - } - } } } diff --git a/ParCoordinator.h b/ParCoordinator.h index 1da245fa..6111204d 100644 --- a/ParCoordinator.h +++ b/ParCoordinator.h @@ -87,9 +87,9 @@ public: public: ParCoordinator(); virtual ~ParCoordinator(); - bool FindMainPars(const char* szPath, FileList* pFileList); - bool ParseParFilename(const char* szParFilename, int* iBaseNameLen, int* iBlocks); - bool SameParCollection(const char* szFilename1, const char* szFilename2); + static bool FindMainPars(const char* szPath, FileList* pFileList); + static bool ParseParFilename(const char* szParFilename, int* iBaseNameLen, int* iBlocks); + static bool SameParCollection(const char* szFilename1, const char* szFilename2); void PausePars(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo); #ifndef DISABLE_PARCHECK diff --git a/PrePostProcessor.cpp b/PrePostProcessor.cpp index abdfe475..3764feb6 100644 --- a/PrePostProcessor.cpp +++ b/PrePostProcessor.cpp @@ -49,6 +49,7 @@ #include "DiskState.h" #include "Util.h" #include "Scheduler.h" +#include "Unpack.h" extern QueueCoordinator* g_pQueueCoordinator; extern Options* g_pOptions; @@ -182,7 +183,9 @@ void PrePostProcessor::Stop() if (!pDownloadQueue->GetPostQueue()->empty()) { PostInfo* pPostInfo = pDownloadQueue->GetPostQueue()->front(); - if (pPostInfo->GetStage() == PostInfo::ptExecutingScript && pPostInfo->GetScriptThread()) + if ((pPostInfo->GetStage() == PostInfo::ptUnpacking || + pPostInfo->GetStage() == PostInfo::ptExecutingScript) && + pPostInfo->GetScriptThread()) { Thread* pScriptThread = pPostInfo->GetScriptThread(); pPostInfo->SetScriptThread(NULL); @@ -261,7 +264,7 @@ void PrePostProcessor::NZBDownloaded(DownloadQueue* pDownloadQueue, NZBInfo* pNZ #else bool bParCheck = g_pOptions->GetParCheck() && g_pOptions->GetDecode(); #endif - if ((bParCheck || m_bPostScript) && + if ((bParCheck || m_bPostScript || g_pOptions->GetUnpack()) && CreatePostJobs(pDownloadQueue, pNZBInfo, bParCheck, true, false)) { pNZBInfo->SetPostProcess(true); @@ -505,24 +508,18 @@ void PrePostProcessor::CheckPostQueue() if (pPostInfo->GetParCheck() && pPostInfo->GetParStatus() == PostInfo::psNone && !g_pOptions->GetPausePostProcess()) { + UpdatePauseState(g_pOptions->GetParPauseQueue(), "par-check"); m_ParCoordinator.StartParJob(pPostInfo); } else #endif if (pPostInfo->GetStage() == PostInfo::ptQueued && !g_pOptions->GetPausePostProcess()) { - StartScriptJob(pDownloadQueue, pPostInfo); + StartProcessJob(pDownloadQueue, pPostInfo); } else if (pPostInfo->GetStage() == PostInfo::ptFinished) { - if (m_bPostScript && g_pOptions->GetPostPauseQueue()) - { - if (UnpauseDownload()) - { - info("Unpausing queue after post-process-script"); - } - } - + UpdatePauseState(false, NULL); JobCompleted(pDownloadQueue, pPostInfo); } else if (!g_pOptions->GetPausePostProcess()) @@ -565,17 +562,31 @@ void PrePostProcessor::SanitisePostQueue(PostQueue* pPostQueue) } } -void PrePostProcessor::StartScriptJob(DownloadQueue* pDownloadQueue, PostInfo* pPostInfo) +void PrePostProcessor::StartProcessJob(DownloadQueue* pDownloadQueue, PostInfo* pPostInfo) { - if (!m_bPostScript) + bool bUnpack = g_pOptions->GetUnpack() && (pPostInfo->GetUnpackStatus() == PostInfo::usNone); + bool bNZBFileCompleted = IsNZBFileCompleted(pDownloadQueue, pPostInfo->GetNZBInfo(), true, true, false); + bool bHasFailedParJobs = pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psFailure || + pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psRepairPossible; + + if (bUnpack && bHasFailedParJobs) + { + warn("Skipping unpack due to par-failure for %s", pPostInfo->GetInfoName()); + pPostInfo->SetUnpackStatus(PostInfo::usSkipped); + pPostInfo->GetNZBInfo()->SetUnpackStatus(NZBInfo::usSkipped); + bUnpack = false; + } + + if ((!bNZBFileCompleted && !(m_bPostScript && g_pOptions->GetAllowReProcess())) || + (!bUnpack && !m_bPostScript)) { pPostInfo->SetStage(PostInfo::ptFinished); return; } - pPostInfo->SetProgressLabel("Executing post-process-script"); + pPostInfo->SetProgressLabel(bUnpack ? "Unpacking" : "Executing post-process-script"); pPostInfo->SetWorking(true); - pPostInfo->SetStage(PostInfo::ptExecutingScript); + pPostInfo->SetStage(bUnpack ? PostInfo::ptUnpacking : PostInfo::ptExecutingScript); pPostInfo->SetFileProgress(0); pPostInfo->SetStageProgress(0); SaveQueue(pDownloadQueue); @@ -586,19 +597,16 @@ void PrePostProcessor::StartScriptJob(DownloadQueue* pDownloadQueue, PostInfo* p } pPostInfo->SetStageTime(time(NULL)); - bool bNZBFileCompleted = IsNZBFileCompleted(pDownloadQueue, pPostInfo->GetNZBInfo(), true, true, false); - bool bHasFailedParJobs = pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::prFailure || - pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::prRepairPossible; - - if (g_pOptions->GetPostPauseQueue()) + if (bUnpack) { - if (PauseDownload()) - { - info("Pausing queue before post-process-script"); - } + UpdatePauseState(g_pOptions->GetUnpackPauseQueue(), "unpack"); + UnpackController::StartUnpackJob(pPostInfo); + } + else + { + UpdatePauseState(g_pOptions->GetPostPauseQueue(), "post-process-script"); + PostScriptController::StartScriptJob(pPostInfo, bNZBFileCompleted, bHasFailedParJobs); } - - PostScriptController::StartScriptJob(pPostInfo, g_pOptions->GetPostProcess(), bNZBFileCompleted, bHasFailedParJobs); } void PrePostProcessor::JobCompleted(DownloadQueue* pDownloadQueue, PostInfo* pPostInfo) @@ -632,8 +640,8 @@ void PrePostProcessor::JobCompleted(DownloadQueue* pDownloadQueue, PostInfo* pPo if (IsNZBFileCompleted(pDownloadQueue, pPostInfo->GetNZBInfo(), true, true, false)) { // Cleaning up queue if all par-checks were successful or all scripts were successful - bool bCanCleanupQueue = pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::prSuccess || - pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::prRepairPossible || + bool bCanCleanupQueue = pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psSuccess || + pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psRepairPossible || pPostInfo->GetNZBInfo()->GetScriptStatus() == NZBInfo::srSuccess; if ((g_pOptions->GetParCleanupQueue() || g_pOptions->GetNzbCleanupDisk()) && bCanCleanupQueue) { @@ -719,7 +727,7 @@ bool PrePostProcessor::IsNZBFileCompleted(DownloadQueue* pDownloadQueue, NZBInfo } bool PrePostProcessor::CreatePostJobs(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, - bool bParCheck, bool bPostScript, bool bAddTop) + bool bParCheck, bool bUnpackOrScript, bool bAddTop) { debug("Queueing post-process-jobs"); @@ -750,8 +758,12 @@ bool PrePostProcessor::CreatePostJobs(DownloadQueue* pDownloadQueue, NZBInfo* pN char szParInfoName[1024]; snprintf(szParInfoName, 1024, "%s%c%s", pNZBInfo->GetName(), (int)PATH_SEPARATOR, szInfoName); szParInfoName[1024-1] = '\0'; - - info("Queueing %s%c%s for par-check", pNZBInfo->GetName(), (int)PATH_SEPARATOR, szInfoName); + + if (cPostQueue.empty()) + { + info("Queueing %s for post-processing", pNZBInfo->GetName()); + } + PostInfo* pPostInfo = new PostInfo(); pPostInfo->SetNZBInfo(pNZBInfo); pPostInfo->SetParFilename(szFullParFilename); @@ -771,9 +783,9 @@ bool PrePostProcessor::CreatePostJobs(DownloadQueue* pDownloadQueue, NZBInfo* pN } } - if (cPostQueue.empty() && bPostScript && m_bPostScript) + if (cPostQueue.empty() && bUnpackOrScript && (m_bPostScript || g_pOptions->GetUnpack())) { - info("Queueing %s for post-process-script", pNZBInfo->GetName()); + info("Queueing %s for post-processing", pNZBInfo->GetName()); PostInfo* pPostInfo = new PostInfo(); pPostInfo->SetNZBInfo(pNZBInfo); pPostInfo->SetParFilename(""); @@ -846,6 +858,26 @@ void PrePostProcessor::ApplySchedulerState() } } +void PrePostProcessor::UpdatePauseState(bool bNeedPause, const char* szReason) +{ + if (bNeedPause) + { + if (PauseDownload()) + { + info("Pausing queue before %s", szReason); + } + } + else if (m_bPostPause) + { + if (UnpauseDownload()) + { + info("Unpausing queue after %s", m_szPauseReason); + } + } + + m_szPauseReason = szReason; +} + bool PrePostProcessor::PauseDownload() { debug("PrePostProcessor::PauseDownload()"); @@ -944,7 +976,7 @@ bool PrePostProcessor::PostQueueDelete(IDList* pIDList) #endif if (pPostInfo->GetScriptThread()) { - debug("Terminating post-process-script for %s", pPostInfo->GetInfoName()); + debug("Terminating %s for %s", (pPostInfo->GetStage() == PostInfo::ptUnpacking ? "unpack" : "post-process-script"), pPostInfo->GetInfoName()); pPostInfo->GetScriptThread()->Stop(); bOK = true; } @@ -1155,8 +1187,9 @@ bool PrePostProcessor::HistoryReturn(IDList* pIDList, bool bReprocess) // reset postprocessing status variables pNZBInfo->SetPostProcess(false); - pNZBInfo->SetParStatus(NZBInfo::prNone); + pNZBInfo->SetParStatus(NZBInfo::psNone); pNZBInfo->SetParCleanup(false); + pNZBInfo->SetUnpackStatus(NZBInfo::usNone); pNZBInfo->SetScriptStatus(NZBInfo::srNone); pNZBInfo->SetParkedFileCount(0); } diff --git a/PrePostProcessor.h b/PrePostProcessor.h index fc33f116..d20cf954 100644 --- a/PrePostProcessor.h +++ b/PrePostProcessor.h @@ -76,24 +76,26 @@ private: bool m_bSchedulerPause; bool m_bPostPause; Scanner m_Scanner; + const char* m_szPauseReason; bool IsNZBFileCompleted(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, bool bIgnorePausedPars, bool bCheckPostQueue, bool bAllowOnlyOneDeleted); void CheckPostQueue(); void JobCompleted(DownloadQueue* pDownloadQueue, PostInfo* pPostInfo); - void StartScriptJob(DownloadQueue* pDownloadQueue, PostInfo* pPostInfo); + void StartProcessJob(DownloadQueue* pDownloadQueue, PostInfo* pPostInfo); void SaveQueue(DownloadQueue* pDownloadQueue); void SanitisePostQueue(PostQueue* pPostQueue); void CheckDiskSpace(); void ApplySchedulerState(); void CheckScheduledResume(); + void UpdatePauseState(bool bNeedPause, const char* szReason); bool PauseDownload(); bool UnpauseDownload(); void NZBAdded(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo); void NZBDownloaded(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo); void NZBDeleted(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo); void NZBCompleted(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, bool bSaveQueue); - bool CreatePostJobs(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, bool bParCheck, bool bPostScript, bool bAddTop); + bool CreatePostJobs(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, bool bParCheck, bool bUnpackOrScript, bool bAddTop); void DeleteQueuedFile(const char* szQueuedFile); NZBInfo* MergeGroups(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo); bool PostQueueMove(IDList* pIDList, EEditAction eAction, int iOffset); diff --git a/QueueCoordinator.cpp b/QueueCoordinator.cpp index 8436f7d2..4bd7aa6a 100644 --- a/QueueCoordinator.cpp +++ b/QueueCoordinator.cpp @@ -446,6 +446,9 @@ bool QueueCoordinator::GetNextArticle(FileInfo* &pFileInfo, ArticleInfo* &pArtic // 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; diff --git a/RemoteClient.cpp b/RemoteClient.cpp index 9027f7a5..d46b7a7d 100644 --- a/RemoteClient.cpp +++ b/RemoteClient.cpp @@ -46,6 +46,7 @@ #include "nzbget.h" #include "RemoteClient.h" +#include "DownloadInfo.h" #include "Options.h" #include "Log.h" #include "Util.h" @@ -1026,15 +1027,14 @@ bool RemoteClient::RequestPostQueue() int iStageProgress = ntohl(pPostQueueAnswer->m_iStageProgress); - static const int EXECUTING_SCRIPT = 5; char szCompleted[100]; szCompleted[0] = '\0'; - if (iStageProgress > 0 && (int)ntohl(pPostQueueAnswer->m_iStage) != EXECUTING_SCRIPT) + if (iStageProgress > 0 && (int)ntohl(pPostQueueAnswer->m_iStage) != (int)PostInfo::ptExecutingScript) { sprintf(szCompleted, ", %i%s", (int)(iStageProgress / 10), "%"); } - const char* szPostStageName[] = { "", ", Loading Pars", ", Verifying source files", ", Repairing", ", Verifying repaired files", ", Executing postprocess-script", "" }; + const char* szPostStageName[] = { "", ", Loading Pars", ", Verifying source files", ", Repairing", ", Verifying repaired files", ", Unpacking", ", Executing postprocess-script", "" }; char* szInfoName = pBufPtr + sizeof(SNZBPostQueueResponseEntry) + ntohl(pPostQueueAnswer->m_iNZBFilenameLen) + ntohl(pPostQueueAnswer->m_iParFilename); printf("[%i] %s%s%s\n", ntohl(pPostQueueAnswer->m_iID), szInfoName, szPostStageName[ntohl(pPostQueueAnswer->m_iStage)], szCompleted); diff --git a/ScriptController.cpp b/ScriptController.cpp index 47a0fe8c..585e862c 100644 --- a/ScriptController.cpp +++ b/ScriptController.cpp @@ -1,7 +1,7 @@ /* * This file is part of nzbget * - * Copyright (C) 2007-2011 Andrey Prygunkov + * Copyright (C) 2007-2013 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 @@ -43,6 +43,7 @@ #include #include #include +#include #include "nzbget.h" #include "ScriptController.h" @@ -243,12 +244,6 @@ void ScriptController::PrepareEnvironmentStrings() int ScriptController::Execute() { - if (!Util::FileExists(m_szScript)) - { - error("Could not start %s: could not find file %s", m_szInfoName, m_szScript); - return -1; - } - PrepareEnvironmentStrings(); int iExitCode = 0; @@ -299,8 +294,7 @@ int ScriptController::Execute() DWORD dwErrCode = GetLastError(); char szErrMsg[255]; szErrMsg[255-1] = '\0'; - if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM || FORMAT_MESSAGE_IGNORE_INSERTS || FORMAT_MESSAGE_ARGUMENT_ARRAY, - NULL, dwErrCode, 0, szErrMsg, 255, NULL)) + if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwErrCode, 0, szErrMsg, 255, NULL)) { error("Could not start %s: %s", m_szInfoName, szErrMsg); } @@ -308,6 +302,10 @@ int ScriptController::Execute() { error("Could not start %s: error %i", m_szInfoName, dwErrCode); } + if (!Util::FileExists(m_szScript)) + { + error("Could not find file %s", m_szScript); + } free(szEnvironmentStrings); return -1; } @@ -370,6 +368,7 @@ int ScriptController::Execute() fflush(stdout); #endif + chdir(m_szWorkingDir); execve(m_szScript, (char* const*)m_szArgs, (char* const*)pEnvironmentStrings); fprintf(stdout, "[ERROR] Could not start script: %s", strerror(errno)); fflush(stdout); @@ -409,7 +408,7 @@ int ScriptController::Execute() debug("Entering pipe-loop"); while (!feof(readpipe) && !m_bTerminated) { - if (fgets(buf, 10240, readpipe)) + if (ReadLine(buf, 10240, readpipe)) { #ifdef CHILD_WATCHDOG if (!bChildConfirmed) @@ -503,6 +502,11 @@ void ScriptController::Terminate() debug("Stopped %s", m_szInfoName); } +bool ScriptController::ReadLine(char* szBuf, int iBufSize, FILE* pStream) +{ + return fgets(szBuf, iBufSize, pStream); +} + void ScriptController::ProcessOutput(char* szText) { debug("Processing output received from script"); @@ -517,23 +521,23 @@ void ScriptController::ProcessOutput(char* szText) if (!strncmp(szText, "[INFO] ", 7)) { - AddMessage(Message::mkInfo, false, g_pOptions->GetInfoTarget(), szText + 7); + AddMessage(Message::mkInfo, false, szText + 7); } else if (!strncmp(szText, "[WARNING] ", 10)) { - AddMessage(Message::mkWarning, false, g_pOptions->GetWarningTarget(), szText + 10); + AddMessage(Message::mkWarning, false, szText + 10); } else if (!strncmp(szText, "[ERROR] ", 8)) { - AddMessage(Message::mkError, false, g_pOptions->GetErrorTarget(), szText + 8); + AddMessage(Message::mkError, false, szText + 8); } else if (!strncmp(szText, "[DETAIL] ", 9)) { - AddMessage(Message::mkDetail, false, g_pOptions->GetDetailTarget(), szText + 9); + AddMessage(Message::mkDetail, false, szText + 9); } else if (!strncmp(szText, "[DEBUG] ", 8)) { - AddMessage(Message::mkDebug, false, g_pOptions->GetDebugTarget(), szText + 8); + AddMessage(Message::mkDebug, false, szText + 8); } else { @@ -543,23 +547,23 @@ void ScriptController::ProcessOutput(char* szText) break; case Options::slDetail: - AddMessage(Message::mkDetail, true, g_pOptions->GetDetailTarget(), szText); + AddMessage(Message::mkDetail, true, szText); break; case Options::slInfo: - AddMessage(Message::mkInfo, true, g_pOptions->GetInfoTarget(), szText); + AddMessage(Message::mkInfo, true, szText); break; case Options::slWarning: - AddMessage(Message::mkWarning, true, g_pOptions->GetWarningTarget(), szText); + AddMessage(Message::mkWarning, true, szText); break; case Options::slError: - AddMessage(Message::mkError, true, g_pOptions->GetErrorTarget(), szText); + AddMessage(Message::mkError, true, szText); break; case Options::slDebug: - AddMessage(Message::mkDebug, true, g_pOptions->GetDebugTarget(), szText); + AddMessage(Message::mkDebug, true, szText); break; } } @@ -567,7 +571,7 @@ void ScriptController::ProcessOutput(char* szText) debug("Processing output received from script - completed"); } -void ScriptController::AddMessage(Message::EKind eKind, bool bDefaultKind, Options::EMessageTarget eMessageTarget, const char* szText) +void ScriptController::AddMessage(Message::EKind eKind, bool bDefaultKind, const char* szText) { switch (eKind) { @@ -593,13 +597,26 @@ void ScriptController::AddMessage(Message::EKind eKind, bool bDefaultKind, Optio } } -void PostScriptController::StartScriptJob(PostInfo* pPostInfo, const char* szScript, bool bNZBFileCompleted, bool bHasFailedParJobs) +void ScriptController::PrintMessage(Message::EKind eKind, const char* szFormat, ...) +{ + char tmp2[1024]; + + va_list ap; + va_start(ap, szFormat); + vsnprintf(tmp2, 1024, szFormat, ap); + tmp2[1024-1] = '\0'; + va_end(ap); + + AddMessage(eKind, false, tmp2); +} + +void PostScriptController::StartScriptJob(PostInfo* pPostInfo, bool bNZBFileCompleted, bool bHasFailedParJobs) { info("Executing post-process-script for %s", pPostInfo->GetInfoName()); PostScriptController* pScriptController = new PostScriptController(); pScriptController->m_pPostInfo = pPostInfo; - pScriptController->SetScript(szScript); + pScriptController->SetScript(g_pOptions->GetPostProcess()); pScriptController->SetWorkingDir(g_pOptions->GetDestDir()); pScriptController->m_bNZBFileCompleted = bNZBFileCompleted; pScriptController->m_bHasFailedParJobs = bHasFailedParJobs; @@ -620,9 +637,14 @@ void PostScriptController::Run() szNZBName[1024-1] = '\0'; char szParStatus[10]; - snprintf(szParStatus, 10, "%i", m_pPostInfo->GetParStatus()); + snprintf(szParStatus, 10, "%i", g_pOptions->GetAllowReProcess() ? (int)m_pPostInfo->GetParStatus() : (int)m_pPostInfo->GetNZBInfo()->GetParStatus()); szParStatus[10-1] = '\0'; + int iUnpackStatus[] = { 0, 0, 1, 2 }; + char szUnpackStatus[10]; + snprintf(szUnpackStatus, 10, "%i", g_pOptions->GetAllowReProcess() ? 0 : iUnpackStatus[m_pPostInfo->GetNZBInfo()->GetUnpackStatus()]); + szUnpackStatus[10-1] = '\0'; + char szCollectionCompleted[10]; snprintf(szCollectionCompleted, 10, "%i", (int)m_bNZBFileCompleted); szCollectionCompleted[10-1] = '\0'; @@ -648,7 +670,7 @@ void PostScriptController::Run() szCategory[1024-1] = '\0'; char szInfoName[1024]; - snprintf(szInfoName, 1024, "post-process-script for %s", m_pPostInfo->GetInfoName()); + snprintf(szInfoName, 1024, "post-process-script for %s", g_pOptions->GetAllowReProcess() ? m_pPostInfo->GetInfoName() : m_pPostInfo->GetNZBInfo()->GetName()); szInfoName[1024-1] = '\0'; SetInfoName(szInfoName); @@ -672,6 +694,7 @@ void PostScriptController::Run() SetEnvVar("NZBPP_NZBFILENAME", szNZBFilename); SetEnvVar("NZBPP_PARFILENAME", szParFilename); SetEnvVar("NZBPP_PARSTATUS", szParStatus); + SetEnvVar("NZBPP_UNPACKSTATUS", szUnpackStatus); SetEnvVar("NZBPP_NZBCOMPLETED", szCollectionCompleted); SetEnvVar("NZBPP_PARFAILED", szHasFailedParJobs); SetEnvVar("NZBPP_CATEGORY", szCategory); @@ -689,14 +712,17 @@ void PostScriptController::Run() int iResult = Execute(); + szInfoName[0] = 'P'; // uppercase + switch (iResult) { case POSTPROCESS_SUCCESS: - info("%s sucessful", szInfoName); + info("%s successful", szInfoName); m_pPostInfo->SetScriptStatus(PostInfo::srSuccess); break; case POSTPROCESS_ERROR: + case -1: // Execute() returns -1 if the process could not be started (file not found or other problem) info("%s failed", szInfoName); m_pPostInfo->SetScriptStatus(PostInfo::srFailure); break; @@ -755,26 +781,18 @@ void PostScriptController::Run() m_pPostInfo->SetWorking(false); } -void PostScriptController::AddMessage(Message::EKind eKind, bool bDefaultKind, Options::EMessageTarget eMessageTarget, const char* szText) +void PostScriptController::AddMessage(Message::EKind eKind, bool bDefaultKind, const char* szText) { if (!strncmp(szText, "[HISTORY] ", 10)) { - if (eMessageTarget == Options::mtScreen || eMessageTarget == Options::mtBoth) - { - m_pPostInfo->GetNZBInfo()->AppendMessage(eKind, 0, szText + 10); - } + m_pPostInfo->GetNZBInfo()->AppendMessage(eKind, 0, szText + 10); } else { - ScriptController::AddMessage(eKind, bDefaultKind, eMessageTarget, szText); - - if (eMessageTarget == Options::mtScreen || eMessageTarget == Options::mtBoth) - { - m_pPostInfo->AppendMessage(eKind, szText); - } + ScriptController::AddMessage(eKind, bDefaultKind, szText); + m_pPostInfo->AppendMessage(eKind, szText); } - if (g_pOptions->GetPausePostProcess()) { time_t tStageTime = m_pPostInfo->GetStageTime(); @@ -854,7 +872,7 @@ void NZBScriptController::ExecuteScript(const char* szScript, const char* szNZBF delete pScriptController; } -void NZBScriptController::AddMessage(Message::EKind eKind, bool bDefaultKind, Options::EMessageTarget eMessageTarget, const char* szText) +void NZBScriptController::AddMessage(Message::EKind eKind, bool bDefaultKind, const char* szText) { if (!strncmp(szText, "[NZB] ", 6)) { @@ -890,7 +908,7 @@ void NZBScriptController::AddMessage(Message::EKind eKind, bool bDefaultKind, Op } else { - ScriptController::AddMessage(eKind, bDefaultKind, eMessageTarget, szText); + ScriptController::AddMessage(eKind, bDefaultKind, szText); } } diff --git a/ScriptController.h b/ScriptController.h index 9ee505af..8e4413b8 100644 --- a/ScriptController.h +++ b/ScriptController.h @@ -1,7 +1,7 @@ /* * This file is part of nzbget * - * Copyright (C) 2007-2011 Andrey Prygunkov + * Copyright (C) 2007-2013 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 @@ -70,11 +70,14 @@ private: pid_t m_hProcess; #endif - void ProcessOutput(char* szText); void PrepareEnvironmentStrings(); protected: - virtual void AddMessage(Message::EKind eKind, bool bDefaultKind, Options::EMessageTarget eMessageTarget, const char* szText); + void ProcessOutput(char* szText); + virtual bool ReadLine(char* szBuf, int iBufSize, FILE* pStream); + void PrintMessage(Message::EKind eKind, const char* szFormat, ...); + virtual void AddMessage(Message::EKind eKind, bool bDefaultKind, const char* szText); + bool GetTerminated() { return m_bTerminated; } public: ScriptController(); @@ -93,7 +96,7 @@ public: void SetEnvVar(const char* szName, const char* szValue); }; -class PostScriptController : public Thread, ScriptController +class PostScriptController : public Thread, public ScriptController { private: PostInfo* m_pPostInfo; @@ -101,13 +104,12 @@ private: bool m_bHasFailedParJobs; protected: - virtual void AddMessage(Message::EKind eKind, bool bDefaultKind, Options::EMessageTarget eMessageTarget, const char* szText); + virtual void AddMessage(Message::EKind eKind, bool bDefaultKind, const char* szText); public: virtual void Run(); virtual void Stop(); - static void StartScriptJob(PostInfo* pPostInfo, const char* szScript, - bool bNZBFileCompleted, bool bHasFailedParJobs); + static void StartScriptJob(PostInfo* pPostInfo, bool bNZBFileCompleted, bool bHasFailedParJobs); }; class NZBScriptController : public ScriptController @@ -118,13 +120,13 @@ private: NZBParameterList* m_pParameterList; protected: - virtual void AddMessage(Message::EKind eKind, bool bDefaultKind, Options::EMessageTarget eMessageTarget, const char* szText); + virtual void AddMessage(Message::EKind eKind, bool bDefaultKind, const char* szText); public: static void ExecuteScript(const char* szScript, const char* szNZBFilename, const char* szDirectory, char** pCategory, int* iPriority, NZBParameterList* pParameterList); }; -class NZBAddedScriptController : public Thread, ScriptController +class NZBAddedScriptController : public Thread, public ScriptController { private: char* m_szNZBName; @@ -134,7 +136,7 @@ public: static void StartScript(DownloadQueue* pDownloadQueue, NZBInfo *pNZBInfo, const char* szScript); }; -class SchedulerScriptController : public Thread, ScriptController +class SchedulerScriptController : public Thread, public ScriptController { public: virtual void Run(); diff --git a/Unpack.cpp b/Unpack.cpp new file mode 100644 index 00000000..1e8eb5b0 --- /dev/null +++ b/Unpack.cpp @@ -0,0 +1,539 @@ +/* + * This file is part of nzbget + * + * Copyright (C) 2013 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 +#endif + +#include "nzbget.h" +#include "Unpack.h" +#include "Log.h" +#include "Util.h" +#include "ParCoordinator.h" + +extern Options* g_pOptions; +extern DownloadQueueHolder* g_pDownloadQueueHolder; + +UnpackController::~UnpackController() +{ + for (FileList::iterator it = m_archiveFiles.begin(); it != m_archiveFiles.end(); it++) + { + free(*it); + } +} + +void UnpackController::StartUnpackJob(PostInfo* pPostInfo) +{ + UnpackController* pUnpackController = new UnpackController(); + pUnpackController->m_pPostInfo = pPostInfo; + pUnpackController->SetAutoDestroy(false); + + pPostInfo->SetScriptThread(pUnpackController); + + pUnpackController->Start(); +} + +void UnpackController::Run() +{ + // the locking is needed for accessing the members of NZBInfo + g_pDownloadQueueHolder->LockQueue(); + + strncpy(m_szDestDir, m_pPostInfo->GetNZBInfo()->GetDestDir(), 1024); + m_szDestDir[1024-1] = '\0'; + + char szName[1024]; + strncpy(szName, m_pPostInfo->GetNZBInfo()->GetName(), 1024); + szName[1024-1] = '\0'; + + bool bUnpack = true; + m_szPassword[0] = '\0'; + + for (NZBParameterList::iterator it = m_pPostInfo->GetNZBInfo()->GetParameters()->begin(); it != m_pPostInfo->GetNZBInfo()->GetParameters()->end(); it++) + { + NZBParameter* pParameter = *it; + if (!strcasecmp(pParameter->GetName(), "*Unpack:") && !strcasecmp(pParameter->GetValue(), "no")) + { + bUnpack = false; + } + if (!strcasecmp(pParameter->GetName(), "*Unpack:Password")) + { + strncpy(m_szPassword, pParameter->GetValue(), 1024-1); + m_szPassword[1024-1] = '\0'; + } + } + + g_pDownloadQueueHolder->UnlockQueue(); + + snprintf(m_szInfoName, 1024, "unpack for %s", szName); + m_szInfoName[1024-1] = '\0'; + + snprintf(m_szInfoNameUp, 1024, "Unpack for %s", szName); // first letter in upper case + m_szInfoNameUp[1024-1] = '\0'; + +#ifndef DISABLE_PARCHECK + if (bUnpack && HasBrokenFiles() && !m_pPostInfo->GetParCheck() && HasParFiles()) + { + info("%s has broken files", szName); + RequestParCheck(); + m_pPostInfo->SetWorking(false); + return; + } +#endif + + if (bUnpack) + { + CheckArchiveFiles(); + } + + if (bUnpack && (m_bHasRarFiles || m_bHasSevenZipFiles || m_bHasSevenZipMultiFiles)) + { + SetInfoName(m_szInfoName); + SetDefaultLogKind(g_pOptions->GetProcessLogKind()); + SetWorkingDir(m_szDestDir); + + PrintMessage(Message::mkInfo, "Unpacking %s", szName); + + CreateUnpackDir(); + + m_bUnpackOK = true; + m_bUnpackStartError = false; + + if (m_bHasRarFiles) + { + m_pPostInfo->SetProgressLabel(""); + ExecuteUnrar(); + } + + if (m_bHasSevenZipFiles && m_bUnpackOK) + { + m_pPostInfo->SetProgressLabel(""); + ExecuteSevenZip(false); + } + + if (m_bHasSevenZipMultiFiles && m_bUnpackOK) + { + m_pPostInfo->SetProgressLabel(""); + ExecuteSevenZip(true); + } + + Completed(); + } + else + { + PrintMessage(Message::mkInfo, (bUnpack ? "Nothing to unpack for %s" : "Unpack for %s skipped"), szName); + +#ifndef DISABLE_PARCHECK + if (bUnpack && !m_pPostInfo->GetParCheck() && HasParFiles()) + { + RequestParCheck(); + } + else +#endif + { + m_pPostInfo->SetUnpackStatus(PostInfo::usSkipped); + m_pPostInfo->GetNZBInfo()->SetUnpackStatus(NZBInfo::usSkipped); + m_pPostInfo->SetStage(PostInfo::ptQueued); + } + } + + m_pPostInfo->SetWorking(false); +} + +void UnpackController::ExecuteUnrar() +{ + // Format: + // unrar x -y -p- -o+ *.rar ./_unpack + + char szPasswordParam[1024]; + const char* szArgs[8]; + szArgs[0] = g_pOptions->GetUnrarCmd(); + szArgs[1] = "x"; + szArgs[2] = "-y"; + szArgs[3] = "-p-"; + if (strlen(m_szPassword) > 0) + { + snprintf(szPasswordParam, 1024, "-p%s", m_szPassword); + szArgs[3] = szPasswordParam; + } + szArgs[4] = "-o+"; + szArgs[5] = "*.rar"; + szArgs[6] = "./_unpack"; + szArgs[7] = NULL; + SetArgs(szArgs, false); + + SetScript(g_pOptions->GetUnrarCmd()); + SetDefaultKindPrefix("Unrar: "); + + m_bAllOKMessageReceived = false; + m_eUnpacker = upUnrar; + + int iExitCode = Execute(); + + m_bUnpackOK = iExitCode == 0 && m_bAllOKMessageReceived && !GetTerminated(); + m_bUnpackStartError = iExitCode == -1; + + if (!m_bUnpackOK && iExitCode > 0) + { + PrintMessage(Message::mkError, "Unrar error code: %i", iExitCode); + } +} + +void UnpackController::ExecuteSevenZip(bool bMultiVolumes) +{ + // Format: + // 7z x -y -p- -o./_unpack *.7z + // OR + // 7z x -y -p- -o./_unpack *.7z.001 + + char szPasswordParam[1024]; + const char* szArgs[7]; + szArgs[0] = g_pOptions->GetSevenZipCmd(); + szArgs[1] = "x"; + szArgs[2] = "-y"; + szArgs[3] = "-p-"; + if (strlen(m_szPassword) > 0) + { + snprintf(szPasswordParam, 1024, "-p%s", m_szPassword); + szArgs[3] = szPasswordParam; + } + szArgs[4] = "-o./_unpack"; + szArgs[5] = bMultiVolumes ? "*.7z.001" : "*.7z"; + szArgs[6] = NULL; + SetArgs(szArgs, false); + + SetScript(g_pOptions->GetSevenZipCmd()); + SetDefaultKindPrefix("7-Zip: "); + + m_bAllOKMessageReceived = false; + m_eUnpacker = upSevenZip; + + PrintMessage(Message::mkInfo, "Executing 7-Zip"); + int iExitCode = Execute(); + + m_bUnpackOK = iExitCode == 0 && m_bAllOKMessageReceived && !GetTerminated(); + m_bUnpackStartError = iExitCode == -1; + + if (!m_bUnpackOK && iExitCode > 0) + { + PrintMessage(Message::mkError, "7-Zip error code: %i", iExitCode); + } +} + +void UnpackController::Completed() +{ + bool bCleanupSuccess = Cleanup(); + + if (m_bUnpackOK && bCleanupSuccess) + { + PrintMessage(Message::mkInfo, "%s %s", m_szInfoNameUp, "successful"); + m_pPostInfo->SetUnpackStatus(PostInfo::usSuccess); + m_pPostInfo->GetNZBInfo()->SetUnpackStatus(NZBInfo::usSuccess); + m_pPostInfo->SetStage(PostInfo::ptQueued); + } + else + { +#ifndef DISABLE_PARCHECK + if (!m_bUnpackOK && !m_pPostInfo->GetParCheck() && !m_bUnpackStartError && !GetTerminated() && HasParFiles()) + { + RequestParCheck(); + } + else +#endif + { + PrintMessage(Message::mkError, "%s failed", m_szInfoNameUp); + m_pPostInfo->SetUnpackStatus(PostInfo::usFailure); + m_pPostInfo->GetNZBInfo()->SetUnpackStatus(NZBInfo::usFailure); + m_pPostInfo->SetStage(PostInfo::ptQueued); + } + } +} + +#ifndef DISABLE_PARCHECK +void UnpackController::RequestParCheck() +{ + PrintMessage(Message::mkInfo, "%s requested par-check/repair", m_szInfoNameUp); + m_pPostInfo->SetRequestParCheck(PostInfo::rpAll); + m_pPostInfo->SetStage(PostInfo::ptFinished); +} +#endif + +bool UnpackController::HasParFiles() +{ + return ParCoordinator::FindMainPars(m_szDestDir, NULL); +} + +bool UnpackController::HasBrokenFiles() +{ + char szBrokenLog[1024]; + snprintf(szBrokenLog, 1024, "%s%c%s", m_szDestDir, PATH_SEPARATOR, "_brokenlog.txt"); + szBrokenLog[1024-1] = '\0'; + return Util::FileExists(szBrokenLog); +} + +void UnpackController::CreateUnpackDir() +{ + snprintf(m_szUnpackDir, 1024, "%s%c%s", m_szDestDir, PATH_SEPARATOR, "_unpack"); + m_szUnpackDir[1024-1] = '\0'; + Util::ForceDirectories(m_szUnpackDir); +} + + +void UnpackController::CheckArchiveFiles() +{ + m_bHasRarFiles = false; + m_bHasSevenZipFiles = false; + m_bHasSevenZipMultiFiles = false; + + RegEx regExRar(".*\\.rar$"); + RegEx regExSevenZip(".*\\.7z$"); + RegEx regExSevenZipMulti(".*\\.7z\\.[0-9]*$"); + + DirBrowser dir(m_szDestDir); + while (const char* filename = dir.Next()) + { + char szFullFilename[1024]; + snprintf(szFullFilename, 1024, "%s%c%s", m_szDestDir, PATH_SEPARATOR, filename); + szFullFilename[1024-1] = '\0'; + + if (strcmp(filename, ".") && strcmp(filename, "..") && !Util::DirectoryExists(szFullFilename)) + { + if (regExRar.Match(filename)) + { + m_bHasRarFiles = true; + } + if (regExSevenZip.Match(filename)) + { + m_bHasSevenZipFiles = true; + } + if (regExSevenZipMulti.Match(filename)) + { + m_bHasSevenZipMultiFiles = true; + } + } + } +} + +bool UnpackController::Cleanup() +{ + // By success: + // - move unpacked files to destination dir; + // - remove _unpack-dir; + // - delete archive-files. + // By failure: + // - remove _unpack-dir. + + bool bOK = true; + + if (m_bUnpackOK) + { + // moving files back + DirBrowser dir(m_szUnpackDir); + while (const char* filename = dir.Next()) + { + if (strcmp(filename, ".") && strcmp(filename, "..")) + { + char szSrcFile[1024]; + snprintf(szSrcFile, 1024, "%s%c%s", m_szUnpackDir, PATH_SEPARATOR, filename); + szSrcFile[1024-1] = '\0'; + + char szDstFile[1024]; + snprintf(szDstFile, 1024, "%s%c%s", m_szDestDir, PATH_SEPARATOR, filename); + szDstFile[1024-1] = '\0'; + + // silently overwrite existing files + remove(szDstFile); + + if (!Util::MoveFile(szSrcFile, szDstFile)) + { + PrintMessage(Message::mkError, "Could not move file %s to %s", szSrcFile, szDstFile); + bOK = false; + } + } + } + } + + if (bOK && !Util::DeleteDirectoryWithContent(m_szUnpackDir)) + { + PrintMessage(Message::mkError, "Could not remove temporary directory %s", m_szUnpackDir); + } + + if (m_bUnpackOK && bOK && g_pOptions->GetUnpackCleanupDisk()) + { + PrintMessage(Message::mkInfo, "Deleting archive files"); + + // Delete rar-files (only files which were used by unrar) + for (FileList::iterator it = m_archiveFiles.begin(); it != m_archiveFiles.end(); it++) + { + char* szFilename = *it; + + char szFullFilename[1024]; + snprintf(szFullFilename, 1024, "%s%c%s", m_szDestDir, PATH_SEPARATOR, szFilename); + szFullFilename[1024-1] = '\0'; + + PrintMessage(Message::mkDetail, "Deleting file %s", szFilename); + + if (remove(szFullFilename) != 0) + { + PrintMessage(Message::mkError, "Could not delete file %s", szFullFilename); + } + } + + // Unfortunately 7-Zip doesn't print the processed archive-files to the output. + // Therefore we don't know for sure which files were extracted. + // We just delete all 7z-files in the directory. + + RegEx regExSevenZip(".*\\.7z$|.*\\.7z\\.[0-9]*$"); + + DirBrowser dir(m_szDestDir); + while (const char* filename = dir.Next()) + { + char szFullFilename[1024]; + snprintf(szFullFilename, 1024, "%s%c%s", m_szDestDir, PATH_SEPARATOR, filename); + szFullFilename[1024-1] = '\0'; + + if (strcmp(filename, ".") && strcmp(filename, "..") && !Util::DirectoryExists(szFullFilename)) + { + if (regExSevenZip.Match(filename)) + { + PrintMessage(Message::mkDetail, "Deleting file %s", filename); + + if (remove(szFullFilename) != 0) + { + PrintMessage(Message::mkError, "Could not delete file %s", szFullFilename); + } + } + } + } + } + + return bOK; +} + +/** + * Unrar prints progress information into the same line using backspace control character. + * In order to print progress continuously we analyze the output after every char + * and update post-job progress information. + */ +bool UnpackController::ReadLine(char* szBuf, int iBufSize, FILE* pStream) +{ + bool bPrinted = false; + + int i = 0; + + for (; i < iBufSize - 1; i++) + { + int ch = fgetc(pStream); + szBuf[i] = ch; + szBuf[i+1] = '\0'; + if (ch == EOF) + { + break; + } + if (ch == '\n') + { + i++; + break; + } + + char* szBackspace = strrchr(szBuf, '\b'); + if (szBackspace) + { + if (!bPrinted) + { + char tmp[1024]; + strncpy(tmp, szBuf, 1024); + tmp[1024-1] = '\0'; + char* szTmpPercent = strrchr(tmp, '\b'); + if (szTmpPercent) + { + *szTmpPercent = '\0'; + } + if (strncmp(szBuf, "...", 3)) + { + ProcessOutput(tmp); + } + bPrinted = true; + } + if (strchr(szBackspace, '%')) + { + int iPercent = atoi(szBackspace + 1); + m_pPostInfo->SetStageProgress(iPercent * 10); + } + } + } + + szBuf[i] = '\0'; + + if (bPrinted) + { + szBuf[0] = '\0'; + } + + return i > 0; +} + +void UnpackController::AddMessage(Message::EKind eKind, bool bDefaultKind, const char* szText) +{ + ScriptController::AddMessage(eKind, bDefaultKind, szText); + m_pPostInfo->AppendMessage(eKind, szText); + + if (m_eUnpacker == upUnrar && !strncmp(szText, "Extracting from ", 16)) + { + const char *szFilename = szText + 16; + debug("Filename: %s", szFilename); + m_archiveFiles.push_back(strdup(szFilename)); + m_pPostInfo->SetProgressLabel(szText); + } + + if (m_eUnpacker == upUnrar && !strncmp(szText, "Extracting ", 11)) + { + m_pPostInfo->SetProgressLabel(szText); + } + + if ((m_eUnpacker == upUnrar && !strncmp(szText, "All OK", 6)) || + (m_eUnpacker == upSevenZip && !strncmp(szText, "Everything is Ok", 16))) + { + m_bAllOKMessageReceived = true; + } +} + +void UnpackController::Stop() +{ + debug("Stopping unpack"); + Thread::Stop(); + Terminate(); +} diff --git a/Unpack.h b/Unpack.h new file mode 100644 index 00000000..a3c9885c --- /dev/null +++ b/Unpack.h @@ -0,0 +1,87 @@ +/* + * This file is part of nzbget + * + * Copyright (C) 2013 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$ + * + */ + + +#ifndef UNPACK_H +#define UNPACK_H + +#include + +#include "Log.h" +#include "Thread.h" +#include "DownloadInfo.h" +#include "ScriptController.h" + +class UnpackController : public Thread, public ScriptController +{ +private: + enum EUnpacker + { + upUnrar, + upSevenZip + }; + +private: + PostInfo* m_pPostInfo; + char m_szInfoName[1024]; + char m_szInfoNameUp[1024]; + char m_szDestDir[1024]; + char m_szUnpackDir[1024]; + char m_szPassword[1024]; + bool m_bAllOKMessageReceived; + bool m_bNoFilesMessageReceived; + bool m_bHasRarFiles; + bool m_bHasSevenZipFiles; + bool m_bHasSevenZipMultiFiles; + bool m_bUnpackOK; + bool m_bUnpackStartError; + EUnpacker m_eUnpacker; + + typedef std::deque FileList; + + FileList m_archiveFiles; + +protected: + virtual bool ReadLine(char* szBuf, int iBufSize, FILE* pStream); + virtual void AddMessage(Message::EKind eKind, bool bDefaultKind, const char* szText); + void ExecuteUnrar(); + void ExecuteSevenZip(bool bMultiVolumes); + void Completed(); + void CreateUnpackDir(); + bool Cleanup(); + bool HasParFiles(); + bool HasBrokenFiles(); + void CheckArchiveFiles(); +#ifndef DISABLE_PARCHECK + void RequestParCheck(); +#endif + +public: + virtual ~UnpackController(); + virtual void Run(); + virtual void Stop(); + static void StartUnpackJob(PostInfo* pPostInfo); +}; + +#endif diff --git a/Util.cpp b/Util.cpp index f1a06fae..4377f672 100644 --- a/Util.cpp +++ b/Util.cpp @@ -599,6 +599,42 @@ bool Util::CreateDirectory(const char* szDirFilename) return DirectoryExists(szDirFilename); } +bool Util::RemoveDirectory(const char* szDirFilename) +{ +#ifdef WIN32 + return ::RemoveDirectory(szDirFilename); +#else + return remove(szDirFilename) == 0; +#endif +} + +bool Util::DeleteDirectoryWithContent(const char* szDirFilename) +{ + bool bOK = true; + + DirBrowser dir(szDirFilename); + while (const char* filename = dir.Next()) + { + char szFullFilename[1024]; + snprintf(szFullFilename, 1024, "%s%c%s", szDirFilename, PATH_SEPARATOR, filename); + szFullFilename[1024-1] = '\0'; + + if (strcmp(filename, ".") && strcmp(filename, "..")) + { + if (Util::DirectoryExists(szFullFilename)) + { + bOK &= DeleteDirectoryWithContent(szFullFilename); + } + else + { + bOK &= remove(szFullFilename) == 0; + } + } + } + + return bOK && RemoveDirectory(szDirFilename); +} + long long Util::FileSize(const char* szFilename) { #ifdef WIN32 diff --git a/Util.h b/Util.h index 80939a80..2aeb4d3c 100644 --- a/Util.h +++ b/Util.h @@ -71,6 +71,8 @@ public: static bool FileExists(const char* szFilename); static bool DirectoryExists(const char* szDirFilename); static bool CreateDirectory(const char* szDirFilename); + static bool RemoveDirectory(const char* szDirFilename); + static bool DeleteDirectoryWithContent(const char* szDirFilename); static bool ForceDirectories(const char* szPath); static bool GetCurrentDirectory(char* szBuffer, int iBufSize); static bool SetCurrentDirectory(const char* szDirFilename); diff --git a/XmlRpc.cpp b/XmlRpc.cpp index 85ead830..596565d7 100644 --- a/XmlRpc.cpp +++ b/XmlRpc.cpp @@ -1,7 +1,7 @@ /* * This file is part of nzbget * - * Copyright (C) 2007-2011 Andrey Prygunkov + * Copyright (C) 2007-2013 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 @@ -1629,7 +1629,7 @@ void PostQueueXmlCommand::Execute() { PostInfo* pPostInfo = *it; - const char* szPostStageName[] = { "QUEUED", "LOADING_PARS", "VERIFYING_SOURCES", "REPAIRING", "VERIFYING_REPAIRED", "EXECUTING_SCRIPT", "FINISHED" }; + const char* szPostStageName[] = { "QUEUED", "LOADING_PARS", "VERIFYING_SOURCES", "REPAIRING", "VERIFYING_REPAIRED", "UNPACKING", "EXECUTING_SCRIPT", "FINISHED" }; char* xmlNZBNicename = EncodeStr(pPostInfo->GetNZBInfo()->GetName()); char* xmlNZBFilename = EncodeStr(pPostInfo->GetNZBInfo()->GetFilename()); @@ -1786,6 +1786,7 @@ void HistoryXmlCommand::Execute() "DestDir%s\n" "Category%s\n" "ParStatus%s\n" + "UnpackStatus%s\n" "ScriptStatus%s\n" "FileSizeLo%u\n" "FileSizeHi%u\n" @@ -1816,6 +1817,7 @@ void HistoryXmlCommand::Execute() "\"DestDir\" : \"%s\",\n" "\"Category\" : \"%s\",\n" "\"ParStatus\" : \"%s\",\n" + "\"UnpackStatus\" : \"%s\",\n" "\"ScriptStatus\" : \"%s\",\n" "\"FileSizeLo\" : %u,\n" "\"FileSizeHi\" : %u,\n" @@ -1864,6 +1866,7 @@ void HistoryXmlCommand::Execute() "}"; const char* szParStatusName[] = { "NONE", "FAILURE", "REPAIR_POSSIBLE", "SUCCESS" }; + const char* szUnpackStatusName[] = { "NONE", "NONE", "FAILURE", "SUCCESS" }; const char* szScriptStatusName[] = { "NONE", "UNKNOWN", "FAILURE", "SUCCESS" }; const char* szUrlStatusName[] = { "UNKNOWN", "UNKNOWN", "SUCCESS", "FAILURE", "UNKNOWN" }; const char* szMessageType[] = { "INFO", "WARNING", "ERROR", "DEBUG", "DETAIL"}; @@ -1899,9 +1902,10 @@ void HistoryXmlCommand::Execute() snprintf(szItemBuf, szItemBufSize, IsJson() ? JSON_HISTORY_ITEM_START : XML_HISTORY_ITEM_START, pHistoryInfo->GetID(), pHistoryInfo->GetID(), "NZB", xmlNicename, xmlNicename, xmlNZBFilename, - xmlDestDir, xmlCategory, szParStatusName[pNZBInfo->GetParStatus()], - szScriptStatusName[pNZBInfo->GetScriptStatus()], iFileSizeLo, iFileSizeHi, iFileSizeMB, - pNZBInfo->GetFileCount(), pNZBInfo->GetParkedFileCount(), pHistoryInfo->GetTime(), "", ""); + xmlDestDir, xmlCategory, szParStatusName[pNZBInfo->GetParStatus()], + szUnpackStatusName[pNZBInfo->GetUnpackStatus()], szScriptStatusName[pNZBInfo->GetScriptStatus()], + iFileSizeLo, iFileSizeHi, iFileSizeMB, pNZBInfo->GetFileCount(), + pNZBInfo->GetParkedFileCount(), pHistoryInfo->GetTime(), "", ""); free(xmlDestDir); } diff --git a/nzbget-postprocess.conf b/nzbget-postprocess.conf index f4f9b4b0..b71f7ac5 100644 --- a/nzbget-postprocess.conf +++ b/nzbget-postprocess.conf @@ -1,10 +1,10 @@ # # This file if part of nzbget # -# Template configuration file for postprocessing script "nzbget-postprocess.sh". +# Template configuration file for post-processing script "nzbget-postprocess.sh". # Please refer to "nzbget-postprocess.sh" for usage instructions. # -# Copyright (C) 2008-2012 Andrey Prygunkov +# Copyright (C) 2008-2013 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 @@ -22,45 +22,29 @@ # # -############################################################################## -### PATHS ### - -# Set the full path to unrar if it is not in your PATH. -UnrarCmd=unrar - ############################################################################## ### OPTIONS ### -# Delete rar-files after unpacking (yes, no). -DeleteRarFiles=yes - # Rename img-files to iso (yes, no). RenameIMG=yes # Joint TS-files (yes, no). -JoinTS=no +JoinTS=yes ############################################################################## ### POSTPROCESSING-PARAMETERS ### # This section defines parameters, which can be set for each nzb-file # individually using either web-interface or command line. -# Example command line for setting parameter "password" to value "123" for +# Example command line for setting parameter "PostProcess" to value "no" for # nzb-file with id=2: -# nzbget -E G O Password=123 2 +# nzbget -E G O PostProcess=no 2 # Perform postprocessing (yes, no). # # Set to "no" to skip postprocessing for this nzb-file. PostProcess=yes -# Password for encrypted posts. -# -# If the post requires a password for unpacking. -Password= - # Destination directory. -# -# NOTE: NZBGet must have write-access-rights for that directory. DestDir= diff --git a/nzbget-postprocess.sh b/nzbget-postprocess.sh index 0c4d3ea2..3c7ca92d 100755 --- a/nzbget-postprocess.sh +++ b/nzbget-postprocess.sh @@ -6,7 +6,7 @@ # # Copyright (C) 2008 Peter Roubos # Copyright (C) 2008 Otmar Werner -# Copyright (C) 2008-2012 Andrey Prygunkov +# Copyright (C) 2008-2013 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 @@ -25,8 +25,7 @@ # ####################### Usage instructions ####################### -# o Script will unrar downloaded rar files, join ts-files and rename img-files -# to iso. +# o Script will cleanup, join ts-files and rename img-files to iso. # # o To use this script with nzbget set the option "PostProcess" in # nzbget configuration file to point to this script file. E.g.: @@ -41,41 +40,29 @@ # # o There are few options, which can be ajdusted for each nzb-file individually. # -# o The script supports the feature called "delayed par-check". -# That means it can try to unpack downloaded files without par-checking -# them fisrt. Only if unpack fails, the script schedules par-check, -# then unpacks again. -# To use delayed par-check set following options in nzbget configuration file: -# ParCheck=no -# ParRepair=yes -# LoadPars=one (or) LoadPars=all -# -# o If you want to par-check/repair all files before trying to unpack them, -# set option "ParCheck=yes". -# ####################### End of Usage instructions ####################### # NZBGet passes following arguments to postprocess-programm as environment # variables: # NZBPP_DIRECTORY - path to destination dir for downloaded files; -# NZBPP_NZBFILENAME - name of processed nzb-file; -# NZBPP_PARFILENAME - name of par-file or empty string (if no collections were -# found); +# NZBPP_NZBNAME - user-friendly name of processed nzb-file as it is displayed +# by the program. The file path and extension are removed. +# If download was renamed, this parameter reflects the new name; +# NZBPP_NZBFILENAME - name of processed nzb-file. It includes file extension and also +# may include full path; +# NZBPP_CATEGORY - category assigned to nzb-file (can be empty string); # NZBPP_PARSTATUS - result of par-check: -# 0 = not checked: par-check disabled or nzb-file does +# 0 = not checked: par-check is disabled or nzb-file does # not contain any par-files; # 1 = checked and failed to repair; # 2 = checked and successfully repaired; -# 3 = checked and can be repaired but repair is disabled; -# NZBPP_NZBCOMPLETED - state of nzb-job: -# 0 = there are more collections in this nzb-file queued; -# 1 = this was the last collection in nzb-file; -# NZBPP_PARFAILED - indication of failed par-jobs for current nzb-file: -# 0 = no failed par-jobs; -# 1 = current par-job or any of the previous par-jobs for -# the same nzb-files failed; -# NZBPP_CATEGORY - category assigned to nzb-file (can be empty string). +# 3 = checked and can be repaired but repair is disabled. +# NZBPP_UNPACKSTATUS - result of unpack: +# 0 = unpack is disabled or was skipped due to nzb-file +# properties or due to errors during par-check; +# 1 = unpack failed; +# 2 = unpack successful. # Name of script's configuration file @@ -99,7 +86,7 @@ fi # (for current nzb-file) via web-interface or via command line with # "nzbget -E G O PostProcess=no " if [ "$NZBPR_PostProcess" = "no" ]; then - echo "[WARNING] Post-Process: Postprocessing disabled for this nzb-file, exiting" + echo "[WARNING] Post-Process: Post-processing disabled for this nzb-file, exiting" exit $POSTPROCESS_NONE fi @@ -129,27 +116,11 @@ if [ "$NZBOP_ALLOWREPROCESS" = "yes" ]; then BadConfig=1 fi -if [ "$NZBOP_LOADPARS" = "none" ]; then - echo "[ERROR] Post-Process: Please set option \"LoadPars\" to \"One\" or \"All\" in nzbget configuration file" - BadConfig=1 -fi - -if [ "$NZBOP_PARREPAIR" = "no" ]; then - echo "[ERROR] Post-Process: Please set option \"ParRepair\" to \"Yes\" in nzbget configuration file" - BadConfig=1 -fi - if [ "$BadConfig" -eq 1 ]; then - echo "[ERROR] Post-Process: Exiting because of not compatible nzbget configuration" + echo "[ERROR] Post-Process: Exiting due to incompatible nzbget configuration" exit $POSTPROCESS_ERROR fi -# Check if all collections in nzb-file were downloaded -if [ ! "$NZBPP_NZBCOMPLETED" -eq 1 ]; then - echo "[INFO] Post-Process: Not the last collection in nzb-file, exiting" - exit $POSTPROCESS_SUCCESS -fi - # Check par status if [ "$NZBPP_PARSTATUS" -eq 1 -o "$NZBPP_PARSTATUS" -eq 3 -o "$NZBPP_PARFAILED" -eq 1 ]; then if [ "$NZBPP_PARSTATUS" -eq 3 ]; then @@ -157,7 +128,13 @@ if [ "$NZBPP_PARSTATUS" -eq 1 -o "$NZBPP_PARSTATUS" -eq 3 -o "$NZBPP_PARFAILED" else echo "[WARNING] Post-Process: Par-check failed, exiting" fi - exit $POSTPROCESS_ERROR + exit $POSTPROCESS_NONE +fi + +# Check unpack status +if [ "$NZBPP_UNPACKSTATUS" -ne 2 ]; then + echo "[WARNING] Post-Process: Unpack failed or disabled, exiting" + exit $POSTPROCESS_NONE fi # Check if destination directory exists (important for reprocessing of history items) @@ -168,109 +145,8 @@ fi cd "$NZBPP_DIRECTORY" -# If not just repaired and file "_brokenlog.txt" exists, the collection is damaged -# exiting with returning code $POSTPROCESS_PARCHECK_ALL to request par-repair -if [ ! "$NZBPP_PARSTATUS" -eq 2 ]; then - if [ -f "_brokenlog.txt" ]; then - if (ls *.[pP][aA][rR]2 >/dev/null 2>&1); then - echo "[INFO] Post-Process: Brokenlog found, requesting par-repair" - exit $POSTPROCESS_PARCHECK_ALL - fi - fi -fi - # All checks done, now processing the files -# Flag indicates that something was unrared -Unrared=0 - -# Unrar the files (if any) to the temporary directory, if there are no rar files this will do nothing -if (ls *.rar >/dev/null 2>&1); then - - # Check if unrar exists - $UnrarCmd >/dev/null 2>&1 - if [ "$?" -eq 127 ]; then - echo "[ERROR] Post-Process: Unrar not found. Set the path to unrar in script's configuration" - exit $POSTPROCESS_ERROR - fi - - # Make a temporary directory to store the unrarred files - ExtractedDirExists=0 - if [ -d extracted ]; then - ExtractedDirExists=1 - else - mkdir extracted - fi - - echo "[INFO] Post-Process: Unraring" - rarpasswordparam="" - if [ "$NZBPR_Password" != "" ]; then - rarpasswordparam="-p$NZBPR_Password" - fi - - $UnrarCmd x -y -p- "$rarpasswordparam" -o+ "*.rar" ./extracted/ - if [ "$?" -ne 0 ]; then - echo "[ERROR] Post-Process: Unrar failed" - if [ "$ExtractedDirExists" -eq 0 ]; then - rm -R extracted - fi - # for delayed par-check/-repair at least one par-file must be already downloaded - if (ls *.[pP][aA][rR]2 >/dev/null 2>&1); then - echo "[INFO] Post-Process: Requesting par-repair" - exit $POSTPROCESS_PARCHECK_ALL - fi - exit $POSTPROCESS_ERROR - fi - Unrared=1 - - # Remove the rar files - if [ "$DeleteRarFiles" = "yes" ]; then - echo "[INFO] Post-Process: Deleting rar-files" - rm *.r[0-9][0-9] >/dev/null 2>&1 - rm *.rar >/dev/null 2>&1 - rm *.s[0-9][0-9] >/dev/null 2>&1 - fi - - # Go to the temp directory and try to unrar again. - # If there are any rars inside the extracted rars then these will no also be unrarred - cd extracted - if (ls *.rar >/dev/null 2>&1); then - echo "[INFO] Post-Process: Unraring (second pass)" - $UnrarCmd x -y -p- -o+ "*.rar" - - if [ "$?" -ne 0 ]; then - echo "[INFO] Post-Process: Unrar (second pass) failed" - exit $POSTPROCESS_ERROR - fi - - # Delete the Rar files - if [ "$DeleteRarFiles" = "yes" ]; then - echo "[INFO] Post-Process: Deleting rar-files (second pass)" - rm *.r[0-9][0-9] >/dev/null 2>&1 - rm *.rar >/dev/null 2>&1 - rm *.s[0-9][0-9] >/dev/null 2>&1 - fi - fi - - # Move everything back to the Download folder - mv * .. - cd .. - rmdir extracted -fi - - -# If there were nothing to unrar and the download was not par-checked, -# we don't know if it's OK. To be sure we force par-check. -# In particular that helps with downloads containing renamed rar-files. -# The par-repair will rename files to correct names, then we can unpack. -if [ "$Unrared" -eq 0 -a "$NZBPP_PARSTATUS" -eq 0 ]; then - if (ls *.[pP][aA][rR]2 >/dev/null 2>&1); then - echo "[INFO] Post-Process: No rar-files found, requesting par-check" - exit $POSTPROCESS_PARCHECK_ALL - fi -fi - - # If download contains only nzb-files move them into nzb-directory # for further download # Check if command "wc" exists @@ -291,10 +167,7 @@ rm *.nzb >/dev/null 2>&1 rm *.sfv >/dev/null 2>&1 rm *.1 >/dev/null 2>&1 rm _brokenlog.txt >/dev/null 2>&1 -if [ "$Unrared" -eq 1 ]; then - # Delete par2-file only if there were files for unpacking. - rm *.[pP][aA][rR]2 >/dev/null 2>&1 -fi +rm *.[pP][aA][rR]2 >/dev/null 2>&1 if [ "$JoinTS" = "yes" ]; then # Join any split .ts files if they are named xxxx.0000.ts xxxx.0001.ts diff --git a/nzbget.conf b/nzbget.conf index b5b27eff..9566bb42 100644 --- a/nzbget.conf +++ b/nzbget.conf @@ -66,7 +66,6 @@ LogFile=${DestDir}/nzbget.log # it is also used to serve JSON-/XML-RPC requests. WebDir= - ############################################################################## ### NEWS-SERVERS ### @@ -738,7 +737,11 @@ UpdateInterval=200 # Paused files remain in queue and can be unpaused by parchecker when needed. LoadPars=one -# Automatic par-verification (yes, no). +# Force par-verification (yes, no). +# +# Force par-check for every download. When set to "no" the par-check is +# performed only if the unpacker or the post-processing script detect a +# damaged download. # # To download only needed par2-files (smart par-files loading) set also # the option to "one". If option is set to "all", @@ -751,7 +754,7 @@ ParCheck=no # # If option is enabled and is not, the program # only verifies downloaded files and downloads needed par2-files, but does -# not start repair-process. This is useful if the server does not have +# not start repair-process. This is useful if computer does not have # enough CPU power, since repairing of large files may take too much # resources and time on a slow computers. ParRepair=yes @@ -823,7 +826,7 @@ ParTimeLimit=0 # NOTE: If parchecker needs additional par-files it temporarily unpauses # the queue. # -# NOTE: See also option . +# NOTE: See also options and . ParPauseQueue=no # Cleanup download queue after successful check/repair (yes, no). @@ -839,38 +842,110 @@ ParCleanupQueue=yes NzbCleanupDisk=no +############################################################################## +### UNPACK ### + +# Unpack downloaded nzb-files (yes, no). +# +# If the download is damaged and could not be repaired using par-files +# the unpacking is not performed. +# +# If the option is disabled the program will try to unpack +# downloaded files first. If the unpacking fails the par-check/repair +# is performed and the unpack will be executed again. +Unpack=yes + +# Pause download queue during unpack (yes, no). +# +# Enable the option to give CPU more time for unpacking. That helps +# to speed up unpacking on slow CPUs. +# +# NOTE: See also options and . +UnpackPauseQueue=no + +# Delete archive files after successful unpacking (yes, no). +UnpackCleanupDisk=yes + +# Full path to unrar executable. +# +# Example: "/usr/bin/unrar". +# +# If unrar is in your PATH you may leave the path part and set only +# the executable name ("unrar" on POSIX or "unrar.exe" on Windows). +UnrarCmd=unrar + +# Full path to 7-Zip executable. +# +# Example: "/usr/bin/7z". +# +# If 7-Zip binary is in your PATH you may leave the path part and set only +# the executable name ("7z" or "7za" on POSIX or "7z.exe" on Windows). +SevenZipCmd=7z + + ############################################################################## ### POST-PROCESSING ### # Set path to program, that must be executed after the download of nzb-file -# or one collection in nzb-file is completed and possibly par-checked/repaired. +# is completed and possibly par-checked/repaired and unpacked, depending +# on other options. # # Example: "PostProcess=~/nzbget-postprocess.sh". # -# NOTE: An example script for unrarring is provided within distribution -# in file "nzbget-postprocess.sh". +# NOTE: An example script is provided within distribution in file +# "nzbget-postprocess.sh". +# +# NOTE: Since version 10.0 NZBGet has a built-in support for unpack +# (option ). In the previous versions unpack was performed by +# post-processing scripts. If you use a script created for older NZBGet +# version you need to disable the built-in unpack for script to operate +# properly. # # INFO FOR DEVELOPERS: -# NZBGet passes following arguments to postprocess-program as environment +# If the option is disabled (that's the default setting) +# the post-processing script is executed once per nzb-file after +# par-check/repair (if needed) and unpacking (if enabled). +# +# If the option is active the post-processing script is +# executed for each collection within nzb-file (for nzb-files having multiple +# collections but at least once). Depending on option the collection +# could be already par-checked/repaired or the script could be called without +# par-check taken place. In the latest case if the script detects errors +# (such as unpack failed) it has an ability to request par-check from +# NZBGet. After par-check/repair NZBGet calls the script once again. +# +# NZBGet passes following arguments to post-processing script as environment # variables: # NZBPP_DIRECTORY - path to destination dir for downloaded files; -# NZBPP_NZBFILENAME - name of processed nzb-file; -# NZBPP_PARFILENAME - name of par-file or empty string (if no collections were -# found); +# NZBPP_NZBNAME - user-friendly name of processed nzb-file as it is displayed +# by the program. The file path and extension are removed. +# If download was renamed, this parameter reflects the new name; +# NZBPP_NZBFILENAME - name of processed nzb-file. It includes file extension and also +# may include full path; +# NZBPP_CATEGORY - category assigned to nzb-file (can be empty string); # NZBPP_PARSTATUS - result of par-check: -# 0 = not checked: par-check disabled or nzb-file does +# 0 = not checked: par-check is disabled or nzb-file does # not contain any par-files; # 1 = checked and failed to repair; # 2 = checked and successfully repaired; -# 3 = checked and can be repaired but repair is disabled; -# NZBPP_NZBCOMPLETED - state of nzb-job: -# 0 = there are more collections in this nzb-file queued; -# 1 = this was the last collection in nzb-file; +# 3 = checked and can be repaired but repair is disabled. +# NZBPP_UNPACKSTATUS - result of unpack: +# 0 = unpack is disabled or was skipped due to nzb-file +# properties or due to errors during par-check; +# 1 = unpack failed; +# 2 = unpack successful. +# +# In addition the following arguments are passed if the option +# is active: +# NZBPP_PARFILENAME - name of par-file or empty string (if no collections were +# found); # NZBPP_PARFAILED - indication of failed par-jobs for current nzb-file: # 0 = no failed par-jobs; # 1 = current par-job or any of the previous par-jobs for # the same nzb-files failed; -# NZBPP_CATEGORY - category assigned to nzb-file (can be empty string). +# NZBPP_NZBCOMPLETED - state of nzb-job: +# 0 = there are more collections in this nzb-file queued; +# 1 = this was the last collection in nzb-file. # # If nzb-file has associated postprocess-parameters (which can be set using # subcommand of command <-E>, for example: NZBGet -E G O "myvar=hello !" 10) @@ -888,13 +963,14 @@ NzbCleanupDisk=no # the values are passed always in lower case. # # Return value: NZBGet processes the exit code returned by the script: -# 91 - request NZBGet to do par-check/repair for current collection in the -# current nzb-file; -# 92 - request NZBGet to do par-check/repair for all collections in the -# current nzb-file; # 93 - post-process successful (status = SUCCESS); # 94 - post-process failed (status = FAILURE); # 95 - post-process skipped (status = NONE); +# 91 - request NZBGet to do par-check/repair for current collection in the +# current nzb-file. This return code is accepted only if the +# option is active; +# 92 - request NZBGet to do par-check/repair for all collections (par-sets) +# in the current nzb-file. # All other return codes are interpreted as "status unknown". # # The return value is used to display the status of post-processing in @@ -903,62 +979,42 @@ NzbCleanupDisk=no # to standard output. For example: # echo "[ERROR] [HISTORY] Unpack failed, not enough disk space"; # -# NOTE: The parameter NZBPP_NZBCOMPLETED is very important and MUST be checked -# even in the simplest scripts. -# If par-check is enabled and nzb-file contains more than one collection -# of files the postprocess-program is called after each collection is completed -# and par-checked. If you want to unpack files or clean up the directory -# (delete par-files, etc.) there are two possibilities, when you can do this: -# 1) you parse NZBPP_PARFILENAME to find out the base name of collection and -# clean up only files from this collection (not reliable, because par-files -# sometimes have different names than rar-files); -# 2) or you just check the parameters NZBPP_NZBCOMPLETED and NZBPP_PARFAILED -# and do the processing, only if NZBPP_NZBCOMPLETED is set to "1" (which -# means, that this was the last collection in nzb-file and all files -# are now completed) and NZBPP_PARFAILED is set to "0" (no failed par-jobs); -# -# NOTE: The term "collection" in the above description actually means +# NOTE: If the option is active NZBGet calls the script +# for each collection within nzb-file. The term "collection" actually means # "par-set". To determine what "collections" are present in nzb-file NZBGet # looks for par-sets. If any collection of files within nzb-file does # not have any par-files, this collection will not be detected. # For example, for nzb-file containing three collections but only two par-sets, # the postprocess will be called two times - after processing of each par-set. -# -# NOTE: If NZBGet doesn't find any collections it calls PostProcess once -# with empty string for parameter NZBPP_PARFILENAME; -# -# NOTE: The using of special return values (91 and 92) for requesting of -# par-check/repair allows to organize the delayed parcheck. To do that: -# 1) set options: LoadPars=one, ParCheck=no, ParRepair=yes; -# 2) in post-process-script check the parameter NZBPP_PARSTATUS. If it is "0", -# that means, the script is called for the first time. Try to unpack files. -# If unpack fails, exit the script with exit code for par-check/repair; -# 3) NZBGet will start par-check/repair. After that it calls the script again; -# 4) on second pass the parameter NZBPP_PARSTATUS will have value -# greater than "0". If it is "2" ("checked and successfully repaired") -# you can try unpack again. +# If NZBGet doesn't find any collections it calls PostProcess once +# with empty string for parameter NZBPP_PARFILENAME. PostProcess= # Allow multiple post-processing for the same nzb-file (yes, no). # # NOTE: Enable this option only if you were advised to do that by the author -# of the post-process-script. +# of the post-processing script. # # NOTE: By enabling you should disable the option # to prevent multiple par-checking. # # INFO FOR DEVELOPERS: -# After the post-processing (par-check and call of a postprocess-script) is -# completed, NZBGet adds the nzb-file to a list of completed-jobs. The nzb-file -# stays in the list until the last file from that nzb-file is deleted from -# the download queue (it occurs straight away if the par-check was successful -# and the option is enabled). -# That means, if a paused file from a nzb-collection becomes unpaused -# (manually or from a post-process-script) after the collection was already -# postprocessed NZBGet will not post-process nzb-file again. -# This prevents the unwanted multiple post-processings of the same nzb-file. -# But it might be needed if the par-check/-repair are performed not directly -# by NZBGet but from a post-process-script. +# This option affects the post-processing in several ways: +# 1) If the option is active the post-processing script (option ) +# is called for each collection (par-set) within nzb-file; +# 2) The post-processing script may be called multiple times when nzb-file +# reaches the state "completely downloaded". This can be needed if +# the par-check/-repair is performed by the post-processing script +# (instead of relying on NZBGet's built-in par-check-feature). +# +# NOTE: If the built-in unpacking is active (option ) this option +# is ignored (as if it were set to "no"). +# +# NOTE: If you develop a script depending on this option you should check +# if the option is active when your script is started and generate an +# error message if the option is not set correctly. You should also check +# the option because if it's active the option +# doesn't work too. AllowReProcess=no # Pause download queue during executing of postprocess-script (yes, no). @@ -966,7 +1022,7 @@ AllowReProcess=no # Enable the option to give CPU more time for postprocess-script. That helps # to speed up postprocess on slow CPUs with fast connection (e.g. NAS-devices). # -# NOTE: See also option . +# NOTE: See also options and . PostPauseQueue=no diff --git a/webui/config.js b/webui/config.js index 5d4f4a17..4bcb05cd 100644 --- a/webui/config.js +++ b/webui/config.js @@ -1,7 +1,7 @@ /* * This file is part of nzbget * - * Copyright (C) 2012 Andrey Prygunkov + * Copyright (C) 2012-2013 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 @@ -336,6 +336,7 @@ var Options = (new function($) } return null; } + this.findOption = findOption; function mergeValues(config, values) { @@ -662,9 +663,9 @@ var Config = (new function($) value = option.defvalue; } - option.formId = section.category + '-' + option.name.replace(/[\.|$]/g, '_'); + option.formId = section.category + '-' + option.name.replace(/[\.|$|\:|\*]/g, '_'); - var caption = option.name; + var caption = option.caption ? option.caption : option.name; if (section.multi) { caption = '' + caption.substring(0, caption.indexOf('.') + 1) + '' + caption.substring(caption.indexOf('.') + 1); diff --git a/webui/downloads.js b/webui/downloads.js index bd9c5eee..88f1d987 100644 --- a/webui/downloads.js +++ b/webui/downloads.js @@ -1,7 +1,7 @@ /* * This file is part of nzbget * - * Copyright (C) 2012 Andrey Prygunkov + * Copyright (C) 2012-2013 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 @@ -273,7 +273,8 @@ var Downloads = (new function($) case 'VERIFYING_SOURCES': group.status = 'checking'; break; case 'REPAIRING': group.status = 'repairing'; break; case 'VERIFYING_REPAIRED': group.status = 'verifying'; break; - case 'EXECUTING_SCRIPT': group.status = 'unpacking'; break; + case 'UNPACKING': group.status = 'unpacking'; break; + case 'EXECUTING_SCRIPT': group.status = 'processing'; break; case 'FINISHED': group.status = 'finished'; break; default: group.status = 'error: ' + group.post.Stage; break; } @@ -633,15 +634,28 @@ var DownloadsUI = (new function($) this.buildProgressLabel = function(group) { var text = ''; - if (group.postprocess && !Status.status.PostPaused && group.post.Stage !== 'REPAIRING') + if (group.postprocess && !Status.status.PostPaused) { - if (group.post.Log && group.post.Log.length > 0) + switch (group.post.Stage) { - text = group.post.Log[group.post.Log.length-1].Text; - } - else if (group.post.ProgressLabel !== '') - { - text = group.post.ProgressLabel; + case "REPAIRING": + break; + case "LOADING_PARS": + case "VERIFYING_SOURCES": + case "VERIFYING_REPAIRED": + case "UNPACKING": + text = group.post.ProgressLabel; + break; + case "EXECUTING_SCRIPT": + if (group.post.Log && group.post.Log.length > 0) + { + text = group.post.Log[group.post.Log.length-1].Text; + } + else + { + text = group.post.ProgressLabel; + } + break; } } diff --git a/webui/edit.js b/webui/edit.js index 9a79067f..2216aaa6 100644 --- a/webui/edit.js +++ b/webui/edit.js @@ -1,7 +1,7 @@ /* * This file is part of nzbget * - * Copyright (C) 2012 Andrey Prygunkov + * Copyright (C) 2012-2013 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 @@ -149,6 +149,7 @@ var DownloadsEditDialog = (new function($) table += 'Total' + size + ''; table += 'Paused' + unpausedSize + ''; table += 'Unpaused' + remaining + ''; + //table += 'Active downloads' + group.ActiveDownloads + ''; table += 'Estimated time' + estimated + ''; table += 'Files (total/remaining/pars)' + group.FileCount + ' / ' + group.RemainingFileCount + ' / ' + group.RemainingParCount + ''; @@ -182,13 +183,16 @@ var DownloadsEditDialog = (new function($) $DownloadsLogTable.fasttable('update', []); $DownloadsFileTable.fasttable('update', []); + var postParamConfig = Options.postParamConfig; + defineBuiltinParams(postParamConfig); + Util.show('#DownloadsEdit_NZBNameReadonly', group.postprocess); Util.show('#DownloadsEdit_CancelPPGroup', group.postprocess); Util.show('#DownloadsEdit_DeleteGroup', !group.postprocess); Util.show('#DownloadsEdit_PauseGroup', !group.postprocess); Util.show('#DownloadsEdit_ResumeGroup', false); Util.show('#DownloadsEdit_Save', !group.postprocess); - var postParam = Options.postParamConfig && Options.postParamConfig.length > 0; + var postParam = postParamConfig && postParamConfig.length > 0; var postLog = group.postprocess && group.post.Log.length > 0; Util.show('#DownloadsEdit_Param', postParam); Util.show('#DownloadsEdit_Log', postLog); @@ -216,7 +220,7 @@ var DownloadsEditDialog = (new function($) if (postParam) { - postParams = $.extend(true, [], Options.postParamConfig); + postParams = $.extend(true, [], postParamConfig); Options.mergeValues(postParams, group.Parameters); var content = Config.buildOptionsContent(postParams[0]); var configData = $('#DownloadsEdit_ParamData'); @@ -427,6 +431,25 @@ var DownloadsEditDialog = (new function($) /*** TAB: POST-PROCESSING PARAMETERS **************************************************/ + function defineBuiltinParams(postParamConfig) + { + if (Options.option('Unpack') !== 'yes') + { + return; + } + + if (postParamConfig.length == 0) + { + postParamConfig.push({category: 'P', postparam: true, options: []}); + } + + if (!Options.findOption(postParamConfig[0].options, '*Unpack:')) + { + postParamConfig[0].options.unshift({name: '*Unpack:Password', value: '', defvalue: '', select: [], caption: 'Password', description: 'Unpack-password for encrypted posts.'}); + postParamConfig[0].options.unshift({name: '*Unpack:', value: '', defvalue: 'yes', select: ['yes', 'no'], caption: 'Unpack', description: 'Set to "no" to disable unpack for this nzb-file.'}); + } + } + function prepareParamRequest() { var request = []; diff --git a/webui/history.js b/webui/history.js index 2e44eb04..f72c5d23 100644 --- a/webui/history.js +++ b/webui/history.js @@ -1,7 +1,7 @@ /* * This file is part of nzbget * - * Copyright (C) 2012 Andrey Prygunkov + * Copyright (C) 2012-2013 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 @@ -113,19 +113,29 @@ var History = (new function($) { if (hist.Kind === 'NZB') { - switch (hist.ScriptStatus) + if (hist.ParStatus == 'FAILURE' || hist.UnpackStatus == 'FAILURE' || hist.ScriptStatus == 'FAILURE') { - case 'SUCCESS': hist.status = 'success'; break; - case 'FAILURE': hist.status = 'failure'; break; - case 'UNKNOWN': hist.status = 'unknown'; break; - case 'NONE': - switch (hist.ParStatus) - { - case 'SUCCESS': hist.status = 'success'; break; - case 'REPAIR_POSSIBLE': hist.status = 'repairable'; break; - case 'FAILURE': hist.status = 'failure'; break; - case 'NONE': hist.status = 'none'; break; - } + hist.status = 'failure'; + } + else + { + switch (hist.ScriptStatus) + { + case 'SUCCESS': hist.status = 'success'; break; + case 'UNKNOWN': hist.status = 'unknown'; break; + case 'NONE': + switch (hist.UnpackStatus) + { + case 'SUCCESS': hist.status = 'success'; break; + case 'NONE': + switch (hist.ParStatus) + { + case 'SUCCESS': hist.status = 'success'; break; + case 'REPAIR_POSSIBLE': hist.status = 'repairable'; break; + case 'NONE': hist.status = 'unknown'; break; + } + } + } } } else if (hist.Kind === 'URL') @@ -180,7 +190,7 @@ var History = (new function($) { var hist = item.hist; - var status = buildStatus(hist); + var status = buildStatus(hist.status, ''); var name = '' + Util.textToHtml(Util.formatNZBName(hist.Name)) + ''; var category = Util.textToHtml(hist.Category); @@ -222,16 +232,27 @@ var History = (new function($) } } - function buildStatus(hist) + function buildStatus(status, prefix) { - switch (hist.status) + switch (status) { - case 'success': return 'success'; - case 'failure': return 'failure'; - case 'unknown': return 'unknown'; - case 'repairable': return 'repairable'; - case 'none': return 'unknown'; - default: return 'internal error(' + hist.status + ')'; + case 'success': + case 'SUCCESS': + return '' + prefix + 'success'; + case 'failure': + case 'FAILURE': + return '' + prefix + 'failure'; + case 'unknown': + case 'UNKNOWN': + return '' + prefix + 'unknown'; + case 'repairable': + case 'REPAIR_POSSIBLE': + return '' + prefix + 'repairable'; + case 'none': + case 'NONE': + return '' + prefix + 'none'; + default: + return '' + prefix + status + ''; } } @@ -358,7 +379,17 @@ var History = (new function($) curHist = hist; - var status = buildStatus(hist); + var status; + if (hist.Kind === 'URL') + { + status = buildStatus(hist.status, ''); + } + else + { + status = buildStatus(hist.ParStatus, 'Par: ') + ' ' + + (Options.option('Unpack') == 'yes' || hist.UnpackStatus != 'NONE' ? buildStatus(hist.UnpackStatus, 'Unpack: ') + ' ' : '') + + buildStatus(hist.ScriptStatus, 'Script: '); + } $('#HistoryEdit_Title').text(Util.formatNZBName(hist.Name)); if (hist.Kind === 'URL') diff --git a/webui/index.html b/webui/index.html index bfb55aa9..58d51c49 100644 --- a/webui/index.html +++ b/webui/index.html @@ -732,13 +732,13 @@ - +