Files
nzbget/daemon/extension/ScriptConfig.cpp
Andrey Prygunkov f3f7fbd0de #176: updated copyright notice in source files
- added link to http://nzbget.net;
- replaced FSF Post address with a web link;
- removed unusable subversion-tags;
- updated year.
2016-03-01 19:45:07 +01:00

411 lines
10 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:";
ScriptConfig::Script::Script(const char* name, const char* location)
{
m_name = name;
m_location = location;
m_displayName = name;
m_postScript = false;
m_scanScript = false;
m_queueScript = false;
m_schedulerScript = false;
m_feedScript = false;
}
void ScriptConfig::InitOptions()
{
InitScripts();
InitConfigTemplates();
}
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 (g_Options->SplitOptionString(buf, optname, optvalue))
{
optEntries->emplace_back(optname, optvalue);
}
}
infile.Close();
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 queueEventsSignatureLen = strlen(QUEUE_EVENTS_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;
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;
continue;
}
bool skip = !strncmp(buf, QUEUE_EVENTS_SIGNATURE, queueEventsSignatureLen);
if (inConfig && !skip)
{
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 (strlen(g_Options->GetScriptDir()) == 0)
{
return;
}
Scripts tmpScripts;
LoadScriptDir(&tmpScripts, g_Options->GetScriptDir(), false);
tmpScripts.sort(
[](Script& script1, Script& script2)
{
return strcmp(script1.GetName(), script2.GetName()) < 0;
});
// first add all scripts from ScriptOrder
Tokenizer tok(g_Options->GetScriptOrder(), ",;");
while (const char* scriptName = tok.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)
{
CharBuffer buffer(1024*10 + 1);
const int beginSignatureLen = strlen(BEGIN_SCRIPT_SIGNATURE);
const int queueEventsSignatureLen = strlen(QUEUE_EVENTS_SIGNATURE);
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))
{
// check if the file contains pp-script-signature
DiskFile infile;
if (infile.Open(fullFilename, DiskFile::omRead))
{
// read first 10KB of the file and look for signature
int readBytes = (int)infile.Read(buffer, buffer.Size() - 1);
infile.Close();
buffer[readBytes] = '\0';
// split buffer into lines
Tokenizer tok(buffer, "\n\r", true);
while (char* line = tok.Next())
{
if (!strncmp(line, BEGIN_SCRIPT_SIGNATURE, beginSignatureLen) &&
strstr(line, END_SCRIPT_SIGNATURE))
{
bool postScript = strstr(line, POST_SCRIPT_SIGNATURE);
bool scanScript = strstr(line, SCAN_SCRIPT_SIGNATURE);
bool queueScript = strstr(line, QUEUE_SCRIPT_SIGNATURE);
bool schedulerScript = strstr(line, SCHEDULER_SCRIPT_SIGNATURE);
bool feedScript = strstr(line, FEED_SCRIPT_SIGNATURE);
if (postScript || scanScript || queueScript || schedulerScript || feedScript)
{
BString<1024> scriptName;
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';
}
scriptName.Format("%s%c%s", FileSystem::BaseFileName(directory2), PATH_SEPARATOR, filename);
}
else
{
scriptName = filename;
}
char* queueEvents = nullptr;
if (queueScript)
{
while (char* line = tok.Next())
{
if (!strncmp(line, QUEUE_EVENTS_SIGNATURE, queueEventsSignatureLen))
{
queueEvents = line + queueEventsSignatureLen;
break;
}
}
}
scripts->emplace_back(scriptName, fullFilename);
Script& script = scripts->back();
script.SetPostScript(postScript);
script.SetScanScript(scanScript);
script.SetQueueScript(queueScript);
script.SetSchedulerScript(schedulerScript);
script.SetFeedScript(feedScript);
script.SetQueueEvents(queueEvents);
break;
}
}
}
}
}
else if (!isSubDir)
{
LoadScriptDir(scripts, fullFilename, true);
}
}
}
}
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);
}
}