mirror of
https://github.com/nzbget/nzbget.git
synced 2026-01-14 17:09:09 -05:00
560 lines
14 KiB
C++
560 lines
14 KiB
C++
/*
|
|
* This file is part of nzbget
|
|
*
|
|
* Copyright (C) 2013-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* $Revision$
|
|
* $Date$
|
|
*
|
|
*/
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#ifdef WIN32
|
|
#include "win32.h"
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include <sys/stat.h>
|
|
#include <set>
|
|
|
|
#include "nzbget.h"
|
|
#include "Util.h"
|
|
#include "Options.h"
|
|
#include "Log.h"
|
|
#include "ScriptConfig.h"
|
|
|
|
static const char* BEGIN_SCRIPT_SIGNATURE = "### NZBGET ";
|
|
static const char* POST_SCRIPT_SIGNATURE = "POST-PROCESSING";
|
|
static const char* SCAN_SCRIPT_SIGNATURE = "SCAN";
|
|
static const char* QUEUE_SCRIPT_SIGNATURE = "QUEUE";
|
|
static const char* SCHEDULER_SCRIPT_SIGNATURE = "SCHEDULER";
|
|
static const char* FEED_SCRIPT_SIGNATURE = "FEED";
|
|
static const char* END_SCRIPT_SIGNATURE = " SCRIPT";
|
|
static const char* QUEUE_EVENTS_SIGNATURE = "### QUEUE EVENTS:";
|
|
|
|
ScriptConfig* g_pScriptConfig = NULL;
|
|
|
|
|
|
ScriptConfig::ConfigTemplate::ConfigTemplate(Script* pScript, const char* szTemplate)
|
|
{
|
|
m_pScript = pScript;
|
|
m_szTemplate = strdup(szTemplate ? szTemplate : "");
|
|
}
|
|
|
|
ScriptConfig::ConfigTemplate::~ConfigTemplate()
|
|
{
|
|
delete m_pScript;
|
|
free(m_szTemplate);
|
|
}
|
|
|
|
ScriptConfig::ConfigTemplates::~ConfigTemplates()
|
|
{
|
|
for (iterator it = begin(); it != end(); it++)
|
|
{
|
|
delete *it;
|
|
}
|
|
}
|
|
|
|
|
|
ScriptConfig::Script::Script(const char* szName, const char* szLocation)
|
|
{
|
|
m_szName = strdup(szName);
|
|
m_szLocation = strdup(szLocation);
|
|
m_szDisplayName = strdup(szName);
|
|
m_bPostScript = false;
|
|
m_bScanScript = false;
|
|
m_bQueueScript = false;
|
|
m_bSchedulerScript = false;
|
|
m_bFeedScript = false;
|
|
m_szQueueEvents = NULL;
|
|
}
|
|
|
|
ScriptConfig::Script::~Script()
|
|
{
|
|
free(m_szName);
|
|
free(m_szLocation);
|
|
free(m_szDisplayName);
|
|
free(m_szQueueEvents);
|
|
}
|
|
|
|
void ScriptConfig::Script::SetDisplayName(const char* szDisplayName)
|
|
{
|
|
free(m_szDisplayName);
|
|
m_szDisplayName = strdup(szDisplayName);
|
|
}
|
|
|
|
void ScriptConfig::Script::SetQueueEvents(const char* szQueueEvents)
|
|
{
|
|
free(m_szQueueEvents);
|
|
m_szQueueEvents = szQueueEvents ? strdup(szQueueEvents) : NULL;
|
|
}
|
|
|
|
|
|
ScriptConfig::Scripts::~Scripts()
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
void ScriptConfig::Scripts::Clear()
|
|
{
|
|
for (iterator it = begin(); it != end(); it++)
|
|
{
|
|
delete *it;
|
|
}
|
|
clear();
|
|
}
|
|
|
|
ScriptConfig::Script* ScriptConfig::Scripts::Find(const char* szName)
|
|
{
|
|
for (iterator it = begin(); it != end(); it++)
|
|
{
|
|
Script* pScript = *it;
|
|
if (!strcmp(pScript->GetName(), szName))
|
|
{
|
|
return pScript;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
ScriptConfig::ScriptConfig()
|
|
{
|
|
InitScripts();
|
|
InitConfigTemplates();
|
|
}
|
|
|
|
ScriptConfig::~ScriptConfig()
|
|
{
|
|
}
|
|
|
|
bool ScriptConfig::LoadConfig(Options::OptEntries* pOptEntries)
|
|
{
|
|
// read config file
|
|
FILE* infile = fopen(g_pOptions->GetConfigFilename(), FOPEN_RB);
|
|
|
|
if (!infile)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int iBufLen = (int)Util::FileSize(g_pOptions->GetConfigFilename()) + 1;
|
|
char* buf = (char*)malloc(iBufLen);
|
|
|
|
while (fgets(buf, iBufLen - 1, infile))
|
|
{
|
|
// remove trailing '\n' and '\r' and spaces
|
|
Util::TrimRight(buf);
|
|
|
|
// skip comments and empty lines
|
|
if (buf[0] == 0 || buf[0] == '#' || strspn(buf, " ") == strlen(buf))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
char* optname;
|
|
char* optvalue;
|
|
if (g_pOptions->SplitOptionString(buf, &optname, &optvalue))
|
|
{
|
|
Options::OptEntry* pOptEntry = new Options::OptEntry();
|
|
pOptEntry->SetName(optname);
|
|
pOptEntry->SetValue(optvalue);
|
|
pOptEntries->push_back(pOptEntry);
|
|
|
|
free(optname);
|
|
free(optvalue);
|
|
}
|
|
}
|
|
|
|
fclose(infile);
|
|
free(buf);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ScriptConfig::SaveConfig(Options::OptEntries* pOptEntries)
|
|
{
|
|
// save to config file
|
|
FILE* infile = fopen(g_pOptions->GetConfigFilename(), FOPEN_RBP);
|
|
|
|
if (!infile)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
std::vector<char*> config;
|
|
std::set<Options::OptEntry*> writtenOptions;
|
|
|
|
// read config file into memory array
|
|
int iBufLen = (int)Util::FileSize(g_pOptions->GetConfigFilename()) + 1;
|
|
char* buf = (char*)malloc(iBufLen);
|
|
while (fgets(buf, iBufLen - 1, infile))
|
|
{
|
|
config.push_back(strdup(buf));
|
|
}
|
|
free(buf);
|
|
|
|
// write config file back to disk, replace old values of existing options with new values
|
|
rewind(infile);
|
|
for (std::vector<char*>::iterator it = config.begin(); it != config.end(); it++)
|
|
{
|
|
char* buf = *it;
|
|
|
|
const char* eq = strchr(buf, '=');
|
|
if (eq && buf[0] != '#')
|
|
{
|
|
// remove trailing '\n' and '\r' and spaces
|
|
Util::TrimRight(buf);
|
|
|
|
char* optname;
|
|
char* optvalue;
|
|
if (g_pOptions->SplitOptionString(buf, &optname, &optvalue))
|
|
{
|
|
Options::OptEntry *pOptEntry = pOptEntries->FindOption(optname);
|
|
if (pOptEntry)
|
|
{
|
|
fputs(pOptEntry->GetName(), infile);
|
|
fputs("=", infile);
|
|
fputs(pOptEntry->GetValue(), infile);
|
|
fputs("\n", infile);
|
|
writtenOptions.insert(pOptEntry);
|
|
}
|
|
|
|
free(optname);
|
|
free(optvalue);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fputs(buf, infile);
|
|
}
|
|
|
|
free(buf);
|
|
}
|
|
|
|
// write new options
|
|
for (Options::OptEntries::iterator it = pOptEntries->begin(); it != pOptEntries->end(); it++)
|
|
{
|
|
Options::OptEntry* pOptEntry = *it;
|
|
std::set<Options::OptEntry*>::iterator fit = writtenOptions.find(pOptEntry);
|
|
if (fit == writtenOptions.end())
|
|
{
|
|
fputs(pOptEntry->GetName(), infile);
|
|
fputs("=", infile);
|
|
fputs(pOptEntry->GetValue(), infile);
|
|
fputs("\n", infile);
|
|
}
|
|
}
|
|
|
|
// close and truncate the file
|
|
int pos = (int)ftell(infile);
|
|
fclose(infile);
|
|
|
|
Util::TruncateFile(g_pOptions->GetConfigFilename(), pos);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ScriptConfig::LoadConfigTemplates(ConfigTemplates* pConfigTemplates)
|
|
{
|
|
char* szBuffer;
|
|
int iLength;
|
|
if (!Util::LoadFileIntoBuffer(g_pOptions->GetConfigTemplate(), &szBuffer, &iLength))
|
|
{
|
|
return false;
|
|
}
|
|
ConfigTemplate* pConfigTemplate = new ConfigTemplate(NULL, szBuffer);
|
|
pConfigTemplates->push_back(pConfigTemplate);
|
|
free(szBuffer);
|
|
|
|
if (!g_pOptions->GetScriptDir())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
Scripts scriptList;
|
|
LoadScripts(&scriptList);
|
|
|
|
const int iBeginSignatureLen = strlen(BEGIN_SCRIPT_SIGNATURE);
|
|
const int iQueueEventsSignatureLen = strlen(QUEUE_EVENTS_SIGNATURE);
|
|
|
|
for (Scripts::iterator it = scriptList.begin(); it != scriptList.end(); it++)
|
|
{
|
|
Script* pScript = *it;
|
|
|
|
FILE* infile = fopen(pScript->GetLocation(), FOPEN_RB);
|
|
if (!infile)
|
|
{
|
|
ConfigTemplate* pConfigTemplate = new ConfigTemplate(pScript, "");
|
|
pConfigTemplates->push_back(pConfigTemplate);
|
|
continue;
|
|
}
|
|
|
|
StringBuilder stringBuilder;
|
|
char buf[1024];
|
|
bool bInConfig = false;
|
|
|
|
while (fgets(buf, sizeof(buf) - 1, infile))
|
|
{
|
|
if (!strncmp(buf, BEGIN_SCRIPT_SIGNATURE, iBeginSignatureLen) &&
|
|
strstr(buf, END_SCRIPT_SIGNATURE) &&
|
|
(strstr(buf, POST_SCRIPT_SIGNATURE) ||
|
|
strstr(buf, SCAN_SCRIPT_SIGNATURE) ||
|
|
strstr(buf, QUEUE_SCRIPT_SIGNATURE) ||
|
|
strstr(buf, SCHEDULER_SCRIPT_SIGNATURE) ||
|
|
strstr(buf, FEED_SCRIPT_SIGNATURE)))
|
|
{
|
|
if (bInConfig)
|
|
{
|
|
break;
|
|
}
|
|
bInConfig = true;
|
|
continue;
|
|
}
|
|
|
|
bool bSkip = !strncmp(buf, QUEUE_EVENTS_SIGNATURE, iQueueEventsSignatureLen);
|
|
|
|
if (bInConfig && !bSkip)
|
|
{
|
|
stringBuilder.Append(buf);
|
|
}
|
|
}
|
|
|
|
fclose(infile);
|
|
|
|
ConfigTemplate* pConfigTemplate = new ConfigTemplate(pScript, stringBuilder.GetBuffer());
|
|
pConfigTemplates->push_back(pConfigTemplate);
|
|
}
|
|
|
|
// clearing the list without deleting of objects, which are in pConfigTemplates now
|
|
scriptList.clear();
|
|
|
|
return true;
|
|
}
|
|
|
|
void ScriptConfig::InitConfigTemplates()
|
|
{
|
|
if (!LoadConfigTemplates(&m_ConfigTemplates))
|
|
{
|
|
error("Could not read configuration templates");
|
|
}
|
|
}
|
|
|
|
void ScriptConfig::InitScripts()
|
|
{
|
|
LoadScripts(&m_Scripts);
|
|
}
|
|
|
|
void ScriptConfig::LoadScripts(Scripts* pScripts)
|
|
{
|
|
if (strlen(g_pOptions->GetScriptDir()) == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Scripts tmpScripts;
|
|
LoadScriptDir(&tmpScripts, g_pOptions->GetScriptDir(), false);
|
|
tmpScripts.sort(CompareScripts);
|
|
|
|
// first add all scripts from m_szScriptOrder
|
|
Tokenizer tok(g_pOptions->GetScriptOrder(), ",;");
|
|
while (const char* szScriptName = tok.Next())
|
|
{
|
|
Script* pScript = tmpScripts.Find(szScriptName);
|
|
if (pScript)
|
|
{
|
|
tmpScripts.remove(pScript);
|
|
pScripts->push_back(pScript);
|
|
}
|
|
}
|
|
|
|
// second add all other scripts from scripts directory
|
|
for (Scripts::iterator it = tmpScripts.begin(); it != tmpScripts.end(); it++)
|
|
{
|
|
Script* pScript = *it;
|
|
if (!pScripts->Find(pScript->GetName()))
|
|
{
|
|
pScripts->push_back(pScript);
|
|
}
|
|
}
|
|
|
|
tmpScripts.clear();
|
|
|
|
BuildScriptDisplayNames(pScripts);
|
|
}
|
|
|
|
void ScriptConfig::LoadScriptDir(Scripts* pScripts, const char* szDirectory, bool bIsSubDir)
|
|
{
|
|
int iBufSize = 1024*10;
|
|
char* szBuffer = (char*)malloc(iBufSize+1);
|
|
|
|
const int iBeginSignatureLen = strlen(BEGIN_SCRIPT_SIGNATURE);
|
|
const int iQueueEventsSignatureLen = strlen(QUEUE_EVENTS_SIGNATURE);
|
|
|
|
DirBrowser dir(szDirectory);
|
|
while (const char* szFilename = dir.Next())
|
|
{
|
|
if (szFilename[0] != '.' && szFilename[0] != '_')
|
|
{
|
|
char szFullFilename[1024];
|
|
snprintf(szFullFilename, 1024, "%s%s", szDirectory, szFilename);
|
|
szFullFilename[1024-1] = '\0';
|
|
|
|
if (!Util::DirectoryExists(szFullFilename))
|
|
{
|
|
// check if the file contains pp-script-signature
|
|
FILE* infile = fopen(szFullFilename, FOPEN_RB);
|
|
if (infile)
|
|
{
|
|
// read first 10KB of the file and look for signature
|
|
int iReadBytes = fread(szBuffer, 1, iBufSize, infile);
|
|
fclose(infile);
|
|
szBuffer[iReadBytes] = 0;
|
|
|
|
// split buffer into lines
|
|
Tokenizer tok(szBuffer, "\n\r", true);
|
|
while (char* szLine = tok.Next())
|
|
{
|
|
if (!strncmp(szLine, BEGIN_SCRIPT_SIGNATURE, iBeginSignatureLen) &&
|
|
strstr(szLine, END_SCRIPT_SIGNATURE))
|
|
{
|
|
bool bPostScript = strstr(szLine, POST_SCRIPT_SIGNATURE);
|
|
bool bScanScript = strstr(szLine, SCAN_SCRIPT_SIGNATURE);
|
|
bool bQueueScript = strstr(szLine, QUEUE_SCRIPT_SIGNATURE);
|
|
bool bSchedulerScript = strstr(szLine, SCHEDULER_SCRIPT_SIGNATURE);
|
|
bool bFeedScript = strstr(szLine, FEED_SCRIPT_SIGNATURE);
|
|
if (bPostScript || bScanScript || bQueueScript || bSchedulerScript || bFeedScript)
|
|
{
|
|
char szScriptName[1024];
|
|
if (bIsSubDir)
|
|
{
|
|
char szDirectory2[1024];
|
|
snprintf(szDirectory2, 1024, "%s", szDirectory);
|
|
szDirectory2[1024-1] = '\0';
|
|
int iLen = strlen(szDirectory2);
|
|
if (szDirectory2[iLen-1] == PATH_SEPARATOR || szDirectory2[iLen-1] == ALT_PATH_SEPARATOR)
|
|
{
|
|
// trim last path-separator
|
|
szDirectory2[iLen-1] = '\0';
|
|
}
|
|
|
|
snprintf(szScriptName, 1024, "%s%c%s", Util::BaseFileName(szDirectory2), PATH_SEPARATOR, szFilename);
|
|
}
|
|
else
|
|
{
|
|
snprintf(szScriptName, 1024, "%s", szFilename);
|
|
}
|
|
szScriptName[1024-1] = '\0';
|
|
|
|
char* szQueueEvents = NULL;
|
|
if (bQueueScript)
|
|
{
|
|
while (char* szLine = tok.Next())
|
|
{
|
|
if (!strncmp(szLine, QUEUE_EVENTS_SIGNATURE, iQueueEventsSignatureLen))
|
|
{
|
|
szQueueEvents = szLine + iQueueEventsSignatureLen;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Script* pScript = new Script(szScriptName, szFullFilename);
|
|
pScript->SetPostScript(bPostScript);
|
|
pScript->SetScanScript(bScanScript);
|
|
pScript->SetQueueScript(bQueueScript);
|
|
pScript->SetSchedulerScript(bSchedulerScript);
|
|
pScript->SetFeedScript(bFeedScript);
|
|
pScript->SetQueueEvents(szQueueEvents);
|
|
pScripts->push_back(pScript);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (!bIsSubDir)
|
|
{
|
|
snprintf(szFullFilename, 1024, "%s%s%c", szDirectory, szFilename, PATH_SEPARATOR);
|
|
szFullFilename[1024-1] = '\0';
|
|
|
|
LoadScriptDir(pScripts, szFullFilename, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
free(szBuffer);
|
|
}
|
|
|
|
bool ScriptConfig::CompareScripts(Script* pScript1, Script* pScript2)
|
|
{
|
|
return strcmp(pScript1->GetName(), pScript2->GetName()) < 0;
|
|
}
|
|
|
|
void ScriptConfig::BuildScriptDisplayNames(Scripts* pScripts)
|
|
{
|
|
// trying to use short name without path and extension.
|
|
// if there are other scripts with the same short name - using a longer name instead (with ot without extension)
|
|
|
|
for (Scripts::iterator it = pScripts->begin(); it != pScripts->end(); it++)
|
|
{
|
|
Script* pScript = *it;
|
|
|
|
char szShortName[256];
|
|
strncpy(szShortName, pScript->GetName(), 256);
|
|
szShortName[256-1] = '\0';
|
|
if (char* ext = strrchr(szShortName, '.')) *ext = '\0'; // strip file extension
|
|
|
|
const char* szDisplayName = Util::BaseFileName(szShortName);
|
|
|
|
for (Scripts::iterator it2 = pScripts->begin(); it2 != pScripts->end(); it2++)
|
|
{
|
|
Script* pScript2 = *it2;
|
|
|
|
char szShortName2[256];
|
|
strncpy(szShortName2, pScript2->GetName(), 256);
|
|
szShortName2[256-1] = '\0';
|
|
if (char* ext = strrchr(szShortName2, '.')) *ext = '\0'; // strip file extension
|
|
|
|
const char* szDisplayName2 = Util::BaseFileName(szShortName2);
|
|
|
|
if (!strcmp(szDisplayName, szDisplayName2) && pScript->GetName() != pScript2->GetName())
|
|
{
|
|
if (!strcmp(szShortName, szShortName2))
|
|
{
|
|
szDisplayName = pScript->GetName();
|
|
}
|
|
else
|
|
{
|
|
szDisplayName = szShortName;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
pScript->SetDisplayName(szDisplayName);
|
|
}
|
|
}
|