1 /*
2 * This file is part of nzbget. See <http://nzbget.net>.
3 *
4 * Copyright (C) 2007-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20
21 #include "nzbget.h"
22 #include "NString.h"
23 #include "QueueScript.h"
24 #include "NzbScript.h"
25 #include "Options.h"
26 #include "Log.h"
27 #include "Util.h"
28 #include "FileSystem.h"
29
30 static const char* QUEUE_EVENT_NAMES[] = {
31 "FILE_DOWNLOADED",
32 "URL_COMPLETED",
33 "NZB_MARKED",
34 "NZB_ADDED",
35 "NZB_NAMED",
36 "NZB_DOWNLOADED",
37 "NZB_DELETED" };
38
39 class QueueScriptController : public Thread, public NzbScriptController
40 {
41 public:
42 virtual void Run();
43 static void StartScript(NzbInfo* nzbInfo, ScriptConfig::Script* script, QueueScriptCoordinator::EEvent event);
44
45 protected:
46 virtual void ExecuteScript(ScriptConfig::Script* script);
47 virtual void AddMessage(Message::EKind kind, const char* text);
48
49 private:
50 CString m_nzbName;
51 CString m_nzbFilename;
52 CString m_url;
53 CString m_category;
54 CString m_destDir;
55 CString m_queuedFilename;
56 int m_id;
57 int m_priority;
58 CString m_dupeKey;
59 EDupeMode m_dupeMode;
60 int m_dupeScore;
61 NzbParameterList m_parameters;
62 int m_prefixLen;
63 ScriptConfig::Script* m_script;
64 QueueScriptCoordinator::EEvent m_event;
65 bool m_markBad;
66 NzbInfo::EDeleteStatus m_deleteStatus;
67 NzbInfo::EUrlStatus m_urlStatus;
68 NzbInfo::EMarkStatus m_markStatus;
69
70 void PrepareParams(const char* scriptName);
71 };
72
73
StartScript(NzbInfo * nzbInfo,ScriptConfig::Script * script,QueueScriptCoordinator::EEvent event)74 void QueueScriptController::StartScript(NzbInfo* nzbInfo, ScriptConfig::Script* script, QueueScriptCoordinator::EEvent event)
75 {
76 QueueScriptController* scriptController = new QueueScriptController();
77
78 scriptController->m_nzbName = nzbInfo->GetName();
79 scriptController->m_nzbFilename = nzbInfo->GetFilename();
80 scriptController->m_url = nzbInfo->GetUrl();
81 scriptController->m_category = nzbInfo->GetCategory();
82 scriptController->m_destDir = nzbInfo->GetDestDir();
83 scriptController->m_queuedFilename = nzbInfo->GetQueuedFilename();
84 scriptController->m_id = nzbInfo->GetId();
85 scriptController->m_priority = nzbInfo->GetPriority();
86 scriptController->m_dupeKey = nzbInfo->GetDupeKey();
87 scriptController->m_dupeMode = nzbInfo->GetDupeMode();
88 scriptController->m_dupeScore = nzbInfo->GetDupeScore();
89 scriptController->m_parameters.CopyFrom(nzbInfo->GetParameters());
90 scriptController->m_script = script;
91 scriptController->m_event = event;
92 scriptController->m_prefixLen = 0;
93 scriptController->m_markBad = false;
94 scriptController->m_deleteStatus = nzbInfo->GetDeleteStatus();
95 scriptController->m_urlStatus = nzbInfo->GetUrlStatus();
96 scriptController->m_markStatus = nzbInfo->GetMarkStatus();
97 scriptController->SetAutoDestroy(true);
98
99 scriptController->Start();
100 }
101
Run()102 void QueueScriptController::Run()
103 {
104 ExecuteScript(m_script);
105
106 SetLogPrefix(nullptr);
107
108 if (m_markBad)
109 {
110 GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
111 NzbInfo* nzbInfo = downloadQueue->GetQueue()->Find(m_id);
112 if (nzbInfo)
113 {
114 nzbInfo->PrintMessage(Message::mkWarning, "Cancelling download and deleting %s", *m_nzbName);
115 nzbInfo->SetDeleteStatus(NzbInfo::dsBad);
116 downloadQueue->EditEntry(m_id, DownloadQueue::eaGroupDelete, nullptr);
117 }
118 }
119
120 g_QueueScriptCoordinator->CheckQueue();
121 }
122
ExecuteScript(ScriptConfig::Script * script)123 void QueueScriptController::ExecuteScript(ScriptConfig::Script* script)
124 {
125 PrintMessage(m_event == QueueScriptCoordinator::qeFileDownloaded ? Message::mkDetail : Message::mkInfo,
126 "Executing queue-script %s for %s", script->GetName(), FileSystem::BaseFileName(m_nzbName));
127
128 SetArgs({script->GetLocation()});
129
130 BString<1024> infoName("queue-script %s for %s", script->GetName(), FileSystem::BaseFileName(m_nzbName));
131 SetInfoName(infoName);
132
133 SetLogPrefix(script->GetDisplayName());
134 m_prefixLen = strlen(script->GetDisplayName()) + 2; // 2 = strlen(": ");
135 PrepareParams(script->GetName());
136
137 Execute();
138
139 SetLogPrefix(nullptr);
140 }
141
PrepareParams(const char * scriptName)142 void QueueScriptController::PrepareParams(const char* scriptName)
143 {
144 ResetEnv();
145
146 SetEnvVar("NZBNA_NZBNAME", m_nzbName);
147 SetIntEnvVar("NZBNA_NZBID", m_id);
148 SetEnvVar("NZBNA_FILENAME", m_nzbFilename);
149 SetEnvVar("NZBNA_DIRECTORY", m_destDir);
150 SetEnvVar("NZBNA_QUEUEDFILE", m_queuedFilename);
151 SetEnvVar("NZBNA_URL", m_url);
152 SetEnvVar("NZBNA_CATEGORY", m_category);
153 SetIntEnvVar("NZBNA_PRIORITY", m_priority);
154 SetIntEnvVar("NZBNA_LASTID", m_id); // deprecated
155
156 SetEnvVar("NZBNA_DUPEKEY", m_dupeKey);
157 SetIntEnvVar("NZBNA_DUPESCORE", m_dupeScore);
158
159 const char* dupeModeName[] = { "SCORE", "ALL", "FORCE" };
160 SetEnvVar("NZBNA_DUPEMODE", dupeModeName[m_dupeMode]);
161
162 SetEnvVar("NZBNA_EVENT", QUEUE_EVENT_NAMES[m_event]);
163
164 const char* deleteStatusName[] = { "NONE", "MANUAL", "HEALTH", "DUPE", "BAD", "GOOD", "COPY", "SCAN" };
165 SetEnvVar("NZBNA_DELETESTATUS", deleteStatusName[m_deleteStatus]);
166
167 const char* urlStatusName[] = { "NONE", "UNKNOWN", "SUCCESS", "FAILURE", "UNKNOWN", "SCAN_SKIPPED", "SCAN_FAILURE" };
168 SetEnvVar("NZBNA_URLSTATUS", urlStatusName[m_urlStatus]);
169
170 const char* markStatusName[] = { "NONE", "BAD", "GOOD", "SUCCESS" };
171 SetEnvVar("NZBNA_MARKSTATUS", markStatusName[m_markStatus]);
172
173 PrepareEnvScript(&m_parameters, scriptName);
174 }
175
AddMessage(Message::EKind kind,const char * text)176 void QueueScriptController::AddMessage(Message::EKind kind, const char* text)
177 {
178 const char* msgText = text + m_prefixLen;
179
180 if (!strncmp(msgText, "[NZB] ", 6))
181 {
182 debug("Command %s detected", msgText + 6);
183 if (!strncmp(msgText + 6, "NZBPR_", 6))
184 {
185 CString param = msgText + 6 + 6;
186 char* value = strchr(param, '=');
187 if (value)
188 {
189 *value = '\0';
190 GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
191 NzbInfo* nzbInfo = QueueScriptCoordinator::FindNzbInfo(downloadQueue, m_id);
192 if (nzbInfo)
193 {
194 nzbInfo->GetParameters()->SetParameter(param, value + 1);
195 }
196 }
197 else
198 {
199 error("Invalid command \"%s\" received from %s", msgText, GetInfoName());
200 }
201 }
202 else if (!strncmp(msgText + 6, "DIRECTORY=", 10) &&
203 m_event == QueueScriptCoordinator::qeNzbDownloaded)
204 {
205 GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
206 NzbInfo* nzbInfo = QueueScriptCoordinator::FindNzbInfo(downloadQueue, m_id);
207 if (nzbInfo)
208 {
209 nzbInfo->SetFinalDir(msgText + 6 + 10);
210 }
211 }
212 else if (!strncmp(msgText + 6, "MARK=BAD", 8))
213 {
214 m_markBad = true;
215 GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
216 NzbInfo* nzbInfo = QueueScriptCoordinator::FindNzbInfo(downloadQueue, m_id);
217 if (nzbInfo)
218 {
219 nzbInfo->PrintMessage(Message::mkWarning, "Marking %s as bad", *m_nzbName);
220 nzbInfo->SetMarkStatus(NzbInfo::ksBad);
221 }
222 }
223 else
224 {
225 error("Invalid command \"%s\" received from %s", msgText, GetInfoName());
226 }
227 }
228 else
229 {
230 NzbInfo* nzbInfo = nullptr;
231 {
232 GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
233 nzbInfo = QueueScriptCoordinator::FindNzbInfo(downloadQueue, m_id);
234 if (nzbInfo)
235 {
236 nzbInfo->AddMessage(kind, text);
237 }
238 }
239
240 if (!nzbInfo)
241 {
242 ScriptController::AddMessage(kind, text);
243 }
244 }
245 }
246
247
InitOptions()248 void QueueScriptCoordinator::InitOptions()
249 {
250 m_hasQueueScripts = false;
251 for (ScriptConfig::Script& script : g_ScriptConfig->GetScripts())
252 {
253 if (script.GetQueueScript())
254 {
255 m_hasQueueScripts = true;
256 break;
257 }
258 }
259 }
260
EnqueueScript(NzbInfo * nzbInfo,EEvent event)261 void QueueScriptCoordinator::EnqueueScript(NzbInfo* nzbInfo, EEvent event)
262 {
263 if (!m_hasQueueScripts)
264 {
265 return;
266 }
267
268 Guard guard(m_queueMutex);
269
270 if (event == qeNzbDownloaded)
271 {
272 // delete all other queued scripts for this nzb
273 m_queue.erase(std::remove_if(m_queue.begin(), m_queue.end(),
274 [nzbInfo](std::unique_ptr<QueueItem>& queueItem)
275 {
276 return queueItem->GetNzbId() == nzbInfo->GetId();
277 }),
278 m_queue.end());
279 }
280
281 // respect option "EventInterval"
282 time_t curTime = Util::CurrentTime();
283 if (event == qeFileDownloaded &&
284 (g_Options->GetEventInterval() == -1 ||
285 (g_Options->GetEventInterval() > 0 && curTime - nzbInfo->GetQueueScriptTime() > 0 &&
286 (int)(curTime - nzbInfo->GetQueueScriptTime()) < g_Options->GetEventInterval())))
287 {
288 return;
289 }
290
291 for (ScriptConfig::Script& script : g_ScriptConfig->GetScripts())
292 {
293 if (UsableScript(script, nzbInfo, event))
294 {
295 bool alreadyQueued = false;
296 if (event == qeFileDownloaded)
297 {
298 // check if this script is already queued for this nzb
299 for (QueueItem* queueItem : &m_queue)
300 {
301 if (queueItem->GetNzbId() == nzbInfo->GetId() && queueItem->GetScript() == &script)
302 {
303 alreadyQueued = true;
304 break;
305 }
306 }
307 }
308
309 if (!alreadyQueued)
310 {
311 std::unique_ptr<QueueItem> queueItem = std::make_unique<QueueItem>(nzbInfo->GetId(), &script, event);
312 if (m_curItem)
313 {
314 m_queue.push_back(std::move(queueItem));
315 }
316 else
317 {
318 m_curItem = std::move(queueItem);
319 QueueScriptController::StartScript(nzbInfo, m_curItem->GetScript(), m_curItem->GetEvent());
320 }
321 }
322
323 nzbInfo->SetQueueScriptTime(Util::CurrentTime());
324 }
325 }
326 }
327
UsableScript(ScriptConfig::Script & script,NzbInfo * nzbInfo,EEvent event)328 bool QueueScriptCoordinator::UsableScript(ScriptConfig::Script& script, NzbInfo* nzbInfo, EEvent event)
329 {
330 if (!script.GetQueueScript())
331 {
332 return false;
333 }
334
335 if (!Util::EmptyStr(script.GetQueueEvents()) && !strstr(script.GetQueueEvents(), QUEUE_EVENT_NAMES[event]))
336 {
337 return false;
338 }
339
340 // check extension scripts assigned for that nzb
341 for (NzbParameter& parameter : nzbInfo->GetParameters())
342 {
343 const char* varname = parameter.GetName();
344 if (strlen(varname) > 0 && varname[0] != '*' && varname[strlen(varname)-1] == ':' &&
345 (!strcasecmp(parameter.GetValue(), "yes") ||
346 !strcasecmp(parameter.GetValue(), "on") ||
347 !strcasecmp(parameter.GetValue(), "1")))
348 {
349 BString<1024> scriptName = varname;
350 scriptName[strlen(scriptName)-1] = '\0'; // remove trailing ':'
351 if (FileSystem::SameFilename(scriptName, script.GetName()))
352 {
353 return true;
354 }
355 }
356 }
357
358 // for URL-events the extension scripts are not assigned yet;
359 // instead we take the default extension scripts for the category (or global)
360 if (event == qeUrlCompleted)
361 {
362 const char* postScript = g_Options->GetExtensions();
363 if (!Util::EmptyStr(nzbInfo->GetCategory()))
364 {
365 Options::Category* categoryObj = g_Options->FindCategory(nzbInfo->GetCategory(), false);
366 if (categoryObj && !Util::EmptyStr(categoryObj->GetExtensions()))
367 {
368 postScript = categoryObj->GetExtensions();
369 }
370 }
371
372 if (!Util::EmptyStr(postScript))
373 {
374 Tokenizer tok(postScript, ",;");
375 while (const char* scriptName = tok.Next())
376 {
377 if (FileSystem::SameFilename(scriptName, script.GetName()))
378 {
379 return true;
380 }
381 }
382 }
383 }
384
385 return false;
386 }
387
FindNzbInfo(DownloadQueue * downloadQueue,int nzbId)388 NzbInfo* QueueScriptCoordinator::FindNzbInfo(DownloadQueue* downloadQueue, int nzbId)
389 {
390 NzbInfo* nzbInfo = downloadQueue->GetQueue()->Find(nzbId);
391 if (nzbInfo)
392 {
393 return nzbInfo;
394 }
395
396 HistoryInfo* historyInfo = downloadQueue->GetHistory()->Find(nzbId);
397 if (historyInfo)
398 {
399 return historyInfo->GetNzbInfo();
400 }
401
402 return nullptr;
403 }
404
CheckQueue()405 void QueueScriptCoordinator::CheckQueue()
406 {
407 if (m_stopped)
408 {
409 return;
410 }
411
412 GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
413 Guard guard(m_queueMutex);
414
415 m_curItem.reset();
416 NzbInfo* curNzbInfo = nullptr;
417 Queue::iterator itCurItem;
418
419 for (Queue::iterator it = m_queue.begin(); it != m_queue.end(); )
420 {
421 std::unique_ptr<QueueItem>& queueItem = *it;
422
423 NzbInfo* nzbInfo = FindNzbInfo(downloadQueue, queueItem->GetNzbId());
424
425 // in a case this nzb must not be processed further - delete queue script from queue
426 EEvent event = queueItem->GetEvent();
427 bool ignoreEvent = !nzbInfo ||
428 (nzbInfo->GetDeleteStatus() != NzbInfo::dsNone && event != qeNzbDeleted && event != qeNzbMarked) ||
429 (nzbInfo->GetMarkStatus() == NzbInfo::ksBad && event != qeNzbMarked);
430
431 if (ignoreEvent)
432 {
433 it = m_queue.erase(it);
434 if (curNzbInfo)
435 {
436 // process from the beginning, while "erase" invalidated "itCurItem"
437 curNzbInfo = nullptr;
438 it = m_queue.begin();
439 }
440 continue;
441 }
442
443 if (!m_curItem || queueItem->GetEvent() > m_curItem->GetEvent())
444 {
445 itCurItem = it;
446 curNzbInfo = nzbInfo;
447 }
448
449 it++;
450 }
451
452 if (curNzbInfo)
453 {
454 m_curItem = std::move(*itCurItem);
455 m_queue.erase(itCurItem);
456 QueueScriptController::StartScript(curNzbInfo, m_curItem->GetScript(), m_curItem->GetEvent());
457 }
458 }
459
HasJob(int nzbId,bool * active)460 bool QueueScriptCoordinator::HasJob(int nzbId, bool* active)
461 {
462 Guard guard(m_queueMutex);
463
464 bool working = m_curItem && m_curItem->GetNzbId() == nzbId;
465 if (active)
466 {
467 *active = working;
468 }
469 if (!working)
470 {
471 for (QueueItem* queueItem : &m_queue)
472 {
473 working = queueItem->GetNzbId() == nzbId;
474 if (working)
475 {
476 break;
477 }
478 }
479 }
480
481 return working;
482 }
483
GetQueueSize()484 int QueueScriptCoordinator::GetQueueSize()
485 {
486 Guard guard(m_queueMutex);
487
488 int queuedCount = m_queue.size();
489 if (m_curItem)
490 {
491 queuedCount++;
492 }
493
494 return queuedCount;
495 }
496