/* * This file is part of nzbget. See . * * Copyright (C) 2008-2019 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, see . */ #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) { 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& task1, const std::unique_ptr& 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); } }