Files
nzbget/Scanner.cpp

364 lines
9.6 KiB
C++

/*
* This file is part of nzbget
*
* Copyright (C) 2007-2010 Andrei Prygounkov <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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Revision$
* $Date$
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <fstream>
#ifdef WIN32
#include <direct.h>
#endif
#include <sys/stat.h>
#include <errno.h>
#include "nzbget.h"
#include "Scanner.h"
#include "Options.h"
#include "Log.h"
#include "QueueCoordinator.h"
#include "ScriptController.h"
#include "DiskState.h"
#include "Util.h"
extern QueueCoordinator* g_pQueueCoordinator;
extern Options* g_pOptions;
extern DiskState* g_pDiskState;
Scanner::FileData::FileData(const char* szFilename)
{
m_szFilename = strdup(szFilename);
m_iSize = 0;
m_tLastChange = 0;
}
Scanner::FileData::~FileData()
{
free(m_szFilename);
}
Scanner::Scanner()
{
debug("Creating Scanner");
m_bRequestedNZBDirScan = false;
m_iNZBDirInterval = g_pOptions->GetNzbDirInterval() * 1000;
m_iPass = 0;
m_iStepMSec = 0;
const char* szNZBScript = g_pOptions->GetNZBProcess();
m_bNZBScript = szNZBScript && strlen(szNZBScript) > 0;
}
Scanner::~Scanner()
{
debug("Destroying Scanner");
for (FileList::iterator it = m_FileList.begin(); it != m_FileList.end(); it++)
{
delete *it;
}
m_FileList.clear();
}
void Scanner::Check()
{
if (g_pOptions->GetNzbDir() && (m_bRequestedNZBDirScan ||
(!g_pOptions->GetPauseScan() && g_pOptions->GetNzbDirInterval() > 0 &&
m_iNZBDirInterval >= g_pOptions->GetNzbDirInterval() * 1000)))
{
// check nzbdir every g_pOptions->GetNzbDirInterval() seconds or if requested
bool bCheckStat = !m_bRequestedNZBDirScan;
m_bRequestedNZBDirScan = false;
CheckIncomingNZBs(g_pOptions->GetNzbDir(), "", bCheckStat);
m_iNZBDirInterval = 0;
// if NzbDirFileAge is less than NzbDirInterval (that can happen if NzbDirInterval
// is set for rare scans like once per hour) we make 4 scans:
// - one additional scan is neccessary to check sizes of detected files;
// - another scan is required to check files which were extracted by NzbProcess-script;
// - third scan is needed to check sizes of extracted files.
if (g_pOptions->GetNzbDirFileAge() < g_pOptions->GetNzbDirInterval())
{
int iMaxPass = m_bNZBScript ? 3 : 1;
if (m_iPass < iMaxPass)
{
// scheduling another scan of incoming directory in NzbDirFileAge seconds.
m_iNZBDirInterval = (g_pOptions->GetNzbDirInterval() - g_pOptions->GetNzbDirFileAge()) * 1000;
m_iPass++;
}
else
{
m_iPass = 0;
}
}
DropOldFiles();
}
m_iNZBDirInterval += m_iStepMSec;
}
/**
* Check if there are files in directory for incoming nzb-files
* and add them to download queue
*/
void Scanner::CheckIncomingNZBs(const char* szDirectory, const char* szCategory, bool bCheckStat)
{
DirBrowser dir(szDirectory);
while (const char* filename = dir.Next())
{
struct stat buffer;
char fullfilename[1023 + 1]; // one char reserved for the trailing slash (if needed)
snprintf(fullfilename, 1023, "%s%s", szDirectory, filename);
fullfilename[1023 - 1] = '\0';
if (!stat(fullfilename, &buffer))
{
// check subfolders
if ((buffer.st_mode & S_IFDIR) != 0 && strcmp(filename, ".") && strcmp(filename, ".."))
{
fullfilename[strlen(fullfilename) + 1] = '\0';
fullfilename[strlen(fullfilename)] = PATH_SEPARATOR;
const char* szUseCategory = filename;
char szSubCategory[1024];
if (strlen(szCategory) > 0)
{
snprintf(szSubCategory, 1023, "%s%c%s", szCategory, PATH_SEPARATOR, filename);
szSubCategory[1024 - 1] = '\0';
szUseCategory = szSubCategory;
}
CheckIncomingNZBs(fullfilename, szUseCategory, bCheckStat);
}
else if ((buffer.st_mode & S_IFDIR) == 0 && CanProcessFile(fullfilename, bCheckStat))
{
ProcessIncomingFile(szDirectory, filename, fullfilename, szCategory);
}
}
}
}
/**
* Only files which were not changed during last g_pOptions->GetNzbDirFileAge() seconds
* can be processed. That prevents the processing of files, which are currently being
* copied into nzb-directory (eg. being downloaded in web-browser).
*/
bool Scanner::CanProcessFile(const char* szFullFilename, bool bCheckStat)
{
const char* szExtension = strrchr(szFullFilename, '.');
if (!szExtension ||
!strcasecmp(szExtension, ".queued") ||
!strcasecmp(szExtension, ".error") ||
!strcasecmp(szExtension, ".processed"))
{
return false;
}
if (!bCheckStat)
{
return true;
}
long long lSize = Util::FileSize(szFullFilename);
time_t tCurrent = time(NULL);
bool bCanProcess = false;
bool bInList = false;
for (FileList::iterator it = m_FileList.begin(); it != m_FileList.end(); it++)
{
FileData* pFileData = *it;
if (!strcmp(pFileData->GetFilename(), szFullFilename))
{
bInList = true;
if (pFileData->GetSize() == lSize &&
tCurrent - pFileData->GetLastChange() >= g_pOptions->GetNzbDirFileAge())
{
bCanProcess = true;
delete pFileData;
m_FileList.erase(it);
}
else
{
pFileData->SetSize(lSize);
if (pFileData->GetSize() != lSize)
{
pFileData->SetLastChange(tCurrent);
}
}
break;
}
}
if (!bInList)
{
FileData* pFileData = new FileData(szFullFilename);
pFileData->SetSize(lSize);
pFileData->SetLastChange(tCurrent);
m_FileList.push_back(pFileData);
}
return bCanProcess;
}
/**
* Remove old files from the list of monitored files.
* Normally these files are deleted from the list when they are processed.
* However if a file was detected by function "CanProcessFile" once but wasn't
* processed later (for example if the user deleted it), it will stay in the list,
* until we remove it here.
*/
void Scanner::DropOldFiles()
{
time_t tCurrent = time(NULL);
int i = 0;
for (FileList::iterator it = m_FileList.begin(); it != m_FileList.end(); )
{
FileData* pFileData = *it;
if ((tCurrent - pFileData->GetLastChange() >=
(g_pOptions->GetNzbDirInterval() + g_pOptions->GetNzbDirFileAge()) * 2) ||
// can occur if the system clock was adjusted
tCurrent < pFileData->GetLastChange())
{
debug("Removing file %s from scan file list", pFileData->GetFilename());
delete pFileData;
m_FileList.erase(it);
it = m_FileList.begin() + i;
}
else
{
it++;
i++;
}
}
}
void Scanner::ProcessIncomingFile(const char* szDirectory, const char* szBaseFilename, const char* szFullFilename, const char* szCategory)
{
const char* szExtension = strrchr(szBaseFilename, '.');
if (!szExtension)
{
return;
}
char* szNZBCategory = strdup(szCategory);
NZBParameterList* pParameterList = new NZBParameterList;
bool bExists = true;
if (m_bNZBScript && strcasecmp(szExtension, ".nzb_processed"))
{
NZBScriptController::ExecuteScript(g_pOptions->GetNZBProcess(), szFullFilename, szDirectory, &szNZBCategory, pParameterList);
bExists = Util::FileExists(szFullFilename);
if (bExists && strcasecmp(szExtension, ".nzb"))
{
char bakname2[1024];
bool bRenameOK = Util::RenameBak(szFullFilename, "processed", false, bakname2, 1024);
if (!bRenameOK)
{
error("Could not rename file %s to %s! Errcode: %i", szFullFilename, bakname2, errno);
}
}
}
if (!strcasecmp(szExtension, ".nzb_processed"))
{
char szRenamedName[1024];
bool bRenameOK = Util::RenameBak(szFullFilename, "nzb", true, szRenamedName, 1024);
if (bRenameOK)
{
AddFileToQueue(szRenamedName, szNZBCategory, pParameterList);
}
else
{
error("Could not rename file %s to %s! Errcode: %i", szFullFilename, szRenamedName, errno);
}
}
else if (bExists && !strcasecmp(szExtension, ".nzb"))
{
AddFileToQueue(szFullFilename, szNZBCategory, pParameterList);
}
for (NZBParameterList::iterator it = pParameterList->begin(); it != pParameterList->end(); it++)
{
delete *it;
}
pParameterList->clear();
delete pParameterList;
free(szNZBCategory);
}
void Scanner::AddFileToQueue(const char* szFilename, const char* szCategory, NZBParameterList* pParameterList)
{
const char* szBasename = Util::BaseFileName(szFilename);
info("Collection %s found", szBasename);
NZBFile* pNZBFile = NZBFile::CreateFromFile(szFilename, szCategory);
if (!pNZBFile)
{
error("Could not add collection %s to queue", szBasename);
}
char bakname2[1024];
bool bRenameOK = Util::RenameBak(szFilename, pNZBFile ? "queued" : "error", false, bakname2, 1024);
if (!bRenameOK)
{
error("Could not rename file %s to %s! Errcode: %i", szFilename, bakname2, errno);
}
if (pNZBFile && bRenameOK)
{
pNZBFile->GetNZBInfo()->SetQueuedFilename(bakname2);
for (NZBParameterList::iterator it = pParameterList->begin(); it != pParameterList->end(); it++)
{
NZBParameter* pParameter = *it;
pNZBFile->GetNZBInfo()->SetParameter(pParameter->GetName(), pParameter->GetValue());
}
g_pQueueCoordinator->AddNZBFileToQueue(pNZBFile, false);
info("Collection %s added to queue", szBasename);
}
if (pNZBFile)
{
delete pNZBFile;
}
}
void Scanner::ScanNZBDir()
{
// ideally we should use mutex to access "m_bRequestedNZBDirScan",
// but it's not critical here.
m_bRequestedNZBDirScan = true;
}