mirror of
https://github.com/nzbget/nzbget.git
synced 2025-12-27 16:17:44 -05:00
from Unit “Options.cpp”. The latter now contains only program options (which cannot be changed without reload).
346 lines
8.5 KiB
C++
346 lines
8.5 KiB
C++
/*
|
|
* This file is part of nzbget. See <http://nzbget.net>.
|
|
*
|
|
* Copyright (C) 2008-2019 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 "Scheduler.h"
|
|
#include "Options.h"
|
|
#include "WorkState.h"
|
|
#include "Log.h"
|
|
#include "NewsServer.h"
|
|
#include "ServerPool.h"
|
|
#include "FeedInfo.h"
|
|
#include "FeedCoordinator.h"
|
|
#include "SchedulerScript.h"
|
|
|
|
void Scheduler::AddTask(std::unique_ptr<Task> task)
|
|
{
|
|
Guard guard(m_taskListMutex);
|
|
m_taskList.push_back(std::move(task));
|
|
}
|
|
|
|
void Scheduler::FirstCheck()
|
|
{
|
|
{
|
|
Guard guard(m_taskListMutex);
|
|
|
|
std::sort(m_taskList.begin(), m_taskList.end(),
|
|
[](const std::unique_ptr<Task>& task1, const std::unique_ptr<Task>& task2)
|
|
{
|
|
return (task1->m_hours < task2->m_hours) ||
|
|
((task1->m_hours == task2->m_hours) && (task1->m_minutes < task2->m_minutes));
|
|
});
|
|
}
|
|
|
|
// check all tasks for the last week
|
|
CheckTasks();
|
|
}
|
|
|
|
void Scheduler::ScheduleNextWork()
|
|
{
|
|
// Ideally we should calculate wait time until next scheduler task or until resume time.
|
|
// The first isn't trivial and the second requires watching/reaction on changed scheduled resume time.
|
|
// We do it simpler instead: check once per minute, when seconds are changing from 59 to 00.
|
|
|
|
time_t curTime = Util::CurrentTime();
|
|
tm sched;
|
|
gmtime_r(&curTime, &sched);
|
|
sched.tm_min++;
|
|
sched.tm_sec = 0;
|
|
time_t nextMinute = Util::Timegm(&sched);
|
|
|
|
m_serviceInterval = nextMinute - curTime;
|
|
}
|
|
|
|
void Scheduler::ServiceWork()
|
|
{
|
|
debug("Scheduler service work");
|
|
|
|
if (!DownloadQueue::IsLoaded())
|
|
{
|
|
return;
|
|
}
|
|
|
|
debug("Scheduler service work: doing work");
|
|
|
|
if (!m_firstChecked)
|
|
{
|
|
FirstCheck();
|
|
m_firstChecked = true;
|
|
return;
|
|
}
|
|
|
|
m_executeProcess = true;
|
|
CheckTasks();
|
|
CheckScheduledResume();
|
|
ScheduleNextWork();
|
|
}
|
|
|
|
void Scheduler::CheckTasks()
|
|
{
|
|
PrepareLog();
|
|
|
|
{
|
|
Guard guard(m_taskListMutex);
|
|
|
|
time_t current = Util::CurrentTime();
|
|
|
|
if (!m_taskList.empty())
|
|
{
|
|
// Detect large step changes of system time
|
|
time_t diff = current - m_lastCheck;
|
|
if (diff > 60 * 90 || diff < 0)
|
|
{
|
|
debug("Reset scheduled tasks (detected clock change greater than 90 minutes or negative)");
|
|
|
|
// check all tasks for the last week
|
|
m_lastCheck = current - 60 * 60 * 24 * 7;
|
|
m_executeProcess = false;
|
|
|
|
for (Task* task : &m_taskList)
|
|
{
|
|
if (task->m_hours != Task::STARTUP_TASK)
|
|
{
|
|
task->m_lastExecuted = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
time_t localCurrent = current + g_WorkState->GetLocalTimeOffset();
|
|
time_t localLastCheck = m_lastCheck + g_WorkState->GetLocalTimeOffset();
|
|
|
|
tm tmCurrent;
|
|
gmtime_r(&localCurrent, &tmCurrent);
|
|
tm tmLastCheck;
|
|
gmtime_r(&localLastCheck, &tmLastCheck);
|
|
|
|
tm tmLoop;
|
|
memcpy(&tmLoop, &tmLastCheck, sizeof(tmLastCheck));
|
|
tmLoop.tm_hour = tmCurrent.tm_hour;
|
|
tmLoop.tm_min = tmCurrent.tm_min;
|
|
tmLoop.tm_sec = tmCurrent.tm_sec;
|
|
time_t loop = Util::Timegm(&tmLoop);
|
|
|
|
while (loop <= localCurrent)
|
|
{
|
|
for (Task* task : &m_taskList)
|
|
{
|
|
if (task->m_lastExecuted != loop)
|
|
{
|
|
tm tmAppoint;
|
|
memcpy(&tmAppoint, &tmLoop, sizeof(tmLoop));
|
|
tmAppoint.tm_hour = task->m_hours;
|
|
tmAppoint.tm_min = task->m_minutes;
|
|
tmAppoint.tm_sec = 0;
|
|
|
|
time_t appoint = Util::Timegm(&tmAppoint);
|
|
|
|
int weekDay = tmAppoint.tm_wday;
|
|
if (weekDay == 0)
|
|
{
|
|
weekDay = 7;
|
|
}
|
|
|
|
bool weekDayOK = task->m_weekDaysBits == 0 || (task->m_weekDaysBits & (1 << (weekDay - 1)));
|
|
bool doTask = (task->m_hours >= 0 && weekDayOK && localLastCheck < appoint && appoint <= localCurrent) ||
|
|
(task->m_hours == Task::STARTUP_TASK && task->m_lastExecuted == 0);
|
|
|
|
if (doTask)
|
|
{
|
|
ExecuteTask(task);
|
|
task->m_lastExecuted = loop;
|
|
}
|
|
}
|
|
}
|
|
loop += 60 * 60 * 24; // inc day
|
|
gmtime_r(&loop, &tmLoop);
|
|
}
|
|
}
|
|
|
|
m_lastCheck = current;
|
|
}
|
|
|
|
PrintLog();
|
|
}
|
|
|
|
void Scheduler::ExecuteTask(Task* task)
|
|
{
|
|
#ifdef DEBUG
|
|
const char* commandName[] = { "Pause", "Unpause", "Pause Post-processing", "Unpause Post-processing",
|
|
"Set download rate", "Execute process", "Execute script",
|
|
"Pause Scan", "Unpause Scan", "Enable Server", "Disable Server", "Fetch Feed" };
|
|
debug("Executing scheduled command: %s", commandName[task->m_command]);
|
|
#endif
|
|
|
|
bool executeProcess = m_executeProcess || task->m_hours == Task::STARTUP_TASK;
|
|
|
|
switch (task->m_command)
|
|
{
|
|
case scDownloadRate:
|
|
if (!task->m_param.Empty())
|
|
{
|
|
g_WorkState->SetSpeedLimit(atoi(task->m_param) * 1024);
|
|
m_downloadRateChanged = true;
|
|
}
|
|
break;
|
|
|
|
case scPauseDownload:
|
|
case scUnpauseDownload:
|
|
g_WorkState->SetPauseDownload(task->m_command == scPauseDownload);
|
|
m_pauseDownloadChanged = true;
|
|
break;
|
|
|
|
case scPausePostProcess:
|
|
case scUnpausePostProcess:
|
|
g_WorkState->SetPausePostProcess(task->m_command == scPausePostProcess);
|
|
m_pausePostProcessChanged = true;
|
|
break;
|
|
|
|
case scPauseScan:
|
|
case scUnpauseScan:
|
|
g_WorkState->SetPauseScan(task->m_command == scPauseScan);
|
|
m_pauseScanChanged = true;
|
|
break;
|
|
|
|
case scExtensions:
|
|
case scProcess:
|
|
if (executeProcess)
|
|
{
|
|
SchedulerScriptController::StartScript(task->m_param, task->m_command == scProcess, task->m_id);
|
|
}
|
|
break;
|
|
|
|
case scActivateServer:
|
|
case scDeactivateServer:
|
|
EditServer(task->m_command == scActivateServer, task->m_param);
|
|
break;
|
|
|
|
case scFetchFeed:
|
|
if (executeProcess)
|
|
{
|
|
FetchFeed(task->m_param);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Scheduler::PrepareLog()
|
|
{
|
|
m_downloadRateChanged = false;
|
|
m_pauseDownloadChanged = false;
|
|
m_pausePostProcessChanged = false;
|
|
m_pauseScanChanged = false;
|
|
m_serverChanged = false;
|
|
}
|
|
|
|
void Scheduler::PrintLog()
|
|
{
|
|
if (m_downloadRateChanged)
|
|
{
|
|
info("Scheduler: setting download rate to %i KB/s", g_WorkState->GetSpeedLimit() / 1024);
|
|
}
|
|
if (m_pauseDownloadChanged)
|
|
{
|
|
info("Scheduler: %s download", g_WorkState->GetPauseDownload() ? "pausing" : "unpausing");
|
|
}
|
|
if (m_pausePostProcessChanged)
|
|
{
|
|
info("Scheduler: %s post-processing", g_WorkState->GetPausePostProcess() ? "pausing" : "unpausing");
|
|
}
|
|
if (m_pauseScanChanged)
|
|
{
|
|
info("Scheduler: %s scan", g_WorkState->GetPauseScan() ? "pausing" : "unpausing");
|
|
}
|
|
if (m_serverChanged)
|
|
{
|
|
int index = 0;
|
|
for (NewsServer* server : g_ServerPool->GetServers())
|
|
{
|
|
if (server->GetActive() != m_serverStatusList[index])
|
|
{
|
|
info("Scheduler: %s %s", server->GetActive() ? "activating" : "deactivating", server->GetName());
|
|
}
|
|
index++;
|
|
}
|
|
g_ServerPool->Changed();
|
|
}
|
|
}
|
|
|
|
void Scheduler::EditServer(bool active, const char* serverList)
|
|
{
|
|
Tokenizer tok(serverList, ",;");
|
|
while (const char* serverRef = tok.Next())
|
|
{
|
|
int id = atoi(serverRef);
|
|
for (NewsServer* server : g_ServerPool->GetServers())
|
|
{
|
|
if ((id > 0 && server->GetId() == id) ||
|
|
!strcasecmp(server->GetName(), serverRef))
|
|
{
|
|
if (!m_serverChanged)
|
|
{
|
|
// store old server status for logging
|
|
m_serverStatusList.clear();
|
|
m_serverStatusList.reserve(g_ServerPool->GetServers()->size());
|
|
for (NewsServer* server2 : g_ServerPool->GetServers())
|
|
{
|
|
m_serverStatusList.push_back(server2->GetActive());
|
|
}
|
|
}
|
|
m_serverChanged = true;
|
|
server->SetActive(active);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Scheduler::FetchFeed(const char* feedList)
|
|
{
|
|
Tokenizer tok(feedList, ",;");
|
|
while (const char* feedRef = tok.Next())
|
|
{
|
|
int id = atoi(feedRef);
|
|
for (FeedInfo* feed : g_FeedCoordinator->GetFeeds())
|
|
{
|
|
if (feed->GetId() == id ||
|
|
!strcasecmp(feed->GetName(), feedRef) ||
|
|
!strcasecmp("0", feedRef))
|
|
{
|
|
g_FeedCoordinator->FetchFeed(!strcasecmp("0", feedRef) ? 0 : feed->GetId());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Scheduler::CheckScheduledResume()
|
|
{
|
|
time_t resumeTime = g_WorkState->GetResumeTime();
|
|
time_t currentTime = Util::CurrentTime();
|
|
if (resumeTime > 0 && currentTime >= resumeTime)
|
|
{
|
|
info("Autoresume");
|
|
g_WorkState->SetResumeTime(0);
|
|
g_WorkState->SetPauseDownload(false);
|
|
g_WorkState->SetPausePostProcess(false);
|
|
g_WorkState->SetPauseScan(false);
|
|
}
|
|
}
|