2
* This file is part of nzbget
4
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
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.
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.
16
* You should have received a copy of the GNU General Public License
17
* along with this program; if not, write to the Free Software
18
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21
* $Date: 2013-05-15 18:45:36 +0200 (Wed, 15 May 2013) $
45
#include "ParCoordinator.h"
49
#include "QueueCoordinator.h"
50
#include "DiskState.h"
52
extern QueueCoordinator* g_pQueueCoordinator;
53
extern Options* g_pOptions;
54
extern DiskState* g_pDiskState;
56
#ifndef DISABLE_PARCHECK
57
bool ParCoordinator::PostParChecker::RequestMorePars(int iBlockNeeded, int* pBlockFound)
59
return m_pOwner->RequestMorePars(m_pPostInfo->GetNZBInfo(), GetParFilename(), iBlockNeeded, pBlockFound);
62
void ParCoordinator::PostParChecker::UpdateProgress()
64
m_pOwner->UpdateParCheckProgress();
67
void ParCoordinator::PostParChecker::PrintMessage(Message::EKind eKind, const char* szFormat, ...)
71
va_start(args, szFormat);
72
vsnprintf(szText, 1024, szFormat, args);
74
szText[1024-1] = '\0';
76
m_pOwner->PrintMessage(m_pPostInfo, eKind, "%s", szText);
79
void ParCoordinator::PostParRenamer::UpdateProgress()
81
m_pOwner->UpdateParRenameProgress();
84
void ParCoordinator::PostParRenamer::PrintMessage(Message::EKind eKind, const char* szFormat, ...)
88
va_start(args, szFormat);
89
vsnprintf(szText, 1024, szFormat, args);
91
szText[1024-1] = '\0';
93
m_pOwner->PrintMessage(m_pPostInfo, eKind, "%s", szText);
97
ParCoordinator::ParCoordinator()
99
debug("Creating ParCoordinator");
101
#ifndef DISABLE_PARCHECK
103
m_ParChecker.m_pOwner = this;
104
m_ParRenamer.m_pOwner = this;
108
ParCoordinator::~ParCoordinator()
110
debug("Destroying ParCoordinator");
113
#ifndef DISABLE_PARCHECK
114
void ParCoordinator::Stop()
116
debug("Stopping ParCoordinator");
120
if (m_ParChecker.IsRunning())
123
int iMSecWait = 5000;
124
while (m_ParChecker.IsRunning() && iMSecWait > 0)
129
if (m_ParChecker.IsRunning())
131
warn("Terminating par-check for %s", m_ParChecker.GetInfoName());
138
void ParCoordinator::PausePars(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo)
140
debug("ParCoordinator: Pausing pars");
142
for (FileQueue::iterator it = pDownloadQueue->GetFileQueue()->begin(); it != pDownloadQueue->GetFileQueue()->end(); it++)
144
FileInfo* pFileInfo = *it;
145
if (pFileInfo->GetNZBInfo() == pNZBInfo)
147
g_pQueueCoordinator->GetQueueEditor()->LockedEditEntry(pDownloadQueue, pFileInfo->GetID(), false,
148
QueueEditor::eaGroupPauseExtraPars, 0, NULL);
154
bool ParCoordinator::FindMainPars(const char* szPath, FileList* pFileList)
161
DirBrowser dir(szPath);
162
while (const char* filename = dir.Next())
165
if (ParseParFilename(filename, &iBaseLen, NULL))
172
// check if the base file already added to list
174
for (FileList::iterator it = pFileList->begin(); it != pFileList->end(); it++)
176
const char* filename2 = *it;
177
exists = SameParCollection(filename, filename2);
185
pFileList->push_back(strdup(filename));
189
return pFileList && !pFileList->empty();
192
bool ParCoordinator::SameParCollection(const char* szFilename1, const char* szFilename2)
194
int iBaseLen1 = 0, iBaseLen2 = 0;
195
return ParseParFilename(szFilename1, &iBaseLen1, NULL) &&
196
ParseParFilename(szFilename2, &iBaseLen2, NULL) &&
197
iBaseLen1 == iBaseLen2 &&
198
!strncasecmp(szFilename1, szFilename2, iBaseLen1);
201
bool ParCoordinator::ParseParFilename(const char* szParFilename, int* iBaseNameLen, int* iBlocks)
203
char szFilename[1024];
204
strncpy(szFilename, szParFilename, 1024);
205
szFilename[1024-1] = '\0';
206
for (char* p = szFilename; *p; p++) *p = tolower(*p); // convert string to lowercase
208
int iLen = strlen(szFilename);
214
// find last occurence of ".par2" and trim filename after it
215
char* szEnd = szFilename;
216
while (char* p = strstr(szEnd, ".par2")) szEnd = p + 5;
218
iLen = strlen(szFilename);
220
if (strcasecmp(szFilename + iLen - 5, ".par2"))
224
*(szFilename + iLen - 5) = '\0';
227
char* p = strrchr(szFilename, '.');
228
if (p && !strncasecmp(p, ".vol", 4))
230
char* b = strchr(p, '+');
237
blockcnt = atoi(b+1);
244
*iBaseNameLen = strlen(szFilename);
254
#ifndef DISABLE_PARCHECK
257
* DownloadQueue must be locked prior to call of this function.
259
void ParCoordinator::StartParCheckJob(PostInfo* pPostInfo)
261
m_eCurrentJob = jkParCheck;
262
m_ParChecker.SetPostInfo(pPostInfo);
263
m_ParChecker.SetDestDir(pPostInfo->GetNZBInfo()->GetDestDir());
264
m_ParChecker.SetNZBName(pPostInfo->GetNZBInfo()->GetName());
265
m_ParChecker.PrintMessage(Message::mkInfo, "Checking pars for %s", pPostInfo->GetInfoName());
266
pPostInfo->SetWorking(true);
267
m_ParChecker.Start();
271
* DownloadQueue must be locked prior to call of this function.
273
void ParCoordinator::StartParRenameJob(PostInfo* pPostInfo)
275
m_eCurrentJob = jkParRename;
276
m_ParRenamer.SetPostInfo(pPostInfo);
277
m_ParRenamer.SetDestDir(pPostInfo->GetNZBInfo()->GetDestDir());
278
m_ParRenamer.SetInfoName(pPostInfo->GetNZBInfo()->GetName());
279
m_ParRenamer.PrintMessage(Message::mkInfo, "Checking renamed files for %s", pPostInfo->GetNZBInfo()->GetName());
280
pPostInfo->SetWorking(true);
281
m_ParRenamer.Start();
284
bool ParCoordinator::Cancel()
286
if (m_eCurrentJob == jkParCheck)
288
#ifdef HAVE_PAR2_CANCEL
289
if (!m_ParChecker.GetCancelled())
291
debug("Cancelling par-repair for %s", m_ParChecker.GetInfoName());
292
m_ParChecker.Cancel();
296
warn("Cannot cancel par-repair for %s, used version of libpar2 does not support cancelling", m_ParChecker.GetInfoName());
299
else if (m_eCurrentJob == jkParRename)
301
if (!m_ParRenamer.GetCancelled())
303
debug("Cancelling par-rename for %s", m_ParRenamer.GetInfoName());
304
m_ParRenamer.Cancel();
312
* DownloadQueue must be locked prior to call of this function.
314
bool ParCoordinator::AddPar(FileInfo* pFileInfo, bool bDeleted)
316
bool bSameCollection = m_ParChecker.IsRunning() &&
317
pFileInfo->GetNZBInfo() == m_ParChecker.GetPostInfo()->GetNZBInfo() &&
318
SameParCollection(pFileInfo->GetFilename(), Util::BaseFileName(m_ParChecker.GetParFilename()));
319
if (bSameCollection && !bDeleted)
321
char szFullFilename[1024];
322
snprintf(szFullFilename, 1024, "%s%c%s", pFileInfo->GetNZBInfo()->GetDestDir(), (int)PATH_SEPARATOR, pFileInfo->GetFilename());
323
szFullFilename[1024-1] = '\0';
324
m_ParChecker.AddParFile(szFullFilename);
326
if (g_pOptions->GetParPauseQueue())
333
m_ParChecker.QueueChanged();
335
return bSameCollection;
338
void ParCoordinator::ParCheckCompleted()
340
DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue();
342
PostInfo* pPostInfo = m_ParChecker.GetPostInfo();
344
// Update ParStatus (accumulate result)
345
if ((m_ParChecker.GetStatus() == ParChecker::psRepaired ||
346
m_ParChecker.GetStatus() == ParChecker::psRepairNotNeeded) &&
347
pPostInfo->GetNZBInfo()->GetParStatus() <= NZBInfo::psSkipped)
349
pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::psSuccess);
351
else if (m_ParChecker.GetStatus() == ParChecker::psRepairPossible &&
352
pPostInfo->GetNZBInfo()->GetParStatus() != NZBInfo::psFailure)
354
pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::psRepairPossible);
358
pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::psFailure);
361
pPostInfo->SetWorking(false);
362
pPostInfo->SetStage(PostInfo::ptQueued);
364
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
366
g_pDiskState->SaveDownloadQueue(pDownloadQueue);
369
g_pQueueCoordinator->UnlockQueue();
374
* returns true, if the files with required number of blocks were unpaused,
375
* or false if there are no more files in queue for this collection or not enough blocks
377
bool ParCoordinator::RequestMorePars(NZBInfo* pNZBInfo, const char* szParFilename, int iBlockNeeded, int* pBlockFound)
379
DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue();
384
int iCurBlockFound = 0;
386
FindPars(pDownloadQueue, pNZBInfo, szParFilename, &blocks, true, true, &iCurBlockFound);
387
iBlockFound += iCurBlockFound;
388
if (iBlockFound < iBlockNeeded)
390
FindPars(pDownloadQueue, pNZBInfo, szParFilename, &blocks, true, false, &iCurBlockFound);
391
iBlockFound += iCurBlockFound;
393
if (iBlockFound < iBlockNeeded && !g_pOptions->GetStrictParName())
395
FindPars(pDownloadQueue, pNZBInfo, szParFilename, &blocks, false, false, &iCurBlockFound);
396
iBlockFound += iCurBlockFound;
399
if (iBlockFound >= iBlockNeeded)
401
// 1. first unpause all files with par-blocks less or equal iBlockNeeded
402
// starting from the file with max block count.
403
// if par-collection was built exponentially and all par-files present,
404
// this step selects par-files with exact number of blocks we need.
405
while (iBlockNeeded > 0)
407
BlockInfo* pBestBlockInfo = NULL;
408
for (Blocks::iterator it = blocks.begin(); it != blocks.end(); it++)
410
BlockInfo* pBlockInfo = *it;
411
if (pBlockInfo->m_iBlockCount <= iBlockNeeded &&
412
(!pBestBlockInfo || pBestBlockInfo->m_iBlockCount < pBlockInfo->m_iBlockCount))
414
pBestBlockInfo = pBlockInfo;
419
if (pBestBlockInfo->m_pFileInfo->GetPaused())
421
m_ParChecker.PrintMessage(Message::mkInfo, "Unpausing %s%c%s for par-recovery", pNZBInfo->GetName(), (int)PATH_SEPARATOR, pBestBlockInfo->m_pFileInfo->GetFilename());
422
pBestBlockInfo->m_pFileInfo->SetPaused(false);
423
pBestBlockInfo->m_pFileInfo->SetExtraPriority(true);
425
iBlockNeeded -= pBestBlockInfo->m_iBlockCount;
426
blocks.remove(pBestBlockInfo);
427
delete pBestBlockInfo;
435
// 2. then unpause other files
436
// this step only needed if the par-collection was built not exponentially
437
// or not all par-files present (or some of them were corrupted)
438
// this step is not optimal, but we hope, that the first step will work good
439
// in most cases and we will not need the second step often
440
while (iBlockNeeded > 0)
442
BlockInfo* pBlockInfo = blocks.front();
443
if (pBlockInfo->m_pFileInfo->GetPaused())
445
m_ParChecker.PrintMessage(Message::mkInfo, "Unpausing %s%c%s for par-recovery", pNZBInfo->GetName(), (int)PATH_SEPARATOR, pBlockInfo->m_pFileInfo->GetFilename());
446
pBlockInfo->m_pFileInfo->SetPaused(false);
447
pBlockInfo->m_pFileInfo->SetExtraPriority(true);
449
iBlockNeeded -= pBlockInfo->m_iBlockCount;
453
g_pQueueCoordinator->UnlockQueue();
457
*pBlockFound = iBlockFound;
460
for (Blocks::iterator it = blocks.begin(); it != blocks.end(); it++)
466
bool bOK = iBlockNeeded <= 0;
468
if (bOK && g_pOptions->GetParPauseQueue())
476
void ParCoordinator::FindPars(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, const char* szParFilename,
477
Blocks* pBlocks, bool bStrictParName, bool bExactParName, int* pBlockFound)
481
// extract base name from m_szParFilename (trim .par2-extension and possible .vol-part)
482
char* szBaseParFilename = Util::BaseFileName(szParFilename);
483
char szMainBaseFilename[1024];
484
int iMainBaseLen = 0;
485
if (!ParseParFilename(szBaseParFilename, &iMainBaseLen, NULL))
488
error("Internal error: could not parse filename %s", szBaseParFilename);
491
int maxlen = iMainBaseLen < 1024 ? iMainBaseLen : 1024 - 1;
492
strncpy(szMainBaseFilename, szBaseParFilename, maxlen);
493
szMainBaseFilename[maxlen] = '\0';
494
for (char* p = szMainBaseFilename; *p; p++) *p = tolower(*p); // convert string to lowercase
496
for (FileQueue::iterator it = pDownloadQueue->GetFileQueue()->begin(); it != pDownloadQueue->GetFileQueue()->end(); it++)
498
FileInfo* pFileInfo = *it;
500
if (pFileInfo->GetNZBInfo() == pNZBInfo &&
501
ParseParFilename(pFileInfo->GetFilename(), NULL, &iBlocks) &&
504
bool bUseFile = true;
508
bUseFile = SameParCollection(pFileInfo->GetFilename(), Util::BaseFileName(szParFilename));
510
else if (bStrictParName)
512
// the pFileInfo->GetFilename() may be not confirmed and may contain
513
// additional texts if Subject could not be parsed correctly
515
char szLoFileName[1024];
516
strncpy(szLoFileName, pFileInfo->GetFilename(), 1024);
517
szLoFileName[1024-1] = '\0';
518
for (char* p = szLoFileName; *p; p++) *p = tolower(*p); // convert string to lowercase
520
char szCandidateFileName[1024];
521
snprintf(szCandidateFileName, 1024, "%s.par2", szMainBaseFilename);
522
szCandidateFileName[1024-1] = '\0';
523
if (!strstr(szLoFileName, szCandidateFileName))
525
snprintf(szCandidateFileName, 1024, "%s.vol", szMainBaseFilename);
526
szCandidateFileName[1024-1] = '\0';
527
bUseFile = strstr(szLoFileName, szCandidateFileName);
531
bool bAlreadyAdded = false;
532
// check if file is not in the list already
535
for (Blocks::iterator it = pBlocks->begin(); it != pBlocks->end(); it++)
537
BlockInfo* pBlockInfo = *it;
538
if (pBlockInfo->m_pFileInfo == pFileInfo)
540
bAlreadyAdded = true;
546
// if it is a par2-file with blocks and it was from the same NZB-request
547
// and it belongs to the same file collection (same base name),
548
// then OK, we can use it
549
if (bUseFile && !bAlreadyAdded)
551
BlockInfo* pBlockInfo = new BlockInfo();
552
pBlockInfo->m_pFileInfo = pFileInfo;
553
pBlockInfo->m_iBlockCount = iBlocks;
554
pBlocks->push_back(pBlockInfo);
555
*pBlockFound += iBlocks;
561
void ParCoordinator::UpdateParCheckProgress()
563
g_pQueueCoordinator->LockQueue();
565
PostInfo* pPostInfo = m_ParChecker.GetPostInfo();
566
if (m_ParChecker.GetFileProgress() == 0)
568
pPostInfo->SetProgressLabel(m_ParChecker.GetProgressLabel());
570
pPostInfo->SetFileProgress(m_ParChecker.GetFileProgress());
571
pPostInfo->SetStageProgress(m_ParChecker.GetStageProgress());
572
PostInfo::EStage StageKind[] = { PostInfo::ptLoadingPars, PostInfo::ptVerifyingSources, PostInfo::ptRepairing, PostInfo::ptVerifyingRepaired };
573
PostInfo::EStage eStage = StageKind[m_ParChecker.GetStage()];
574
time_t tCurrent = time(NULL);
576
if (!pPostInfo->GetStartTime())
578
pPostInfo->SetStartTime(tCurrent);
581
if (pPostInfo->GetStage() != eStage)
583
pPostInfo->SetStage(eStage);
584
pPostInfo->SetStageTime(tCurrent);
587
bool bParCancel = false;
588
#ifdef HAVE_PAR2_CANCEL
589
if (!m_ParChecker.GetCancelled())
591
if ((g_pOptions->GetParTimeLimit() > 0) &&
592
m_ParChecker.GetStage() == ParChecker::ptRepairing &&
593
((g_pOptions->GetParTimeLimit() > 5 && tCurrent - pPostInfo->GetStageTime() > 5 * 60) ||
594
(g_pOptions->GetParTimeLimit() <= 5 && tCurrent - pPostInfo->GetStageTime() > 1 * 60)))
596
// first five (or one) minutes elapsed, now can check the estimated time
597
int iEstimatedRepairTime = (int)((tCurrent - pPostInfo->GetStartTime()) * 1000 /
598
(pPostInfo->GetStageProgress() > 0 ? pPostInfo->GetStageProgress() : 1));
599
if (iEstimatedRepairTime > g_pOptions->GetParTimeLimit() * 60)
601
debug("Estimated repair time %i seconds", iEstimatedRepairTime);
602
m_ParChecker.PrintMessage(Message::mkWarning, "Cancelling par-repair for %s, estimated repair time (%i minutes) exceeds allowed repair time", m_ParChecker.GetInfoName(), iEstimatedRepairTime / 60);
611
m_ParChecker.Cancel();
614
g_pQueueCoordinator->UnlockQueue();
616
CheckPauseState(pPostInfo);
619
void ParCoordinator::CheckPauseState(PostInfo* pPostInfo)
621
if (g_pOptions->GetPausePostProcess())
623
time_t tStageTime = pPostInfo->GetStageTime();
624
time_t tStartTime = pPostInfo->GetStartTime();
625
time_t tWaitTime = time(NULL);
627
// wait until Post-processor is unpaused
628
while (g_pOptions->GetPausePostProcess() && !m_bStopped)
632
// update time stamps
634
time_t tDelta = time(NULL) - tWaitTime;
638
pPostInfo->SetStageTime(tStageTime + tDelta);
643
pPostInfo->SetStartTime(tStartTime + tDelta);
649
void ParCoordinator::ParRenameCompleted()
651
DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue();
653
PostInfo* pPostInfo = m_ParRenamer.GetPostInfo();
654
pPostInfo->GetNZBInfo()->SetRenameStatus(m_ParRenamer.GetStatus() == ParRenamer::psSuccess ? NZBInfo::rsSuccess : NZBInfo::rsFailure);
655
pPostInfo->SetWorking(false);
656
pPostInfo->SetStage(PostInfo::ptQueued);
658
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
660
g_pDiskState->SaveDownloadQueue(pDownloadQueue);
663
g_pQueueCoordinator->UnlockQueue();
666
void ParCoordinator::UpdateParRenameProgress()
668
g_pQueueCoordinator->LockQueue();
670
PostInfo* pPostInfo = m_ParRenamer.GetPostInfo();
671
pPostInfo->SetProgressLabel(m_ParRenamer.GetProgressLabel());
672
pPostInfo->SetStageProgress(m_ParRenamer.GetStageProgress());
673
time_t tCurrent = time(NULL);
675
if (!pPostInfo->GetStartTime())
677
pPostInfo->SetStartTime(tCurrent);
680
if (pPostInfo->GetStage() != PostInfo::ptRenaming)
682
pPostInfo->SetStage(PostInfo::ptRenaming);
683
pPostInfo->SetStageTime(tCurrent);
686
g_pQueueCoordinator->UnlockQueue();
688
CheckPauseState(pPostInfo);
691
void ParCoordinator::PrintMessage(PostInfo* pPostInfo, Message::EKind eKind, const char* szFormat, ...)
695
va_start(args, szFormat);
696
vsnprintf(szText, 1024, szFormat, args);
698
szText[1024-1] = '\0';
700
pPostInfo->AppendMessage(eKind, szText);
704
case Message::mkDetail:
705
detail("%s", szText);
708
case Message::mkInfo:
712
case Message::mkWarning:
716
case Message::mkError:
720
case Message::mkDebug: