mirror of
https://github.com/nzbget/nzbget.git
synced 2025-12-24 06:37:44 -05:00
Scheduler scripts can now be selected in option “Extensions” if the scripts provide default time definition.
501 lines
12 KiB
C++
501 lines
12 KiB
C++
/*
|
|
* This file is part of nzbget. See <http://nzbget.net>.
|
|
*
|
|
* Copyright (C) 2013-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
#include "nzbget.h"
|
|
#include "Util.h"
|
|
#include "FileSystem.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:";
|
|
static const char* TASK_TIME_SIGNATURE = "### TASK TIME:";
|
|
static const char* DEFINITION_SIGNATURE = "###";
|
|
|
|
void ScriptConfig::InitOptions()
|
|
{
|
|
InitScripts();
|
|
InitConfigTemplates();
|
|
CreateTasks();
|
|
}
|
|
|
|
bool ScriptConfig::LoadConfig(Options::OptEntries* optEntries)
|
|
{
|
|
// read config file
|
|
DiskFile infile;
|
|
|
|
if (!infile.Open(g_Options->GetConfigFilename(), DiskFile::omRead))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int fileLen = (int)FileSystem::FileSize(g_Options->GetConfigFilename());
|
|
CString buf;
|
|
buf.Reserve(fileLen);
|
|
|
|
while (infile.ReadLine(buf, fileLen + 1))
|
|
{
|
|
// 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;
|
|
}
|
|
|
|
CString optname;
|
|
CString optvalue;
|
|
if (Options::SplitOptionString(buf, optname, optvalue))
|
|
{
|
|
optEntries->emplace_back(optname, optvalue);
|
|
}
|
|
}
|
|
|
|
infile.Close();
|
|
|
|
Options::ConvertOldOptions(optEntries);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ScriptConfig::SaveConfig(Options::OptEntries* optEntries)
|
|
{
|
|
// save to config file
|
|
DiskFile infile;
|
|
|
|
if (!infile.Open(g_Options->GetConfigFilename(), DiskFile::omReadWrite))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
std::vector<CString> config;
|
|
std::set<Options::OptEntry*> writtenOptions;
|
|
|
|
// read config file into memory array
|
|
int fileLen = (int)FileSystem::FileSize(g_Options->GetConfigFilename()) + 1;
|
|
CString content;
|
|
content.Reserve(fileLen);
|
|
while (infile.ReadLine(content, fileLen + 1))
|
|
{
|
|
config.push_back(*content);
|
|
}
|
|
content.Clear();
|
|
|
|
// write config file back to disk, replace old values of existing options with new values
|
|
infile.Seek(0);
|
|
for (CString& buf : config)
|
|
{
|
|
const char* eq = strchr(buf, '=');
|
|
if (eq && buf[0] != '#')
|
|
{
|
|
// remove trailing '\n' and '\r' and spaces
|
|
buf.TrimRight();
|
|
|
|
CString optname;
|
|
CString optvalue;
|
|
if (g_Options->SplitOptionString(buf, optname, optvalue))
|
|
{
|
|
Options::OptEntry* optEntry = optEntries->FindOption(optname);
|
|
if (optEntry)
|
|
{
|
|
infile.Print("%s=%s\n", optEntry->GetName(), optEntry->GetValue());
|
|
writtenOptions.insert(optEntry);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
infile.Print("%s", *buf);
|
|
}
|
|
}
|
|
|
|
// write new options
|
|
for (Options::OptEntry& optEntry : *optEntries)
|
|
{
|
|
std::set<Options::OptEntry*>::iterator fit = writtenOptions.find(&optEntry);
|
|
if (fit == writtenOptions.end())
|
|
{
|
|
infile.Print("%s=%s\n", optEntry.GetName(), optEntry.GetValue());
|
|
}
|
|
}
|
|
|
|
// close and truncate the file
|
|
int pos = (int)infile.Position();
|
|
infile.Close();
|
|
|
|
FileSystem::TruncateFile(g_Options->GetConfigFilename(), pos);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ScriptConfig::LoadConfigTemplates(ConfigTemplates* configTemplates)
|
|
{
|
|
CharBuffer buffer;
|
|
if (!FileSystem::LoadFileIntoBuffer(g_Options->GetConfigTemplate(), buffer, true))
|
|
{
|
|
return false;
|
|
}
|
|
configTemplates->emplace_back(Script("", ""), buffer);
|
|
|
|
if (!g_Options->GetScriptDir())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
Scripts scriptList;
|
|
LoadScripts(&scriptList);
|
|
|
|
const int beginSignatureLen = strlen(BEGIN_SCRIPT_SIGNATURE);
|
|
const int definitionSignatureLen = strlen(DEFINITION_SIGNATURE);
|
|
|
|
for (Script& script : scriptList)
|
|
{
|
|
DiskFile infile;
|
|
if (!infile.Open(script.GetLocation(), DiskFile::omRead))
|
|
{
|
|
configTemplates->emplace_back(std::move(script), "");
|
|
continue;
|
|
}
|
|
|
|
StringBuilder templ;
|
|
char buf[1024];
|
|
bool inConfig = false;
|
|
bool inHeader = false;
|
|
|
|
while (infile.ReadLine(buf, sizeof(buf) - 1))
|
|
{
|
|
if (!strncmp(buf, BEGIN_SCRIPT_SIGNATURE, beginSignatureLen) &&
|
|
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 (inConfig)
|
|
{
|
|
break;
|
|
}
|
|
inConfig = true;
|
|
inHeader = true;
|
|
continue;
|
|
}
|
|
|
|
inHeader &= !strncmp(buf, DEFINITION_SIGNATURE, definitionSignatureLen);
|
|
|
|
if (inConfig && !inHeader)
|
|
{
|
|
templ.Append(buf);
|
|
}
|
|
}
|
|
|
|
infile.Close();
|
|
|
|
configTemplates->emplace_back(std::move(script), templ);
|
|
}
|
|
|
|
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* scripts)
|
|
{
|
|
if (Util::EmptyStr(g_Options->GetScriptDir()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Scripts tmpScripts;
|
|
|
|
Tokenizer tokDir(g_Options->GetScriptDir(), ",;");
|
|
while (const char* scriptDir = tokDir.Next())
|
|
{
|
|
LoadScriptDir(&tmpScripts, scriptDir, false);
|
|
}
|
|
|
|
tmpScripts.sort(
|
|
[](Script& script1, Script& script2)
|
|
{
|
|
return strcmp(script1.GetName(), script2.GetName()) < 0;
|
|
});
|
|
|
|
// first add all scripts from ScriptOrder
|
|
Tokenizer tokOrder(g_Options->GetScriptOrder(), ",;");
|
|
while (const char* scriptName = tokOrder.Next())
|
|
{
|
|
Scripts::iterator pos = std::find_if(tmpScripts.begin(), tmpScripts.end(),
|
|
[scriptName](Script& script)
|
|
{
|
|
return !strcmp(script.GetName(), scriptName);
|
|
});
|
|
|
|
if (pos != tmpScripts.end())
|
|
{
|
|
scripts->splice(scripts->end(), tmpScripts, pos);
|
|
}
|
|
}
|
|
|
|
// then add all other scripts from scripts directory
|
|
scripts->splice(scripts->end(), std::move(tmpScripts));
|
|
|
|
BuildScriptDisplayNames(scripts);
|
|
}
|
|
|
|
void ScriptConfig::LoadScriptDir(Scripts* scripts, const char* directory, bool isSubDir)
|
|
{
|
|
DirBrowser dir(directory);
|
|
while (const char* filename = dir.Next())
|
|
{
|
|
if (filename[0] != '.' && filename[0] != '_')
|
|
{
|
|
BString<1024> fullFilename("%s%c%s", directory, PATH_SEPARATOR, filename);
|
|
|
|
if (!FileSystem::DirectoryExists(fullFilename))
|
|
{
|
|
BString<1024> scriptName = BuildScriptName(directory, filename, isSubDir);
|
|
if (ScriptExists(scripts, scriptName))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Script script(scriptName, fullFilename);
|
|
if (LoadScriptFile(&script))
|
|
{
|
|
scripts->push_back(std::move(script));
|
|
}
|
|
}
|
|
else if (!isSubDir)
|
|
{
|
|
LoadScriptDir(scripts, fullFilename, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ScriptConfig::LoadScriptFile(Script* script)
|
|
{
|
|
DiskFile infile;
|
|
if (!infile.Open(script->GetLocation(), DiskFile::omRead))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
CharBuffer buffer(1024 * 10 + 1);
|
|
|
|
const int beginSignatureLen = strlen(BEGIN_SCRIPT_SIGNATURE);
|
|
const int queueEventsSignatureLen = strlen(QUEUE_EVENTS_SIGNATURE);
|
|
const int taskTimeSignatureLen = strlen(TASK_TIME_SIGNATURE);
|
|
const int definitionSignatureLen = strlen(DEFINITION_SIGNATURE);
|
|
|
|
// check if the file contains pp-script-signature
|
|
// read first 10KB of the file and look for signature
|
|
int readBytes = (int)infile.Read(buffer, buffer.Size() - 1);
|
|
infile.Close();
|
|
buffer[readBytes] = '\0';
|
|
|
|
bool postScript = false;
|
|
bool scanScript = false;
|
|
bool queueScript = false;
|
|
bool schedulerScript = false;
|
|
bool feedScript = false;
|
|
char* queueEvents = nullptr;
|
|
char* taskTime = nullptr;
|
|
|
|
bool inConfig = false;
|
|
bool afterConfig = false;
|
|
|
|
// Declarations "QUEUE EVENT:" and "TASK TIME:" can be placed:
|
|
// - in script definition body (between opening and closing script signatures);
|
|
// - immediately before script definition (before opening script signature);
|
|
// - immediately after script definition (after closing script signature).
|
|
// The last two pissibilities are provided to increase compatibility of scripts with older
|
|
// nzbget versions which do not expect the extra declarations in the script defintion body.
|
|
|
|
Tokenizer tok(buffer, "\n\r", true);
|
|
while (char* line = tok.Next())
|
|
{
|
|
if (!strncmp(line, QUEUE_EVENTS_SIGNATURE, queueEventsSignatureLen))
|
|
{
|
|
queueEvents = line + queueEventsSignatureLen;
|
|
}
|
|
else if (!strncmp(line, TASK_TIME_SIGNATURE, taskTimeSignatureLen))
|
|
{
|
|
taskTime = line + taskTimeSignatureLen;
|
|
}
|
|
|
|
bool header = !strncmp(line, DEFINITION_SIGNATURE, definitionSignatureLen);
|
|
if (!header && !inConfig)
|
|
{
|
|
queueEvents = nullptr;
|
|
taskTime = nullptr;
|
|
}
|
|
|
|
if (!header && afterConfig)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (!strncmp(line, BEGIN_SCRIPT_SIGNATURE, beginSignatureLen) && strstr(line, END_SCRIPT_SIGNATURE))
|
|
{
|
|
if (!inConfig)
|
|
{
|
|
inConfig = true;
|
|
postScript = strstr(line, POST_SCRIPT_SIGNATURE);
|
|
scanScript = strstr(line, SCAN_SCRIPT_SIGNATURE);
|
|
queueScript = strstr(line, QUEUE_SCRIPT_SIGNATURE);
|
|
schedulerScript = strstr(line, SCHEDULER_SCRIPT_SIGNATURE);
|
|
feedScript = strstr(line, FEED_SCRIPT_SIGNATURE);
|
|
}
|
|
else
|
|
{
|
|
afterConfig = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!(postScript || scanScript || queueScript || schedulerScript || feedScript))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// trim decorations
|
|
char* p;
|
|
while (queueEvents && *queueEvents && *(p = queueEvents + strlen(queueEvents) - 1) == '#') *p = '\0';
|
|
if (queueEvents) queueEvents = Util::Trim(queueEvents);
|
|
while (taskTime && *taskTime && *(p = taskTime + strlen(taskTime) - 1) == '#') *p = '\0';
|
|
if (taskTime) taskTime = Util::Trim(taskTime);
|
|
|
|
script->SetPostScript(postScript);
|
|
script->SetScanScript(scanScript);
|
|
script->SetQueueScript(queueScript);
|
|
script->SetSchedulerScript(schedulerScript);
|
|
script->SetFeedScript(feedScript);
|
|
script->SetQueueEvents(queueEvents);
|
|
script->SetTaskTime(taskTime);
|
|
|
|
return true;
|
|
}
|
|
|
|
BString<1024> ScriptConfig::BuildScriptName(const char* directory, const char* filename, bool isSubDir)
|
|
{
|
|
if (isSubDir)
|
|
{
|
|
BString<1024> directory2 = directory;
|
|
int len = strlen(directory2);
|
|
if (directory2[len-1] == PATH_SEPARATOR || directory2[len-1] == ALT_PATH_SEPARATOR)
|
|
{
|
|
// trim last path-separator
|
|
directory2[len-1] = '\0';
|
|
}
|
|
|
|
return BString<1024>("%s%c%s", FileSystem::BaseFileName(directory2), PATH_SEPARATOR, filename);
|
|
}
|
|
else
|
|
{
|
|
return filename;
|
|
}
|
|
}
|
|
|
|
bool ScriptConfig::ScriptExists(Scripts* scripts, const char* scriptName)
|
|
{
|
|
return std::find_if(scripts->begin(), scripts->end(),
|
|
[scriptName](Script& script)
|
|
{
|
|
return !strcmp(script.GetName(), scriptName);
|
|
}) != scripts->end();
|
|
}
|
|
|
|
void ScriptConfig::BuildScriptDisplayNames(Scripts* scripts)
|
|
{
|
|
// 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 (Script& script : scripts)
|
|
{
|
|
BString<1024> shortName = script.GetName();
|
|
if (char* ext = strrchr(shortName, '.')) *ext = '\0'; // strip file extension
|
|
|
|
const char* displayName = FileSystem::BaseFileName(shortName);
|
|
|
|
for (Script& script2 : scripts)
|
|
{
|
|
BString<1024> shortName2 = script2.GetName();
|
|
if (char* ext = strrchr(shortName2, '.')) *ext = '\0'; // strip file extension
|
|
|
|
const char* displayName2 = FileSystem::BaseFileName(shortName2);
|
|
|
|
if (!strcmp(displayName, displayName2) && script.GetName() != script2.GetName())
|
|
{
|
|
if (!strcmp(shortName, shortName2))
|
|
{
|
|
displayName = script.GetName();
|
|
}
|
|
else
|
|
{
|
|
displayName = shortName;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
script.SetDisplayName(displayName);
|
|
}
|
|
}
|
|
|
|
void ScriptConfig::CreateTasks()
|
|
{
|
|
for (Script& script : m_scripts)
|
|
{
|
|
if (script.GetSchedulerScript() && !Util::EmptyStr(script.GetTaskTime()))
|
|
{
|
|
Tokenizer tok(g_Options->GetExtensions(), ",;");
|
|
while (const char* scriptName = tok.Next())
|
|
{
|
|
if (FileSystem::SameFilename(scriptName, script.GetName()))
|
|
{
|
|
g_Options->CreateSchedulerTask(0, script.GetTaskTime(),
|
|
nullptr, Options::scScript, script.GetName());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|