Compare commits

..

1 Commits

Author SHA1 Message Date
Andrey Prygunkov
fee7819f96 version 0.3.1 2008-02-01 17:22:09 +00:00
470 changed files with 26234 additions and 161186 deletions

25
.gitattributes vendored
View File

@@ -1,25 +0,0 @@
* text=auto
# Use CRLF for certain Windows files.
*.vcproj eol=crlf
*.sln eol=crlf
*.bat eol=crlf
README-WINDOWS.txt eol=crlf
nzbget-setup.nsi eol=crlf
windows/package-info.json eol=crlf
windows/resources/resource.h eol=crlf
windows/resources/nzbget.rc eol=crlf
# Configure GitHub's language detector
lib/* linguist-vendored linguist-language=C++
webui/lib/* linguist-vendored
Makefile.in linguist-vendored
configure linguist-vendored
config.sub linguist-vendored
aclocal.m4 linguist-vendored
config.guess linguist-vendored
depcomp linguist-vendored
install-sh linguist-vendored
missing linguist-vendored
configure.ac linguist-vendored=false
Makefile.am linguist-vendored=false

74
.gitignore vendored
View File

@@ -1,74 +0,0 @@
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
# GNU Autotools
.deps/
config.h
config.h.in~
config.log
config.status
Makefile
stamp-h1
autom4te.cache/
.dirstamp
*.o-*
# Visual Studio User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
.vs/
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
*.ilk
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.vspscc
*.vssscc
*.pidb
*.svclog
*.scc
*.sln
.vscode/
# macOS
.DS_Store
# NZBGet specific
nzbget
code_revision.cpp
*.temp
*.pyc
pytest.ini
.cache

View File

@@ -1,17 +0,0 @@
# Configuration file for integration with http://lgtm.com
path_classifiers:
library:
# exclude these directories from default alerts report:
- lib
- webui/lib
extraction:
cpp:
configure:
command:
# compile with tests to activate scanning of C++ sources for tests
- ./configure --enable-tests
queries:
- exclude: js/incomplete-sanitization # this one gives false positives only and nothing useful

View File

@@ -1,67 +0,0 @@
sudo: required
dist: trusty
language: cpp
matrix:
include:
- compiler: gcc
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-5
- unrar
- p7zip-full
- par2
env:
- COMPILER=g++-5
- compiler: gcc
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.9
- unrar
- p7zip-full
- par2
env:
- COMPILER=g++-4.9
- compiler: gcc
addons:
apt:
packages:
- unrar
- p7zip-full
- par2
env:
- COMPILER=g++-4.8
- CXXFLAGS="-std=c++11 -O2 -s"
- CONFIGUREOPTS="--disable-cpp-check"
- compiler: clang
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-precise-3.6
packages:
- clang-3.6
- unrar
- p7zip-full
- par2
env:
- COMPILER=clang++-3.6
install:
- sudo pip install -U pytest
script:
- $COMPILER --version
- CXX=$COMPILER ./configure $CONFIGUREOPTS --enable-tests && make
- ./nzbget --tests
- cd tests/functional && pytest -v

4
AUTHORS Normal file
View File

@@ -0,0 +1,4 @@
nzbget:
Sven Henkel <sidddy@users.sourceforge.net> (versions 0.1.0 - ?)
Bo Cordes Petersen <placebodk@users.sourceforge.net> (versions ? - 0.2.3)
Andrei Prygounkov <hugbug@users.sourceforge.net> (versions 0.3.0 - 0.3.*)

910
ArticleDownloader.cpp Normal file
View File

@@ -0,0 +1,910 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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>
#ifdef WIN32
#include <direct.h>
#else
#include <unistd.h>
#include <sys/time.h>
#endif
#include <sys/stat.h>
#include "nzbget.h"
#include "ArticleDownloader.h"
#include "Decoder.h"
#include "Log.h"
#include "Options.h"
#include "ServerPool.h"
#include "Util.h"
extern DownloadSpeedMeter* g_pDownloadSpeedMeter;
extern Options* g_pOptions;
extern ServerPool* g_pServerPool;
const char* ArticleDownloader::m_szJobStatus[] = { "WAITING", "RUNNING", "FINISHED", "FAILED", "DECODING", "JOINING", "NOT_FOUND", "FATAL_ERROR" };
ArticleDownloader::ArticleDownloader()
{
debug("Creating ArticleDownloader");
m_szResultFilename = NULL;
m_szTempFilename = NULL;
m_szArticleFilename = NULL;
m_szInfoName = NULL;
m_szOutputFilename = NULL;
m_pConnection = NULL;
m_eStatus = adUndefined;
m_bDuplicate = false;
SetLastUpdateTimeNow();
}
ArticleDownloader::~ArticleDownloader()
{
debug("Destroying ArticleDownloader");
if (m_szTempFilename)
{
free(m_szTempFilename);
}
if (m_szArticleFilename)
{
free(m_szArticleFilename);
}
if (m_szInfoName)
{
free(m_szInfoName);
}
if (m_szOutputFilename)
{
free(m_szOutputFilename);
}
}
void ArticleDownloader::SetTempFilename(const char* v)
{
m_szTempFilename = strdup(v);
}
void ArticleDownloader::SetOutputFilename(const char* v)
{
m_szOutputFilename = strdup(v);
}
void ArticleDownloader::SetInfoName(const char * v)
{
m_szInfoName = strdup(v);
}
void ArticleDownloader::SetStatus(EStatus eStatus)
{
m_eStatus = eStatus;
Notify(NULL);
}
void ArticleDownloader::Run()
{
debug("Entering ArticleDownloader-loop");
SetStatus(adRunning);
m_szResultFilename = m_pArticleInfo->GetResultFilename();
if (g_pOptions->GetContinuePartial())
{
struct stat buffer;
bool fileExists = !stat(m_szResultFilename, &buffer);
if (fileExists)
{
// file exists from previous program's start
info("Article %s already downloaded, skipping", m_szInfoName);
SetStatus(adFinished);
FreeConnection(true);
return;
}
}
info("Downloading %s", m_szInfoName);
int retry = g_pOptions->GetRetries();
EStatus Status = adFailed;
int iMaxLevel = g_pServerPool->GetMaxLevel();
int* LevelStatus = (int*)malloc((iMaxLevel + 1) * sizeof(int));
for (int i = 0; i <= iMaxLevel; i++)
{
LevelStatus[i] = 0;
}
int level = 0;
while (!IsStopped() && (retry > 0))
{
SetLastUpdateTimeNow();
Status = adFailed;
if (!m_pConnection)
{
m_pConnection = g_pServerPool->GetConnection(level, true);
}
if (IsStopped())
{
Status = adFailed;
break;
}
if (!m_pConnection)
{
debug("m_pConnection is NULL");
error("Serious error: Connection is NULL");
}
// test connection
bool connected = m_pConnection && m_pConnection->Connect() >= 0;
if (connected && !IsStopped())
{
// Okay, we got a Connection. Now start downloading!!
Status = Download();
}
bool bAuthError = m_pConnection && m_pConnection->GetAuthError();
if (connected)
{
// freeing connection allows other threads to start.
// we doing this only if the problem was with article or group.
// if the problem occurs by Connect() we do not free the connection,
// to prevent starting of thousands of threads (cause each of them
// will also free it's connection after the same connect-error).
FreeConnection(Status == adFinished);
}
if ((Status == adFailed || (Status == adCrcError && g_pOptions->GetRetryOnCrcError())) &&
((retry > 1) || !connected || bAuthError) && !IsStopped())
{
info("Waiting %i sec to retry", g_pOptions->GetRetryInterval());
int msec = 0;
while (!IsStopped() && (msec < g_pOptions->GetRetryInterval() * 1000))
{
usleep(100 * 1000);
msec += 100;
}
}
if (IsStopped())
{
Status = adFailed;
break;
}
if ((Status == adFinished) || (Status == adFatalError) ||
(Status == adCrcError && !g_pOptions->GetRetryOnCrcError()))
{
break;
}
LevelStatus[level] = Status;
bool bAllLevelNotFound = true;
for (int lev = 0; lev <= iMaxLevel; lev++)
{
if (LevelStatus[lev] != adNotFound)
{
bAllLevelNotFound = false;
break;
}
}
if (bAllLevelNotFound)
{
if (iMaxLevel > 0)
{
warn("Article %s @ all servers failed: Article not found", m_szInfoName);
}
break;
}
// do not count connect-errors, only article- and group-errors
if (connected && !bAuthError)
{
level++;
if (level > iMaxLevel)
{
level = 0;
}
retry--;
}
}
FreeConnection(Status == adFinished);
free(LevelStatus);
if (m_bDuplicate)
{
Status = adFinished;
}
if (Status != adFinished)
{
Status = adFailed;
}
if (Status == adFailed)
{
if (IsStopped())
{
info("Download %s cancelled", m_szInfoName);
}
else
{
warn("Download %s failed", m_szInfoName);
}
}
SetStatus(Status);
debug("Exiting ArticleDownloader-loop");
}
ArticleDownloader::EStatus ArticleDownloader::Download()
{
// at first, change group
bool grpchanged = false;
for (FileInfo::Groups::iterator it = m_pFileInfo->GetGroups()->begin(); it != m_pFileInfo->GetGroups()->end(); it++)
{
grpchanged = m_pConnection->JoinGroup(*it);
if (grpchanged)
{
break;
}
}
if (!grpchanged)
{
if (!m_pConnection->GetAuthError() && !IsStopped())
{
warn("Article %s @ %s failed: Could not join group", m_szInfoName, m_pConnection->GetServer()->GetHost());
}
return adFailed;
}
// now, let's begin!
char tmp[1024];
snprintf(tmp, 1024, "ARTICLE %s\r\n", m_pArticleInfo->GetMessageID());
tmp[1024-1] = '\0';
char* answer = NULL;
for (int retry = 3; retry > 0; retry--)
{
answer = m_pConnection->Request(tmp);
if (answer && !strncmp(answer, "2", 1))
{
break;
}
}
if (!answer)
{
if (!m_pConnection->GetAuthError() && !IsStopped())
{
warn("Article %s @ %s failed: Connection closed by remote host", m_szInfoName, m_pConnection->GetServer()->GetHost());
}
return adFailed;
}
if (strncmp(answer, "2", 1))
{
warn("Article %s @ %s failed: %s", m_szInfoName, m_pConnection->GetServer()->GetHost(), answer);
return (!strncmp(answer, "41", 2) || !strncmp(answer, "42", 2)) ? adNotFound : adFailed;
}
// positive answer!
if (g_pOptions->GetDecoder() == Options::dcYenc)
{
m_YDecoder.Clear();
m_YDecoder.SetAutoSeek(g_pOptions->GetDirectWrite());
m_YDecoder.SetCrcCheck(g_pOptions->GetCrcCheck());
}
m_pOutFile = NULL;
EStatus Status = adRunning;
bool bBody = false;
const int LineBufSize = 1024*10;
char* szLineBuf = (char*)malloc(LineBufSize);
while (!IsStopped())
{
SetLastUpdateTimeNow();
// Throttle the bandwidth
while (!IsStopped() && (g_pOptions->GetDownloadRate() > 0.0f) &&
(g_pDownloadSpeedMeter->CalcCurrentDownloadSpeed() > g_pOptions->GetDownloadRate()))
{
SetLastUpdateTimeNow();
usleep(200 * 1000);
}
int iLen = 0;
char* line = m_pConnection->ReadLine(szLineBuf, LineBufSize, &iLen);
g_pDownloadSpeedMeter->AddSpeedReading(iLen);
// Have we encountered a timeout?
if (!line)
{
warn("Article %s @ %s failed: Unexpected end of article", m_szInfoName, m_pConnection->GetServer()->GetHost());
Status = adFailed;
break;
}
//detect end of article
if ((!strcmp(line, ".\r\n")) || (!strcmp(line, ".\n")))
{
break;
}
//detect lines starting with "." (marked as "..")
if (!strncmp(line, "..", 2))
{
line++;
}
// check id of returned article
if (!bBody)
{
if ((!strcmp(line, "\r\n")) || (!strcmp(line, "\n")))
{
bBody = true;
}
else if (!strncmp(line, "Message-ID: ", 12))
{
char* p = line + 12;
if (strncmp(p, m_pArticleInfo->GetMessageID(), strlen(m_pArticleInfo->GetMessageID())))
{
if (char* e = strrchr(p, '\r')) *e = '\0'; // remove trailing CR-character
warn("Article %s @ %s failed: Wrong message-id, expected %s, returned %s", m_szInfoName, m_pConnection->GetServer()->GetHost(), m_pArticleInfo->GetMessageID(), p);
Status = adFailed;
break;
}
}
}
if (!Write(line, iLen))
{
Status = adFatalError;
break;
}
}
free(szLineBuf);
if (m_pOutFile)
{
fclose(m_pOutFile);
}
if (IsStopped())
{
remove(m_szTempFilename);
return adFailed;
}
if (Status == adFailed)
{
remove(m_szTempFilename);
return adFailed;
}
if (Status == adRunning)
{
FreeConnection(true);
return Decode();
}
else
{
return Status;
}
}
bool ArticleDownloader::Write(char* szLine, int iLen)
{
if (!m_pOutFile && !PrepareFile(szLine))
{
return false;
}
if (g_pOptions->GetDecoder() == Options::dcYenc)
{
return m_YDecoder.Write(szLine, m_pOutFile);
}
else
{
return fwrite(szLine, 1, iLen, m_pOutFile) > 0;
}
}
bool ArticleDownloader::PrepareFile(char* szLine)
{
bool bOpen = false;
// prepare file for writing
if (g_pOptions->GetDecoder() == Options::dcYenc)
{
if (!strncmp(szLine, "=ybegin part=", 13))
{
if (g_pOptions->GetDupeCheck())
{
m_pFileInfo->LockOutputFile();
if (!m_pFileInfo->GetOutputInitialized())
{
char* pb = strstr(szLine, "name=");
if (pb)
{
pb += 5; //=strlen("name=")
char* pe;
for (pe = pb; *pe != '\0' && *pe != '\n' && *pe != '\r'; pe++) ;
if (!m_szArticleFilename)
{
m_szArticleFilename = (char*)malloc(pe - pb + 1);
strncpy(m_szArticleFilename, pb, pe - pb);
m_szArticleFilename[pe - pb] = '\0';
}
if (m_pFileInfo->IsDupe(m_szArticleFilename))
{
m_bDuplicate = true;
return false;
}
}
}
if (!g_pOptions->GetDirectWrite())
{
m_pFileInfo->SetOutputInitialized(true);
}
m_pFileInfo->UnlockOutputFile();
}
if (g_pOptions->GetDirectWrite())
{
char* pb = strstr(szLine, "size=");
if (pb)
{
m_pFileInfo->LockOutputFile();
if (!m_pFileInfo->GetOutputInitialized())
{
pb += 5; //=strlen("size=")
long iArticleFilesize = atol(pb);
if (!SetFileSize(m_szOutputFilename, iArticleFilesize))
{
error("Could not create file %s!", m_szOutputFilename);
return false;
}
m_pFileInfo->SetOutputInitialized(true);
}
m_pFileInfo->UnlockOutputFile();
bOpen = true;
}
}
else
{
bOpen = true;
}
}
}
else
{
bOpen = true;
}
if (bOpen)
{
const char* szFilename = g_pOptions->GetDirectWrite() ? m_szOutputFilename : m_szTempFilename;
m_pOutFile = fopen(szFilename, g_pOptions->GetDirectWrite() ? "r+" : "w");
if (!m_pOutFile)
{
error("Could not %s file %s", g_pOptions->GetDirectWrite() ? "open" : "create", szFilename);
return false;
}
if (g_pOptions->GetWriteBufferSize() == -1)
{
setvbuf(m_pOutFile, (char *)NULL, _IOFBF, m_pArticleInfo->GetSize());
}
else if (g_pOptions->GetWriteBufferSize() > 0)
{
setvbuf(m_pOutFile, (char *)NULL, _IOFBF, g_pOptions->GetWriteBufferSize());
}
}
return true;
}
ArticleDownloader::EStatus ArticleDownloader::Decode()
{
if ((g_pOptions->GetDecoder() == Options::dcUulib) ||
(g_pOptions->GetDecoder() == Options::dcYenc))
{
SetStatus(adDecoding);
char tmpdestfile[1024];
char* szDecoderTempFilename = NULL;
Decoder* pDecoder = NULL;
if (g_pOptions->GetDecoder() == Options::dcYenc)
{
pDecoder = &m_YDecoder;
szDecoderTempFilename = m_szTempFilename;
}
else if (g_pOptions->GetDecoder() == Options::dcUulib)
{
pDecoder = new UULibDecoder();
pDecoder->SetSrcFilename(m_szTempFilename);
snprintf(tmpdestfile, 1024, "%s.dec", m_szResultFilename);
tmpdestfile[1024-1] = '\0';
szDecoderTempFilename = tmpdestfile;
pDecoder->SetDestFilename(szDecoderTempFilename);
}
bool bOK = pDecoder->Execute();
if (!g_pOptions->GetDirectWrite())
{
if (bOK)
{
rename(szDecoderTempFilename, m_szResultFilename);
}
else if (g_pOptions->GetDecoder() == Options::dcUulib)
{
remove(szDecoderTempFilename);
}
}
if (!m_szArticleFilename && pDecoder->GetArticleFilename())
{
m_szArticleFilename = strdup(pDecoder->GetArticleFilename());
}
remove(m_szTempFilename);
bool bCrcError = pDecoder->GetCrcError();
if (pDecoder != &m_YDecoder)
{
delete pDecoder;
}
if (bOK)
{
info("Successfully downloaded %s", m_szInfoName);
if (g_pOptions->GetDirectWrite() && g_pOptions->GetContinuePartial())
{
// create empty flag-file to indicate that the artcile was downloaded
FILE* flagfile = fopen(m_szResultFilename, "w");
if (!flagfile)
{
error("Could not create file %s", m_szResultFilename);
// this error can be ignored
}
fclose(flagfile);
}
return adFinished;
}
else
{
remove(m_szResultFilename);
if (bCrcError)
{
warn("Decoding %s failed: CRC-Error", m_szInfoName);
return adCrcError;
}
else
{
warn("Decoding %s failed", m_szInfoName);
return adFailed;
}
}
}
else if (g_pOptions->GetDecoder() == Options::dcNone)
{
// rawmode
rename(m_szTempFilename, m_szResultFilename);
info("Article %s successfully downloaded", m_szInfoName);
return adFinished;
}
else
{
// should not occur
error("Internal error: Decoding %s failed", m_szInfoName);
return adFatalError;
}
}
void ArticleDownloader::LogDebugInfo()
{
char szTime[50];
#ifdef HAVE_CTIME_R_3
ctime_r(&m_tLastUpdateTime, szTime, 50);
#else
ctime_r(&m_tLastUpdateTime, szTime);
#endif
debug(" Download: status=%s, LastUpdateTime=%s, filename=%s", GetStatusText(), szTime, BaseFileName(GetTempFilename()));
}
void ArticleDownloader::Stop()
{
debug("Trying to stop ArticleDownloader");
Thread::Stop();
m_mutexConnection.Lock();
if (m_pConnection)
{
m_pConnection->Cancel();
}
m_mutexConnection.Unlock();
debug("ArticleDownloader stopped successfully");
}
bool ArticleDownloader::Terminate()
{
NNTPConnection* pConnection = m_pConnection;
bool terminated = Kill();
if (terminated && pConnection)
{
debug("Terminating connection");
pConnection->Cancel();
pConnection->Disconnect();
g_pServerPool->FreeConnection(pConnection, true);
}
return terminated;
}
void ArticleDownloader::FreeConnection(bool bKeepConnected)
{
if (m_pConnection)
{
debug("Releasing connection");
m_mutexConnection.Lock();
if (!bKeepConnected || m_pConnection->GetStatus() == Connection::csCancelled)
{
m_pConnection->Disconnect();
}
g_pServerPool->FreeConnection(m_pConnection, true);
m_pConnection = NULL;
m_mutexConnection.Unlock();
}
}
void ArticleDownloader::CompleteFileParts()
{
debug("Completing file parts");
debug("ArticleFilename: %s", m_pFileInfo->GetFilename());
SetStatus(adJoining);
char szNZBNiceName[1024];
m_pFileInfo->GetNiceNZBName(szNZBNiceName, 1024);
char InfoFilename[1024];
snprintf(InfoFilename, 1024, "%s%c%s", szNZBNiceName, (int)PATH_SEPARATOR, m_pFileInfo->GetFilename());
InfoFilename[1024-1] = '\0';
if (g_pOptions->GetDecoder() == Options::dcNone)
{
info("Moving articles for %s", InfoFilename);
}
else if (g_pOptions->GetDirectWrite())
{
info("Checking articles for %s", InfoFilename);
}
else
{
info("Joining articles for %s", InfoFilename);
}
char ofn[1024];
snprintf(ofn, 1024, "%s%c%s", m_pFileInfo->GetDestDir(), (int)PATH_SEPARATOR, m_pFileInfo->GetFilename());
ofn[1024-1] = '\0';
// Ensure the DstDir is created
mkdir(m_pFileInfo->GetDestDir(), S_DIRMODE);
// prevent overwriting existing files
struct stat statbuf;
int dupcount = 0;
while (!stat(ofn, &statbuf))
{
dupcount++;
snprintf(ofn, 1024, "%s%c%s_duplicate%d", m_pFileInfo->GetDestDir(), (int)PATH_SEPARATOR, m_pFileInfo->GetFilename(), dupcount);
ofn[1024-1] = '\0';
}
FILE* outfile = NULL;
char tmpdestfile[1024];
snprintf(tmpdestfile, 1024, "%s.tmp", ofn);
tmpdestfile[1024-1] = '\0';
if (((g_pOptions->GetDecoder() == Options::dcUulib) ||
(g_pOptions->GetDecoder() == Options::dcYenc)) &&
!g_pOptions->GetDirectWrite())
{
remove(tmpdestfile);
outfile = fopen(tmpdestfile, "w+");
if (!outfile)
{
error("Could not create file %s!", tmpdestfile);
SetStatus(adFinished);
return;
}
if (g_pOptions->GetWriteBufferSize() == -1 && (*m_pFileInfo->GetArticles())[0])
{
setvbuf(outfile, (char *)NULL, _IOFBF, (*m_pFileInfo->GetArticles())[0]->GetSize());
}
else if (g_pOptions->GetWriteBufferSize() > 0)
{
setvbuf(outfile, (char *)NULL, _IOFBF, g_pOptions->GetWriteBufferSize());
}
}
else if (g_pOptions->GetDecoder() == Options::dcNone)
{
remove(tmpdestfile);
mkdir(ofn, S_DIRMODE);
}
bool complete = true;
int iBrokenCount = 0;
static const int BUFFER_SIZE = 1024 * 50;
char* buffer = NULL;
if (((g_pOptions->GetDecoder() == Options::dcUulib) ||
(g_pOptions->GetDecoder() == Options::dcYenc)) &&
!g_pOptions->GetDirectWrite())
{
buffer = (char*)malloc(BUFFER_SIZE);
}
for (FileInfo::Articles::iterator it = m_pFileInfo->GetArticles()->begin(); it != m_pFileInfo->GetArticles()->end(); it++)
{
ArticleInfo* pa = *it;
if (pa->GetStatus() != ArticleInfo::aiFinished)
{
iBrokenCount++;
complete = false;
}
else if (((g_pOptions->GetDecoder() == Options::dcUulib) ||
(g_pOptions->GetDecoder() == Options::dcYenc)) &&
!g_pOptions->GetDirectWrite())
{
FILE* infile;
const char* fn = pa->GetResultFilename();
infile = fopen(fn, "r");
if (infile)
{
int cnt = BUFFER_SIZE;
while (cnt == BUFFER_SIZE)
{
cnt = (int)fread(buffer, 1, BUFFER_SIZE, infile);
fwrite(buffer, 1, cnt, outfile);
SetLastUpdateTimeNow();
}
fclose(infile);
}
else
{
complete = false;
iBrokenCount++;
info("Could not find file %s. Status is broken", fn);
}
}
else if (g_pOptions->GetDecoder() == Options::dcNone)
{
const char* fn = pa->GetResultFilename();
char dstFileName[1024];
snprintf(dstFileName, 1024, "%s%c%03i", ofn, (int)PATH_SEPARATOR, pa->GetPartNumber());
dstFileName[1024-1] = '\0';
rename(fn, dstFileName);
}
}
if (buffer)
{
free(buffer);
}
if (outfile)
{
fclose(outfile);
rename(tmpdestfile, ofn);
}
if (g_pOptions->GetDirectWrite())
{
rename(m_szOutputFilename, ofn);
}
if (!g_pOptions->GetDirectWrite() || g_pOptions->GetContinuePartial())
{
for (FileInfo::Articles::iterator it = m_pFileInfo->GetArticles()->begin(); it != m_pFileInfo->GetArticles()->end(); it++)
{
ArticleInfo* pa = *it;
remove(pa->GetResultFilename());
}
}
if (complete)
{
info("Successfully downloaded %s", InfoFilename);
}
else
{
warn("%i of %i article downloads failed for \"%s\"", iBrokenCount, m_pFileInfo->GetArticles()->size(), InfoFilename);
if (g_pOptions->GetRenameBroken())
{
char brokenfn[1024];
snprintf(brokenfn, 1024, "%s_broken", ofn);
brokenfn[1024-1] = '\0';
bool OK = rename(ofn, brokenfn) == 0;
if (OK)
{
info("Renaming broken file from %s to %s", ofn, brokenfn);
}
else
{
warn("Renaming broken file from %s to %s failed", ofn, brokenfn);
}
}
else
{
info("Not renaming broken file %s", ofn);
}
if (g_pOptions->GetCreateBrokenLog())
{
char szBrokenLogName[1024];
snprintf(szBrokenLogName, 1024, "%s%c_brokenlog.txt", m_pFileInfo->GetDestDir(), (int)PATH_SEPARATOR);
szBrokenLogName[1024-1] = '\0';
FILE* file = fopen(szBrokenLogName, "a");
fprintf(file, "%s (%i/%i)\n", m_pFileInfo->GetFilename(), m_pFileInfo->GetArticles()->size() - iBrokenCount, m_pFileInfo->GetArticles()->size());
fclose(file);
}
warn("%s is incomplete!", InfoFilename);
}
SetStatus(adFinished);
}

116
ArticleDownloader.h Normal file
View File

@@ -0,0 +1,116 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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$
*
*/
#ifndef ARTICLEDOWNLOADER_H
#define ARTICLEDOWNLOADER_H
#include <time.h>
#ifdef WIN32
#include <sys/timeb.h>
#endif
#include "Observer.h"
#include "DownloadInfo.h"
#include "Thread.h"
#include "NNTPConnection.h"
#include "Decoder.h"
class ArticleDownloader : public Thread, public Subject
{
public:
enum EStatus
{
adUndefined,
adRunning,
adFinished,
adFailed,
adDecodeError,
adCrcError,
adDecoding,
adJoining,
adNotFound,
adFatalError
};
private:
FileInfo* m_pFileInfo;
ArticleInfo* m_pArticleInfo;
NNTPConnection* m_pConnection;
EStatus m_eStatus;
Mutex m_mutexConnection;
const char* m_szResultFilename;
char* m_szTempFilename;
char* m_szArticleFilename;
char* m_szInfoName;
char* m_szOutputFilename;
time_t m_tLastUpdateTime;
static const char* m_szJobStatus[];
YDecoder m_YDecoder;
FILE* m_pOutFile;
bool m_bDuplicate;
EStatus Download();
bool Write(char* szLine, int iLen);
bool PrepareFile(char* szLine);
EStatus Decode();
void FreeConnection(bool bKeepConnected);
public:
ArticleDownloader();
~ArticleDownloader();
void SetFileInfo(FileInfo* pFileInfo) { m_pFileInfo = pFileInfo; }
FileInfo* GetFileInfo() { return m_pFileInfo; }
void SetArticleInfo(ArticleInfo* pArticleInfo) { m_pArticleInfo = pArticleInfo; }
ArticleInfo* GetArticleInfo() { return m_pArticleInfo; }
void SetStatus(EStatus eStatus);
EStatus GetStatus() { return m_eStatus; }
const char* GetStatusText() { return m_szJobStatus[m_eStatus]; }
virtual void Run();
virtual void Stop();
bool Terminate();
time_t GetLastUpdateTime() { return m_tLastUpdateTime; }
void SetLastUpdateTimeNow() { m_tLastUpdateTime = ::time(NULL); }
const char* GetTempFilename() { return m_szTempFilename; }
void SetTempFilename(const char* v);
void SetOutputFilename(const char* v);
const char* GetArticleFilename() { return m_szArticleFilename; }
void SetInfoName(const char* v);
const char* GetInfoName() { return m_szInfoName; }
void CompleteFileParts();
void SetConnection(NNTPConnection* pConnection) { m_pConnection = pConnection; }
void LogDebugInfo();
};
class DownloadSpeedMeter
{
public:
virtual ~DownloadSpeedMeter() {};
virtual float CalcCurrentDownloadSpeed() = 0;
virtual void AddSpeedReading(int iBytes) = 0;
};
#endif

41
COPYING
View File

@@ -1,12 +1,12 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
@@ -15,7 +15,7 @@ software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
@@ -55,8 +55,8 @@ patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
@@ -110,7 +110,7 @@ above, provided that you also meet all of these conditions:
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
@@ -168,7 +168,7 @@ access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
@@ -225,7 +225,7 @@ impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
@@ -255,7 +255,7 @@ make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
@@ -277,9 +277,9 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
@@ -303,9 +303,10 @@ the "copyright" line and a pointer to where the full notice is found.
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.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
@@ -335,5 +336,5 @@ necessary. Here is a sample; alter the names:
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.

2800
ChangeLog
View File

File diff suppressed because it is too large Load Diff

184
ColoredFrontend.cpp Normal file
View File

@@ -0,0 +1,184 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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 <stdio.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include "nzbget.h"
#include "ColoredFrontend.h"
ColoredFrontend::ColoredFrontend()
{
m_bSummary = true;
m_bNeedGoBack = false;
#ifdef WIN32
m_hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
#endif
}
void ColoredFrontend::BeforePrint()
{
if (m_bNeedGoBack)
{
// go back one line
#ifdef WIN32
CONSOLE_SCREEN_BUFFER_INFO BufInfo;
GetConsoleScreenBufferInfo(m_hConsole, &BufInfo);
BufInfo.dwCursorPosition.Y--;
SetConsoleCursorPosition(m_hConsole, BufInfo.dwCursorPosition);
#else
printf("\r\033[1A");
#endif
m_bNeedGoBack = false;
}
}
void ColoredFrontend::PrintStatus()
{
char tmp[1024];
char timeString[100];
timeString[0] = '\0';
float fCurrentDownloadSpeed = m_bStandBy ? 0 : m_fCurrentDownloadSpeed;
if (fCurrentDownloadSpeed > 0.0 && !m_bPause)
{
long long remain_sec = m_lRemainingSize / ((long long int)(fCurrentDownloadSpeed * 1024));
int h = remain_sec / 3600;
int m = (remain_sec % 3600) / 60;
int s = remain_sec % 60;
sprintf(timeString, " (~ %.2d:%.2d:%.2d)", h, m, s);
}
char szDownloadLimit[128];
if (m_fDownloadLimit > 0.0f)
{
sprintf(szDownloadLimit, ", Limit %.0f KB/S", m_fDownloadLimit);
}
else
{
szDownloadLimit[0] = 0;
}
char szParStatus[128];
if (m_iParJobCount > 0)
{
sprintf(szParStatus, ", %i par", m_iParJobCount);
}
else
{
szParStatus[0] = 0;
}
#ifdef WIN32
char* szControlSeq = "";
#else
printf("\033[s");
char* szControlSeq = "\033[K";
#endif
snprintf(tmp, 1024, " %d threads, %.0f KB/s, %.2f MB remaining%s%s%s%s%s\n",
m_iThreadCount, fCurrentDownloadSpeed, (float)(m_lRemainingSize / 1024.0 / 1024.0),
timeString, szParStatus, m_bPause ? (m_bStandBy ? ", Paused" : ", Pausing") : "", szDownloadLimit, szControlSeq);
tmp[1024-1] = '\0';
printf("%s", tmp);
m_bNeedGoBack = true;
}
void ColoredFrontend::PrintMessage(Message * pMessage)
{
#ifdef WIN32
switch (pMessage->GetKind())
{
case Message::mkDebug:
SetConsoleTextAttribute(m_hConsole, 8);
printf("[DEBUG]");
break;
case Message::mkError:
SetConsoleTextAttribute(m_hConsole, 4);
printf("[ERROR]");
break;
case Message::mkWarning:
SetConsoleTextAttribute(m_hConsole, 5);
printf("[WARNING]");
break;
case Message::mkInfo:
SetConsoleTextAttribute(m_hConsole, 2);
printf("[INFO]");
break;
}
SetConsoleTextAttribute(m_hConsole, 7);
char* msg = strdup(pMessage->GetText());
CharToOem(msg, msg);
printf(" %s\n", msg);
free(msg);
#else
const char* msg = pMessage->GetText();
switch (pMessage->GetKind())
{
case Message::mkDebug:
printf("[DEBUG] %s\033[K\n", msg);
break;
case Message::mkError:
printf("\033[31m[ERROR]\033[39m %s\033[K\n", msg);
break;
case Message::mkWarning:
printf("\033[35m[WARNING]\033[39m %s\033[K\n", msg);
break;
case Message::mkInfo:
printf("\033[32m[INFO]\033[39m %s\033[K\n", msg);
break;
}
#endif
}
void ColoredFrontend::PrintSkip()
{
#ifdef WIN32
printf(".....\n");
#else
printf(".....\033[K\n");
#endif
}
void ColoredFrontend::BeforeExit()
{
if (IsRemoteMode())
{
printf("\n");
}
}

View File

@@ -1,8 +1,8 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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
@@ -15,7 +15,12 @@
* 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/>.
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Revision$
* $Date$
*
*/
@@ -27,22 +32,22 @@
class ColoredFrontend : public LoggableFrontend
{
public:
ColoredFrontend();
protected:
virtual void BeforePrint();
virtual void PrintMessage(Message& message);
virtual void PrintStatus();
virtual void PrintSkip();
virtual void BeforeExit();
private:
bool m_needGoBack = false;
bool m_bNeedGoBack;
#ifdef WIN32
HANDLE m_console;
HANDLE m_hConsole;
#endif
protected:
virtual void BeforePrint();
virtual void PrintMessage(Message* pMessage);
virtual void PrintStatus();
virtual void PrintSkip();
virtual void BeforeExit();
public:
ColoredFrontend();
};
#endif

542
Connection.cpp Normal file
View File

@@ -0,0 +1,542 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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>
#ifndef WIN32
#include <netdb.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#endif
#include "nzbget.h"
#include "Connection.h"
#include "Log.h"
static const int CONNECTION_READBUFFER_SIZE = 1024;
void Connection::Init()
{
debug("Intiializing global connection data");
#ifdef WIN32
WSADATA wsaData;
int err = WSAStartup(MAKEWORD(2, 0), &wsaData);
if (err != 0)
{
error("Could not initialize socket library");
return;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE( wsaData.wVersion ) != 0)
{
error("Could not initialize socket library");
WSACleanup();
return;
}
#endif
}
void Connection::Final()
{
debug("Finalizing global connection data");
#ifdef WIN32
WSACleanup();
#endif
}
Connection::Connection(NetAddress* pNetAddress)
{
debug("Creating Connection");
m_pNetAddress = pNetAddress;
m_eStatus = csDisconnected;
m_iSocket = INVALID_SOCKET;
m_iBufAvail = 0;
m_iTimeout = 60;
m_bSuppressErrors = true;
m_szReadBuf = (char*)malloc(CONNECTION_READBUFFER_SIZE + 1);
}
Connection::~Connection()
{
debug("Destroying Connection");
if (m_eStatus == csConnected)
{
Disconnect();
}
free(m_szReadBuf);
}
int Connection::Connect()
{
debug("Connecting");
if (m_eStatus == csConnected)
return 0;
int iRes = DoConnect();
if (iRes >= 0)
m_eStatus = csConnected;
else
Connection::DoDisconnect();
return iRes;
}
int Connection::Disconnect()
{
debug("Disconnecting");
if (m_eStatus == csDisconnected)
return 0;
int iRes = DoDisconnect();
m_eStatus = csDisconnected;
m_iSocket = INVALID_SOCKET;
m_iBufAvail = 0;
return iRes;
}
int Connection::Bind()
{
debug("Binding");
if (m_eStatus == csListening)
{
return 0;
}
int iRes = DoBind();
if (iRes == 0)
{
m_eStatus = csListening;
}
return iRes;
}
int Connection::WriteLine(char* line)
{
//debug("Connection::write(char* line)");
if (m_eStatus != csConnected)
{
return -1;
}
int iRes = DoWriteLine(line);
return iRes;
}
int Connection::Send(char* pBuffer, int iSize)
{
debug("Sending data");
if (m_eStatus != csConnected)
{
return -1;
}
int iRes = send(m_iSocket, pBuffer, iSize, 0);
return iRes;
}
char* Connection::ReadLine(char* pBuffer, int iSize, int* pBytesRead)
{
if (m_eStatus != csConnected)
{
return NULL;
}
char* res = DoReadLine(pBuffer, iSize, pBytesRead);
return res;
}
SOCKET Connection::Accept()
{
debug("Accepting connection");
if (m_eStatus != csListening)
{
return INVALID_SOCKET;
}
SOCKET iRes = DoAccept();
return iRes;
}
int Connection::Recv(char* pBuffer, int iSize)
{
debug("Receiving data");
memset(pBuffer, 0, iSize);
int iReceived = recv(m_iSocket, pBuffer, iSize, 0);
if (iReceived < 0)
{
ReportError("Could not receive data on socket", NULL, 0);
}
return iReceived;
}
bool Connection::RecvAll(char * pBuffer, int iSize)
{
debug("Receiving data (full buffer)");
memset(pBuffer, 0, iSize);
char* pBufPtr = (char*)pBuffer;
int NeedBytes = iSize;
// Read from the socket until nothing remains
while (NeedBytes > 0)
{
int iReceived = recv(m_iSocket, pBufPtr, NeedBytes, 0);
// Did the recv succeed?
if (iReceived <= 0)
{
ReportError("Could not receive data on socket", NULL, 0);
return false;
}
pBufPtr += iReceived;
NeedBytes -= iReceived;
}
return true;
}
int Connection::DoConnect()
{
debug("Do connecting");
struct sockaddr_in sSocketAddress;
memset(&sSocketAddress, '\0', sizeof(sSocketAddress));
sSocketAddress.sin_family = AF_INET;
sSocketAddress.sin_port = htons(m_pNetAddress->GetPort());
sSocketAddress.sin_addr.s_addr = ResolveHostAddr(m_pNetAddress->GetHost());
if (sSocketAddress.sin_addr.s_addr == (unsigned int)-1)
{
return -1;
}
m_iSocket = socket(PF_INET, SOCK_STREAM, 0);
if (m_iSocket == INVALID_SOCKET)
{
ReportError("Socket creation failed for %s!", m_pNetAddress->GetHost(), 0);
return -1;
}
int res = connect(m_iSocket , (struct sockaddr *) & sSocketAddress, sizeof(sSocketAddress));
if (res < 0)
{
ReportError("Connection to %s failed!", m_pNetAddress->GetHost(), 0);
return -1;
}
#ifdef WIN32
int MSecVal = m_iTimeout * 1000;
int err = setsockopt(m_iSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&MSecVal, sizeof(MSecVal));
#else
struct timeval TimeVal;
TimeVal.tv_sec = m_iTimeout;
TimeVal.tv_usec = 0;
int err = setsockopt(m_iSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&TimeVal, sizeof(TimeVal));
#endif
if (err != 0)
{
ReportError("setsockopt failed", NULL, 0);
}
return 0;
}
unsigned int Connection::ResolveHostAddr(const char* szHost)
{
unsigned int uaddr = inet_addr(szHost);
if (uaddr == (unsigned int)-1)
{
struct hostent* hinfo;
bool err = false;
int h_errnop = 0;
#ifdef WIN32
hinfo = gethostbyname(szHost);
err = hinfo == NULL;
h_errnop = WSAGetLastError();
#else
struct hostent hinfobuf;
static const int strbuflen = 1024;
char* strbuf = (char*)malloc(strbuflen);
#ifdef HAVE_GETHOSTBYNAME_R_6
err = gethostbyname_r(szHost, &hinfobuf, strbuf, strbuflen, &hinfo, &h_errnop);
err = err || (hinfo == NULL); // error on null hinfo (means 'no entry')
#else
hinfo = gethostbyname_r(szHost, &hinfobuf, strbuf, strbuflen, &h_errnop);
err = hinfo == NULL;
#endif
#endif
if (err)
{
ReportError("Could not resolve hostname %s", szHost, h_errnop);
#ifndef WIN32
free(strbuf);
#endif
return (unsigned int)-1;
}
memcpy(&uaddr, hinfo->h_addr_list[0], sizeof(uaddr));
#ifndef WIN32
free(strbuf);
#endif
}
return uaddr;
}
int Connection::DoDisconnect()
{
debug("Do disconnecting");
if (m_iSocket > 0)
{
closesocket(m_iSocket);
m_iSocket = INVALID_SOCKET;
}
m_eStatus = csDisconnected;
return 0;
}
int Connection::DoWriteLine(char* szText)
{
//debug("Connection::doWrite()");
return send(m_iSocket, szText, strlen(szText), 0);
}
char* Connection::DoReadLine(char* pBuffer, int iSize, int* pBytesRead)
{
//debug( "Connection::DoReadLine()" );
char* pBufPtr = pBuffer;
iSize--; // for trailing '0'
int iBytesRead = 0;
int iBufAvail = m_iBufAvail; // local variable is faster
char* szBufPtr = m_szBufPtr; // local variable is faster
while (iSize)
{
if (!iBufAvail)
{
iBufAvail = recv(m_iSocket, m_szReadBuf, CONNECTION_READBUFFER_SIZE, 0);
if (iBufAvail < 0)
{
ReportError("Could not receive data on socket", NULL, 0);
break;
}
else if (iBufAvail == 0)
{
break;
}
szBufPtr = m_szReadBuf;
m_szReadBuf[iBufAvail] = '\0';
}
int len = 0;
char* p = (char*)memchr(szBufPtr, '\n', iBufAvail);
if (p)
{
len = p - szBufPtr + 1;
}
else
{
len = iBufAvail;
}
if (len > iSize)
{
len = iSize;
}
memcpy(pBufPtr, szBufPtr, len);
pBufPtr += len;
szBufPtr += len;
iBufAvail -= len;
iBytesRead += len;
iSize -= len;
if (p)
{
break;
}
}
*pBufPtr = '\0';
m_iBufAvail = iBufAvail > 0 ? iBufAvail : 0; // copy back to member
m_szBufPtr = szBufPtr; // copy back to member
if (pBytesRead)
{
*pBytesRead = iBytesRead;
}
if (pBufPtr == pBuffer)
{
return NULL;
}
return pBuffer;
}
int Connection::DoBind()
{
debug("Do binding");
m_iSocket = socket(PF_INET, SOCK_STREAM, 0);
if (m_iSocket == INVALID_SOCKET)
{
ReportError("Socket creation failed for %s!", m_pNetAddress->GetHost(), 0);
return -1;
}
struct sockaddr_in sSocketAddress;
memset(&sSocketAddress, '\0', sizeof(sSocketAddress));
sSocketAddress.sin_family = AF_INET;
if (!m_pNetAddress->GetHost() || strlen(m_pNetAddress->GetHost()) == 0)
{
sSocketAddress.sin_addr.s_addr = htonl(INADDR_ANY);
}
else
{
sSocketAddress.sin_addr.s_addr = ResolveHostAddr(m_pNetAddress->GetHost());
if (sSocketAddress.sin_addr.s_addr == (unsigned int)-1)
{
return -1;
}
}
sSocketAddress.sin_port = htons(m_pNetAddress->GetPort());
int opt = 1;
setsockopt(m_iSocket, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt));
if (bind(m_iSocket, (struct sockaddr *) &sSocketAddress, sizeof(sSocketAddress)) < 0)
{
ReportError("Binding socket failed for %s", m_pNetAddress->GetHost(), 0);
return -1;
}
if (listen(m_iSocket, 10) < 0)
{
ReportError("Listen on socket failed for %s", m_pNetAddress->GetHost(), 0);
return -1;
}
return 0;
}
SOCKET Connection::DoAccept()
{
struct sockaddr_in ClientAddress;
socklen_t SockLen;
SockLen = sizeof(ClientAddress);
SOCKET iSocket = accept(GetSocket(), (struct sockaddr *) & ClientAddress, &SockLen);
if (iSocket == INVALID_SOCKET && m_eStatus != csCancelled)
{
ReportError("Could not accept connection", NULL, 0);
}
return iSocket;
}
void Connection::Cancel()
{
debug("Cancelling connection");
if (m_iSocket != INVALID_SOCKET)
{
m_eStatus = csCancelled;
int r = shutdown(m_iSocket, SHUT_RDWR);
if (r == -1)
{
ReportError("Could not shutdown connection", NULL, 0);
}
}
}
void Connection::ReportError(const char* szMsgPrefix, const char* szMsgArg, int ErrCode)
{
if (ErrCode == 0)
{
#ifdef WIN32
ErrCode = WSAGetLastError();
#else
ErrCode = errno;
#endif
}
char szErrPrefix[1024];
snprintf(szErrPrefix, 1024, szMsgPrefix, szMsgArg);
szErrPrefix[1024-1] = '\0';
#ifdef WIN32
if (m_bSuppressErrors)
{
debug("%s: ErrNo %i", szErrPrefix, ErrCode);
}
else
{
error("%s: ErrNo %i", szErrPrefix, ErrCode);
}
#else
const char* szErrMsg = hstrerror(ErrCode);
if (m_bSuppressErrors)
{
debug("%s: ErrNo %i, %s", szErrPrefix, ErrCode, szErrMsg);
}
else
{
error("%s: ErrNo %i, %s", szErrPrefix, ErrCode, szErrMsg);
}
#endif
}

85
Connection.h Normal file
View File

@@ -0,0 +1,85 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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$
*
*/
#ifndef CONNECTION_H
#define CONNECTION_H
#include "NetAddress.h"
class Connection
{
public:
enum EStatus
{
csConnected,
csDisconnected,
csListening,
csCancelled
};
protected:
NetAddress* m_pNetAddress;
SOCKET m_iSocket;
char* m_szReadBuf;
int m_iBufAvail;
char* m_szBufPtr;
EStatus m_eStatus;
int m_iTimeout;
bool m_bSuppressErrors;
unsigned int ResolveHostAddr(const char* szHost);
void ReportError(const char* szMsgPrefix, const char* szMsgArg, int ErrCode);
virtual int DoConnect();
virtual int DoDisconnect();
int DoBind();
int DoWriteLine(char* text);
char* DoReadLine(char* pBuffer, int iSize, int* pBytesRead);
SOCKET DoAccept();
public:
Connection(NetAddress* pNetAddress);
virtual ~Connection();
static void Init();
static void Final();
int Connect();
int Disconnect();
int Bind();
int Send(char* pBuffer, int iSize);
int Recv(char* pBuffer, int iSize);
bool RecvAll(char* pBuffer, int iSize);
char* ReadLine(char* pBuffer, int iSize, int* pBytesRead);
int WriteLine(char* text);
SOCKET Accept();
void Cancel();
NetAddress* GetServer() { return m_pNetAddress; }
SOCKET GetSocket() { return m_iSocket; }
void SetTimeout(int iTimeout) { m_iTimeout = iTimeout; }
EStatus GetStatus() { return m_eStatus; }
void SetSuppressErrors(bool bSuppressErrors) { m_bSuppressErrors = bSuppressErrors; }
bool GetSuppressErrors() { return m_bSuppressErrors; }
};
#endif

373
Decoder.cpp Normal file
View File

@@ -0,0 +1,373 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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 <stdio.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include "nzbget.h"
#ifdef ENABLE_UULIB
#ifndef PROTOTYPES
#define PROTOTYPES
#endif
#include <uudeview.h>
#endif
#include "Decoder.h"
#include "Log.h"
#include "Util.h"
Mutex UULibDecoder::m_mutexDecoder;
unsigned int YDecoder::crc_tab[256];
Decoder::Decoder()
{
debug("Creating Decoder");
m_szSrcFilename = NULL;
m_szDestFilename = NULL;
m_szArticleFilename = NULL;
m_bCrcError = false;
}
Decoder::~ Decoder()
{
debug("Destroying Decoder");
if (m_szArticleFilename)
{
free(m_szArticleFilename);
}
}
/*
* UULibDecoder
*/
bool UULibDecoder::Execute()
{
bool res = false;
#ifndef ENABLE_UULIB
error("Program was compiled without option ENABLE_UULIB defined. uulib-Decoder is not available.");
#else
m_mutexDecoder.Lock();
UUInitialize();
UUSetOption(UUOPT_DESPERATE, 1, NULL);
// UUSetOption(UUOPT_DUMBNESS,1,NULL);
// UUSetOption( UUOPT_SAVEPATH, 1, szDestDir );
UULoadFile((char*) m_szSrcFilename, NULL, 0);
// choose right attachment
uulist* attachment = NULL;
for (int i = 0; ; i++)
{
uulist* att_tmp = UUGetFileListItem(i);
if (!att_tmp)
{
break;
}
if ((att_tmp) && (att_tmp->haveparts))
{
if (!attachment)
{
attachment = att_tmp;
}
else
{
//f**k, multiple attachments!? Can't handle this.
attachment = NULL;
break;
}
}
}
if (attachment)
{
// okay, we got only one attachment, perfect!
if ((attachment->haveparts) && (attachment->haveparts[0])) // && (!attachment->haveparts[1])) FUCK UULIB
{
int r = UUDecodeFile(attachment, (char*)m_szDestFilename);
if (r == UURET_OK)
{
// we did it!
res = true;
m_szArticleFilename = strdup(attachment->filename);
}
}
else
{
error("[ERROR] Wrong number of parts!\n");
}
}
else
{
error("[ERROR] Wrong number of attachments!\n");
}
UUCleanUp();
m_mutexDecoder.Unlock();
#endif // ENABLE_UULIB
return res;
}
/**
* YDecoder
* Very primitive (but fast) implementation of yEnc-Decoder
*/
void YDecoder::Init()
{
debug("Initializing global decoder");
crc32gentab();
}
void YDecoder::Final()
{
debug("Finalizing global Decoder");
}
YDecoder::YDecoder()
{
Clear();
}
void YDecoder::Clear()
{
m_bBody = false;
m_bEnd = false;
m_lExpectedCRC = 0;
m_lCalculatedCRC = 0xFFFFFFFF;
m_iBegin = 0;
m_iEnd = 0;
m_bAutoSeek = false;
m_bNeedSetPos = false;
m_bCrcCheck = false;
if (m_szArticleFilename)
{
free(m_szArticleFilename);
}
m_szArticleFilename = NULL;
}
/* from crc32.c (http://www.koders.com/c/fid699AFE0A656F0022C9D6B9D1743E697B69CE5815.aspx)
*
* (c) 1999,2000 Krzysztof Dabrowski
* (c) 1999,2000 ElysiuM deeZine
* Released under GPL (thanks)
*
* chksum_crc32gentab() -- to a global crc_tab[256], this one will
* calculate the crcTable for crc32-checksums.
* it is generated to the polynom [..]
*/
void YDecoder::crc32gentab()
{
unsigned long crc, poly;
int i, j;
poly = 0xEDB88320L;
for (i = 0; i < 256; i++)
{
crc = i;
for (j = 8; j > 0; j--)
{
if (crc & 1)
{
crc = (crc >> 1) ^ poly;
}
else
{
crc >>= 1;
}
}
crc_tab[i] = crc;
}
}
/* This is modified version of chksum_crc() from
* crc32.c (http://www.koders.com/c/fid699AFE0A656F0022C9D6B9D1743E697B69CE5815.aspx)
* (c) 1999,2000 Krzysztof Dabrowski
* (c) 1999,2000 ElysiuM deeZine
*
* chksum_crc() -- to a given block, this one calculates the
* crc32-checksum until the length is
* reached. the crc32-checksum will be
* the result.
*/
unsigned long YDecoder::crc32m(unsigned long startCrc, unsigned char *block, unsigned int length)
{
register unsigned long crc = startCrc;
for (unsigned long i = 0; i < length; i++)
{
crc = ((crc >> 8) & 0x00FFFFFF) ^ crc_tab[(crc ^ *block++) & 0xFF];
}
return crc;
}
unsigned int YDecoder::DecodeBuffer(char* buffer)
{
if (m_bBody)
{
if (!strncmp(buffer, "=yend size=", 11))
{
m_bEnd = true;
char* pc = strstr(buffer, "pcrc32=");
if (pc)
{
pc += 7; //=strlen("pcrc32=")
m_lExpectedCRC = strtoul(pc, NULL, 16);
}
return 0;
}
char* iptr = buffer;
char* optr = buffer;
while (true)
{
switch (*iptr)
{
case '=': //escape-sequence
iptr++;
*optr = *iptr - 64 - 42;
optr++;
break;
case '\n': // ignored char
case '\r': // ignored char
break;
case '\0':
goto BreakLoop;
default: // normal char
*optr = *iptr - 42;
optr++;
break;
}
iptr++;
}
BreakLoop:
if (m_bCrcCheck)
{
m_lCalculatedCRC = crc32m(m_lCalculatedCRC, (unsigned char *)buffer, optr - buffer);
}
return optr - buffer;
}
else
{
if (!strncmp(buffer, "=ypart begin=", 13))
{
m_bBody = true;
char* pb = strstr(buffer, "begin=");
if (pb)
{
pb += 6; //=strlen("begin=")
m_iBegin = (int)atoi(pb);
}
pb = strstr(buffer, "end=");
if (pb)
{
pb += 4; //=strlen("end=")
m_iEnd = (int)atoi(pb);
}
}
else if (!strncmp(buffer, "=ybegin part=", 13))
{
char* pb = strstr(buffer, "name=");
if (pb)
{
pb += 5; //=strlen("name=")
char* pe;
for (pe = pb; *pe != '\0' && *pe != '\n' && *pe != '\r'; pe++) ;
if (m_szArticleFilename)
{
free(m_szArticleFilename);
}
m_szArticleFilename = (char*)malloc(pe - pb + 1);
strncpy(m_szArticleFilename, pb, pe - pb);
m_szArticleFilename[pe - pb] = '\0';
}
}
}
return 0;
}
bool YDecoder::Write(char* buffer, FILE* outfile)
{
unsigned int wcnt = DecodeBuffer(buffer);
if (wcnt > 0)
{
if (m_bNeedSetPos)
{
if (m_iBegin == 0 || m_iEnd == 0 || !outfile)
{
return false;
}
if (fseek(outfile, m_iBegin - 1, SEEK_SET))
{
return false;
}
m_bNeedSetPos = false;
}
fwrite(buffer, 1, wcnt, outfile);
}
return true;
}
bool YDecoder::Execute()
{
m_lCalculatedCRC ^= 0xFFFFFFFF;
debug("Expected pcrc32=%x", m_lExpectedCRC);
debug("Calculated pcrc32=%x", m_lCalculatedCRC);
m_bCrcError = m_bCrcCheck && (m_lExpectedCRC != m_lCalculatedCRC);
return m_bBody && m_bEnd && !m_bCrcError;
}

89
Decoder.h Normal file
View File

@@ -0,0 +1,89 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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$
*
*/
#ifndef DECODER_H
#define DECODER_H
#include "Thread.h"
class Decoder
{
protected:
const char* m_szSrcFilename;
const char* m_szDestFilename;
char* m_szArticleFilename;
bool m_bCrcError;
public:
Decoder();
virtual ~Decoder();
virtual bool Execute() = 0;
void SetSrcFilename(const char* szSrcFilename) { m_szSrcFilename = szSrcFilename; }
void SetDestFilename(const char* szDestFilename) { m_szDestFilename = szDestFilename; }
const char* GetArticleFilename() { return m_szArticleFilename; }
bool GetCrcError() { return m_bCrcError; }
};
class UULibDecoder: public Decoder
{
private:
static Mutex m_mutexDecoder;
public:
virtual bool Execute();
};
class YDecoder: public Decoder
{
protected:
static unsigned int crc_tab[256];
bool m_bBody;
bool m_bEnd;
unsigned long m_lExpectedCRC;
unsigned long m_lCalculatedCRC;
unsigned long m_iBegin;
unsigned long m_iEnd;
bool m_bAutoSeek;
bool m_bNeedSetPos;
bool m_bCrcCheck;
unsigned int DecodeBuffer(char* buffer);
static void crc32gentab();
unsigned long crc32m(unsigned long startCrc, unsigned char *block, unsigned int length);
public:
YDecoder();
virtual bool Execute();
void Clear();
bool Write(char* buffer, FILE* outfile);
void SetAutoSeek(bool bAutoSeek) { m_bAutoSeek = m_bNeedSetPos = bAutoSeek; }
void SetCrcCheck(bool bCrcCheck) { m_bCrcCheck = bCrcCheck; }
static void Init();
static void Final();
};
#endif

396
DiskState.cpp Normal file
View File

@@ -0,0 +1,396 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2007 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 <sys/stat.h>
#include <stdio.h>
#include "nzbget.h"
#include "DiskState.h"
#include "Options.h"
#include "Log.h"
#include "Util.h"
extern Options* g_pOptions;
/* Save Download Queue to Disk.
* The Disk State consists of file "queue", which contains the order of files
* and of one diskstate-file for each file in download queue.
* This function saves only file "queue".
*/
bool DiskState::Save(DownloadQueue* pDownloadQueue)
{
debug("Saving queue to disk");
char fileName[1024];
snprintf(fileName, 1024, "%s%s", g_pOptions->GetQueueDir(), "queue");
fileName[1024-1] = '\0';
FILE* outfile = fopen(fileName, "w");
if (!outfile)
{
error("Could not create file %s", fileName);
perror(fileName);
return false;
}
fprintf(outfile, "nzbget diskstate file version 1\n");
int cnt = 0;
for (DownloadQueue::iterator it = pDownloadQueue->begin(); it != pDownloadQueue->end(); it++)
{
FileInfo* pFileInfo = *it;
if (!pFileInfo->GetDeleted())
{
fprintf(outfile, "%i,%i\n", pFileInfo->GetID(), (int)pFileInfo->GetPaused());
cnt++;
}
}
fclose(outfile);
if (cnt == 0)
{
remove(fileName);
}
return true;
}
bool DiskState::SaveFile(FileInfo* pFileInfo)
{
char fileName[1024];
snprintf(fileName, 1024, "%s%i", g_pOptions->GetQueueDir(), pFileInfo->GetID());
fileName[1024-1] = '\0';
return SaveFileInfo(pFileInfo, fileName);
}
bool DiskState::Load(DownloadQueue* pDownloadQueue)
{
debug("Loading queue from disk");
char fileName[1024];
snprintf(fileName, 1024, "%s%s", g_pOptions->GetQueueDir(), "queue");
fileName[1024-1] = '\0';
FILE* infile = fopen(fileName, "r");
if (!infile)
{
error("Could not open file %s", fileName);
return false;
}
bool res = false;
char FileSignatur[128];
fgets(FileSignatur, sizeof(FileSignatur), infile);
if (!strcmp(FileSignatur, "nzbget diskstate file version 1\n"))
{
int id, paused;
while (fscanf(infile, "%i,%i\n", &id, &paused) != EOF)
{
char fileName[1024];
snprintf(fileName, 1024, "%s%i", g_pOptions->GetQueueDir(), id);
fileName[1024-1] = '\0';
FileInfo* pFileInfo = new FileInfo();
bool res = LoadFileInfo(pFileInfo, fileName, true, false);
if (res)
{
pFileInfo->SetID(id);
pFileInfo->SetPaused(paused);
pDownloadQueue->push_back(pFileInfo);
}
else
{
warn("Could not load diskstate for file %s", fileName);
delete pFileInfo;
}
}
res = true;
}
else
{
error("Could not load diskstate due file version mismatch");
res = false;
}
fclose(infile);
return res;
}
bool DiskState::LoadArticles(FileInfo* pFileInfo)
{
char fileName[1024];
snprintf(fileName, 1024, "%s%i", g_pOptions->GetQueueDir(), pFileInfo->GetID());
fileName[1024-1] = '\0';
return LoadFileInfo(pFileInfo, fileName, false, true);
}
bool DiskState::SaveFileInfo(FileInfo* pFileInfo, const char* szFilename)
{
debug("Saving FileInfo to disk");
FILE* outfile = fopen(szFilename, "w");
if (!outfile)
{
error("Could not create file %s", szFilename);
return false;
}
fprintf(outfile, "%s\n", pFileInfo->GetNZBFilename());
fprintf(outfile, "%s\n", pFileInfo->GetSubject());
fprintf(outfile, "%s\n", pFileInfo->GetDestDir());
fprintf(outfile, "%s\n", pFileInfo->GetFilename());
fprintf(outfile, "%i\n", pFileInfo->GetFilenameConfirmed());
fprintf(outfile, "%lu,%lu\n", (unsigned long)(pFileInfo->GetSize() >> 32), (unsigned long)(pFileInfo->GetSize()));
fprintf(outfile, "%i\n", pFileInfo->GetGroups()->size());
for (FileInfo::Groups::iterator it = pFileInfo->GetGroups()->begin(); it != pFileInfo->GetGroups()->end(); it++)
{
fprintf(outfile, "%s\n", *it);
}
fprintf(outfile, "%i\n", pFileInfo->GetArticles()->size());
for (FileInfo::Articles::iterator it = pFileInfo->GetArticles()->begin(); it != pFileInfo->GetArticles()->end(); it++)
{
ArticleInfo* pArticleInfo = *it;
fprintf(outfile, "%i,%i\n", pArticleInfo->GetPartNumber(), pArticleInfo->GetSize());
fprintf(outfile, "%s\n", pArticleInfo->GetMessageID());
}
fclose(outfile);
return true;
}
bool DiskState::LoadFileInfo(FileInfo* pFileInfo, const char * szFilename, bool bFileSummary, bool bArticles)
{
debug("Loading FileInfo from disk");
FILE* infile = fopen(szFilename, "r");
if (!infile)
{
error("Could not open file %s", szFilename);
return false;
}
char buf[1024];
if (!fgets(buf, sizeof(buf), infile)) goto error;
if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n'
if (bFileSummary) pFileInfo->SetNZBFilename(buf);
if (!fgets(buf, sizeof(buf), infile)) goto error;
if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n'
if (bFileSummary) pFileInfo->SetSubject(buf);
if (!fgets(buf, sizeof(buf), infile)) goto error;
if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n'
if (bFileSummary) pFileInfo->SetDestDir(buf);
if (!fgets(buf, sizeof(buf), infile)) goto error;
if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n'
if (bFileSummary) pFileInfo->SetFilename(buf);
int iFilenameConfirmed;
if (fscanf(infile, "%i\n", &iFilenameConfirmed) != 1) goto error;
if (bFileSummary) pFileInfo->SetFilenameConfirmed(iFilenameConfirmed);
unsigned long High, Low;
if (fscanf(infile, "%lu,%lu\n", &High, &Low) != 2) goto error;
if (bFileSummary) pFileInfo->SetSize((((unsigned long long)High) << 32) + Low);
if (bFileSummary) pFileInfo->SetRemainingSize(pFileInfo->GetSize());
int size;
if (fscanf(infile, "%i\n", &size) != 1) goto error;
for (int i = 0; i < size; i++)
{
if (!fgets(buf, sizeof(buf), infile)) goto error;
if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n'
if (bFileSummary) pFileInfo->GetGroups()->push_back(strdup(buf));
}
if (bArticles)
{
if (fscanf(infile, "%i\n", &size) != 1) goto error;
for (int i = 0; i < size; i++)
{
int PartNumber, PartSize;
if (fscanf(infile, "%i,%i\n", &PartNumber, &PartSize) != 2) goto error;
if (!fgets(buf, sizeof(buf), infile)) goto error;
if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n'
ArticleInfo* pArticleInfo = new ArticleInfo();
pArticleInfo->SetPartNumber(PartNumber);
pArticleInfo->SetSize(PartSize);
pArticleInfo->SetMessageID(buf);
pFileInfo->GetArticles()->push_back(pArticleInfo);
}
}
fclose(infile);
return true;
error:
fclose(infile);
error("Error reading diskstate for file %s", szFilename);
return false;
}
/*
* Delete all files from Queue.
* Returns true if successful, false if not
*/
bool DiskState::Discard()
{
debug("Discarding queue");
char fileName[1024];
snprintf(fileName, 1024, "%s%s", g_pOptions->GetQueueDir(), "queue");
fileName[1024-1] = '\0';
FILE* infile = fopen(fileName, "r");
if (!infile)
{
error("Could not open file %s", fileName);
return false;
}
bool res = false;
char FileSignatur[128];
fgets(FileSignatur, sizeof(FileSignatur), infile);
if (!strcmp(FileSignatur, "nzbget diskstate file version 1\n"))
{
int id, paused;
while (fscanf(infile, "%i,%i\n", &id, &paused) == 2)
{
char fileName[1024];
snprintf(fileName, 1024, "%s%i", g_pOptions->GetQueueDir(), id);
fileName[1024-1] = '\0';
remove(fileName);
}
res = true;
}
else
{
error("Could not discard diskstate due file version mismatch");
res = false;
}
fclose(infile);
if (res)
{
remove(fileName);
}
return res;
}
bool DiskState::Exists()
{
debug("Checking if a saved queue exists on disk");
char fileName[1024];
snprintf(fileName, 1024, "%s%s", g_pOptions->GetQueueDir(), "queue");
fileName[1024-1] = '\0';
struct stat buffer;
bool fileExists = !stat(fileName, &buffer);
return fileExists;
}
bool DiskState::DiscardFile(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo)
{
// delete diskstate-file
char fileName[1024];
snprintf(fileName, 1024, "%s%i", g_pOptions->GetQueueDir(), pFileInfo->GetID());
fileName[1024-1] = '\0';
remove(fileName);
return !pDownloadQueue || Save(pDownloadQueue);
}
void DiskState::CleanupTempDir(DownloadQueue* pDownloadQueue)
{
// build array of IDs of files in queue for faster access
int* ids = (int*)malloc(sizeof(int) * (pDownloadQueue->size() + 1));
int* ptr = ids;
for (DownloadQueue::iterator it = pDownloadQueue->begin(); it != pDownloadQueue->end(); it++)
{
FileInfo* pFileInfo = *it;
*ptr++ = pFileInfo->GetID();
}
*ptr = 0;
// read directory
DirBrowser dir(g_pOptions->GetTempDir());
while (const char* filename = dir.Next())
{
int id, part;
bool del = strstr(filename, ".tmp") || strstr(filename, ".dec") ||
((sscanf(filename, "%i.out", &id) == 1) &&
!(g_pOptions->GetContinuePartial() && g_pOptions->GetDirectWrite()));
if (!del)
{
if ((sscanf(filename, "%i.%i", &id, &part) == 2) ||
(sscanf(filename, "%i.out", &id) == 1))
{
del = true;
ptr = ids;
while (*ptr)
{
if (*ptr == id)
{
del = false;
break;
}
ptr++;
}
}
}
if (del)
{
char szFullFilename[1024];
snprintf(szFullFilename, 1024, "%s%s", g_pOptions->GetTempDir(), filename);
szFullFilename[1024-1] = '\0';
remove(szFullFilename);
}
}
free(ids);
}

48
DiskState.h Normal file
View File

@@ -0,0 +1,48 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2007 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$
*
*/
#ifndef DISKSTATE_H
#define DISKSTATE_H
#include "DownloadInfo.h"
class DiskState
{
private:
bool SaveFileInfo(FileInfo* pFileInfo, const char* szFilename);
bool LoadFileInfo(FileInfo* pFileInfo, const char* szFilename, bool bFileSummary, bool bArticles);
public:
bool Exists();
bool Save(DownloadQueue* pDownloadQueue);
bool Load(DownloadQueue* pDownloadQueue);
bool SaveFile(FileInfo* pFileInfo);
bool LoadArticles(FileInfo* pFileInfo);
bool Discard();
bool DiscardFile(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo);
void CleanupTempDir(DownloadQueue* pDownloadQueue);
};
#endif

258
DownloadInfo.cpp Normal file
View File

@@ -0,0 +1,258 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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 <sys/stat.h>
#include "nzbget.h"
#include "DownloadInfo.h"
#include "Log.h"
#include "Util.h"
int FileInfo::m_iIDGen = 0;
ArticleInfo::ArticleInfo()
{
//debug("Creating ArticleInfo");
m_szMessageID = NULL;
m_iSize = 0;
m_eStatus = aiUndefined;
m_szResultFilename = NULL;
}
ArticleInfo::~ ArticleInfo()
{
//debug("Destroying ArticleInfo");
if (m_szMessageID)
{
free(m_szMessageID);
}
if (m_szResultFilename)
{
free(m_szResultFilename);
}
}
void ArticleInfo::SetMessageID(const char * szMessageID)
{
m_szMessageID = strdup(szMessageID);
}
void ArticleInfo::SetResultFilename(const char * v)
{
m_szResultFilename = strdup(v);
}
FileInfo::FileInfo()
{
debug("Creating FileInfo");
m_Articles.clear();
m_Groups.clear();
m_szSubject = NULL;
m_szFilename = NULL;
m_bFilenameConfirmed = false;
m_szDestDir = NULL;
m_szNZBFilename = NULL;
m_lSize = 0;
m_lRemainingSize = 0;
m_bPaused = false;
m_bDeleted = false;
m_iCompleted = 0;
m_bOutputInitialized = false;
m_iIDGen++;
m_iID = m_iIDGen;
}
FileInfo::~ FileInfo()
{
debug("Destroying FileInfo");
if (m_szSubject)
{
free(m_szSubject);
}
if (m_szFilename)
{
free(m_szFilename);
}
if (m_szDestDir)
{
free(m_szDestDir);
}
if (m_szNZBFilename)
{
free(m_szNZBFilename);
}
for (Groups::iterator it = m_Groups.begin(); it != m_Groups.end() ;it++)
{
free(*it);
}
m_Groups.clear();
ClearArticles();
}
void FileInfo::ClearArticles()
{
for (Articles::iterator it = m_Articles.begin(); it != m_Articles.end() ;it++)
{
delete *it;
}
m_Articles.clear();
}
void FileInfo::SetID(int s)
{
m_iID = s;
if (m_iIDGen < m_iID)
{
m_iIDGen = m_iID;
}
}
void FileInfo::SetSubject(const char* szSubject)
{
m_szSubject = strdup(szSubject);
}
void FileInfo::SetDestDir(const char* szDestDir)
{
m_szDestDir = strdup(szDestDir);
}
void FileInfo::SetNZBFilename(const char * szNZBFilename)
{
m_szNZBFilename = strdup(szNZBFilename);
}
void FileInfo::GetNiceNZBName(char* szBuffer, int iSize)
{
MakeNiceNZBName(m_szNZBFilename, szBuffer, iSize);
}
void FileInfo::MakeNiceNZBName(const char * szNZBFilename, char * szBuffer, int iSize)
{
char postname[1024];
const char* szBaseName = BaseFileName(szNZBFilename);
// if .nzb file has a certain structure, try to strip out certain elements
if (sscanf(szBaseName, "msgid_%*d_%1023s", postname) == 1)
{
// OK, using stripped name
}
else
{
// using complete filename
strncpy(postname, szBaseName, 1024);
postname[1024-1] = '\0';
}
// wipe out ".nzb"
if (char* p = strrchr(postname, '.')) *p = '\0';
::MakeValidFilename(postname, '_');
// if the resulting name is empty, use basename without cleaing up "msgid_"
if (strlen(postname) == 0)
{
// using complete filename
strncpy(postname, szBaseName, 1024);
postname[1024-1] = '\0';
// wipe out ".nzb"
if (char* p = strrchr(postname, '.')) *p = '\0';
::MakeValidFilename(postname, '_');
// if the resulting name is STILL empty, use "noname"
if (strlen(postname) == 0)
{
strncpy(postname, "noname", 1024);
}
}
strncpy(szBuffer, postname, iSize);
szBuffer[iSize-1] = '\0';
}
void FileInfo::SetFilename(const char* szFilename)
{
if (m_szFilename)
{
free(m_szFilename);
}
m_szFilename = strdup(szFilename);
}
void FileInfo::MakeValidFilename()
{
::MakeValidFilename(m_szFilename, '_');
}
void FileInfo::LockOutputFile()
{
m_mutexOutputFile.Lock();
}
void FileInfo::UnlockOutputFile()
{
m_mutexOutputFile.Unlock();
}
bool FileInfo::IsDupe(const char* szFilename)
{
struct stat buffer;
char fileName[1024];
snprintf(fileName, 1024, "%s%c%s", m_szDestDir, (int)PATH_SEPARATOR, szFilename);
fileName[1024-1] = '\0';
bool exists = !stat(fileName, &buffer);
if (exists)
{
return true;
}
snprintf(fileName, 1024, "%s%c%s_broken", m_szDestDir, (int)PATH_SEPARATOR, szFilename);
fileName[1024-1] = '\0';
exists = !stat(fileName, &buffer);
if (exists)
{
return true;
}
return false;
}

133
DownloadInfo.h Normal file
View File

@@ -0,0 +1,133 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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$
*
*/
#ifndef DOWNLOADINFO_H
#define DOWNLOADINFO_H
#include <vector>
#include <deque>
#include "Thread.h"
class ArticleInfo
{
public:
enum EStatus
{
aiUndefined,
aiRunning,
aiFinished,
aiFailed
};
private:
int m_iPartNumber;
char* m_szMessageID;
int m_iSize;
EStatus m_eStatus;
char* m_szResultFilename;
public:
ArticleInfo();
~ArticleInfo();
void SetPartNumber(int s) { m_iPartNumber = s; }
int GetPartNumber() { return m_iPartNumber; }
const char* GetMessageID() { return m_szMessageID; }
void SetMessageID(const char* szMessageID);
void SetSize(int s) { m_iSize = s; }
int GetSize() { return m_iSize; }
EStatus GetStatus() { return m_eStatus; }
void SetStatus(EStatus Status) { m_eStatus = Status; }
const char* GetResultFilename() { return m_szResultFilename; }
void SetResultFilename(const char* v);
};
class FileInfo
{
public:
typedef std::vector<ArticleInfo*> Articles;
typedef std::vector<char*> Groups;
private:
int m_iID;
Articles m_Articles;
Groups m_Groups;
char* m_szNZBFilename;
char* m_szSubject;
char* m_szFilename;
char* m_szDestDir;
long long m_lSize;
long long m_lRemainingSize;
bool m_bPaused;
bool m_bDeleted;
bool m_bFilenameConfirmed;
int m_iCompleted;
bool m_bOutputInitialized;
Mutex m_mutexOutputFile;
static int m_iIDGen;
public:
FileInfo();
~FileInfo();
int GetID() { return m_iID; }
void SetID(int s);
Articles* GetArticles() { return &m_Articles; }
Groups* GetGroups() { return &m_Groups; }
const char* GetNZBFilename() { return m_szNZBFilename; }
void SetNZBFilename(const char* szNZBFilename);
void GetNiceNZBName(char* szBuffer, int iSize);
static void MakeNiceNZBName(const char* szNZBFilename, char* szBuffer, int iSize);
const char* GetSubject() { return m_szSubject; }
void SetSubject(const char* szSubject);
const char* GetFilename() { return m_szFilename; }
void SetFilename(const char* szFilename);
void MakeValidFilename();
bool GetFilenameConfirmed() { return m_bFilenameConfirmed; }
void SetFilenameConfirmed(bool bFilenameConfirmed) { m_bFilenameConfirmed = bFilenameConfirmed; }
void SetSize(long long s) { m_lSize = s; m_lRemainingSize = s; }
long long GetSize() { return m_lSize; }
long long GetRemainingSize() { return m_lRemainingSize; }
void SetRemainingSize(long long s) { m_lRemainingSize = s; }
bool GetPaused() { return m_bPaused; }
void SetPaused(bool Paused) { m_bPaused = Paused; }
bool GetDeleted() { return m_bDeleted; }
void SetDeleted(bool Deleted) { m_bDeleted = Deleted; }
const char* GetDestDir() { return m_szDestDir; }
void SetDestDir(const char* szDestDir);
int GetCompleted() { return m_iCompleted; }
void SetCompleted(int s) { m_iCompleted = s; }
void ClearArticles();
void LockOutputFile();
void UnlockOutputFile();
bool GetOutputInitialized() { return m_bOutputInitialized; }
void SetOutputInitialized(bool bOutputInitialized) { m_bOutputInitialized = bOutputInitialized; }
bool IsDupe(const char* szFilename);
};
typedef std::deque<FileInfo*> DownloadQueue;
#endif

428
Frontend.cpp Normal file
View File

@@ -0,0 +1,428 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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>
#ifndef WIN32
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#endif
#include "nzbget.h"
#include "Options.h"
#include "Frontend.h"
#include "Log.h"
#include "Connection.h"
#include "MessageBase.h"
#include "QueueCoordinator.h"
#include "PrePostProcessor.h"
#include "RemoteClient.h"
#include "Util.h"
extern QueueCoordinator* g_pQueueCoordinator;
extern PrePostProcessor* g_pPrePostProcessor;
extern Options* g_pOptions;
Frontend::Frontend()
{
debug("Creating Frontend");
m_iNeededLogFirstID = 0;
m_iNeededLogEntries = 0;
m_bSummary = false;
m_bFileList = false;
m_fCurrentDownloadSpeed = 0;
m_lRemainingSize = 0;
m_bPause = false;
m_fDownloadLimit = 0;
m_iThreadCount = 0;
m_iParJobCount = 0;
m_iUpTimeSec = 0;
m_iDnTimeSec = 0;
m_iAllBytes = 0;
m_bStandBy = 0;
m_RemoteMessages.clear();
m_RemoteQueue.clear();
m_iUpdateInterval = g_pOptions->GetUpdateInterval();
}
bool Frontend::PrepareData()
{
if (IsRemoteMode())
{
if (IsStopped())
{
return false;
}
if (!RequestMessages() || ((m_bSummary || m_bFileList) && !RequestFileList()))
{
printf("\nUnable to send request to nzbget-server at %s (port %i) \n", g_pOptions->GetServerIP(), g_pOptions->GetServerPort());
Stop();
return false;
}
}
else
{
if (m_bSummary)
{
m_fCurrentDownloadSpeed = g_pQueueCoordinator->CalcCurrentDownloadSpeed();
m_lRemainingSize = g_pQueueCoordinator->CalcRemainingSize();
m_bPause = g_pOptions->GetPause();
m_fDownloadLimit = g_pOptions->GetDownloadRate();
m_iThreadCount = Thread::GetThreadCount();
PrePostProcessor::ParQueue* pParQueue = g_pPrePostProcessor->LockParQueue();
m_iParJobCount = pParQueue->size();
g_pPrePostProcessor->UnlockParQueue();
g_pQueueCoordinator->CalcStat(&m_iUpTimeSec, &m_iDnTimeSec, &m_iAllBytes, &m_bStandBy);
}
}
return true;
}
void Frontend::FreeData()
{
if (IsRemoteMode())
{
for (Log::Messages::iterator it = m_RemoteMessages.begin(); it != m_RemoteMessages.end(); it++)
{
delete *it;
}
m_RemoteMessages.clear();
for (DownloadQueue::iterator it = m_RemoteQueue.begin(); it != m_RemoteQueue.end(); it++)
{
delete *it;
}
m_RemoteQueue.clear();
}
}
Log::Messages * Frontend::LockMessages()
{
if (IsRemoteMode())
{
return &m_RemoteMessages;
}
else
{
return g_pLog->LockMessages();
}
}
void Frontend::UnlockMessages()
{
if (!IsRemoteMode())
{
g_pLog->UnlockMessages();
}
}
DownloadQueue * Frontend::LockQueue()
{
if (IsRemoteMode())
{
return &m_RemoteQueue;
}
else
{
return g_pQueueCoordinator->LockQueue();
}
}
void Frontend::UnlockQueue()
{
if (!IsRemoteMode())
{
g_pQueueCoordinator->UnlockQueue();
}
}
bool Frontend::IsRemoteMode()
{
return g_pOptions->GetRemoteClientMode();
}
void Frontend::ServerPauseUnpause(bool bPause)
{
if (IsRemoteMode())
{
RequestPauseUnpause(bPause);
}
else
{
g_pOptions->SetPause(bPause);
}
}
void Frontend::ServerSetDownloadRate(float fRate)
{
if (IsRemoteMode())
{
RequestSetDownloadRate(fRate);
}
else
{
g_pOptions->SetDownloadRate(fRate);
}
}
void Frontend::ServerDumpDebug()
{
if (IsRemoteMode())
{
RequestDumpDebug();
}
else
{
g_pQueueCoordinator->LogDebugInfo();
}
}
bool Frontend::ServerEditQueue(QueueEditor::EEditAction eAction, int iOffset, int iID)
{
if (IsRemoteMode())
{
return RequestEditQueue(eAction, iOffset, iID);
}
else
{
return g_pQueueCoordinator->GetQueueEditor()->EditEntry(iID, true, eAction, iOffset);
}
return false;
}
void Frontend::InitMessageBase(SNZBRequestBase* pMessageBase, int iRequest, int iSize)
{
pMessageBase->m_iSignature = htonl(NZBMESSAGE_SIGNATURE);
pMessageBase->m_iType = htonl(iRequest);
pMessageBase->m_iStructSize = htonl(iSize);
strncpy(pMessageBase->m_szPassword, g_pOptions->GetServerPassword(), NZBREQUESTPASSWORDSIZE);
pMessageBase->m_szPassword[NZBREQUESTPASSWORDSIZE - 1] = '\0';
}
bool Frontend::RequestMessages()
{
NetAddress netAddress(g_pOptions->GetServerIP(), g_pOptions->GetServerPort());
Connection connection(&netAddress);
bool OK = connection.Connect() >= 0;
if (!OK)
{
return false;
}
SNZBLogRequest LogRequest;
InitMessageBase(&LogRequest.m_MessageBase, eRemoteRequestLog, sizeof(LogRequest));
LogRequest.m_iLines = htonl(m_iNeededLogEntries);
if (m_iNeededLogEntries == 0)
{
LogRequest.m_iIDFrom = htonl(m_iNeededLogFirstID > 0 ? m_iNeededLogFirstID : 1);
}
else
{
LogRequest.m_iIDFrom = 0;
}
if (connection.Send((char*)(&LogRequest), sizeof(LogRequest)) < 0)
{
return false;
}
// Now listen for the returned log
SNZBLogResponse LogResponse;
int iResponseLen = connection.Recv((char*) &LogResponse, sizeof(LogResponse));
if (iResponseLen != sizeof(LogResponse) ||
(int)ntohl(LogResponse.m_MessageBase.m_iSignature) != (int)NZBMESSAGE_SIGNATURE ||
ntohl(LogResponse.m_MessageBase.m_iStructSize) != sizeof(LogResponse))
{
return false;
}
char* pBuf = NULL;
if (ntohl(LogResponse.m_iTrailingDataLength) > 0)
{
pBuf = (char*)malloc(ntohl(LogResponse.m_iTrailingDataLength));
if (!connection.RecvAll(pBuf, ntohl(LogResponse.m_iTrailingDataLength)))
{
free(pBuf);
return false;
}
}
connection.Disconnect();
if (ntohl(LogResponse.m_iTrailingDataLength) > 0)
{
char* pBufPtr = (char*)pBuf;
for (unsigned int i = 0; i < ntohl(LogResponse.m_iNrTrailingEntries); i++)
{
SNZBLogResponseEntry* pLogAnswer = (SNZBLogResponseEntry*) pBufPtr;
char* szText = pBufPtr + sizeof(SNZBLogResponseEntry);
Message* pMessage = new Message(ntohl(pLogAnswer->m_iID), (Message::EKind)ntohl(pLogAnswer->m_iKind), ntohl(pLogAnswer->m_tTime), szText);
m_RemoteMessages.push_back(pMessage);
pBufPtr += sizeof(SNZBLogResponseEntry) + ntohl(pLogAnswer->m_iTextLen);
}
free(pBuf);
}
return true;
}
bool Frontend::RequestFileList()
{
NetAddress netAddress(g_pOptions->GetServerIP(), g_pOptions->GetServerPort());
Connection connection(&netAddress);
bool OK = connection.Connect() >= 0;
if (!OK)
{
return false;
}
SNZBListRequest ListRequest;
InitMessageBase(&ListRequest.m_MessageBase, eRemoteRequestList, sizeof(ListRequest));
ListRequest.m_bFileList = htonl(m_bFileList);
ListRequest.m_bServerState = htonl(m_bSummary);
if (connection.Send((char*)(&ListRequest), sizeof(ListRequest)) < 0)
{
return false;
}
// Now listen for the returned list
SNZBListResponse ListResponse;
int iResponseLen = connection.Recv((char*) &ListResponse, sizeof(ListResponse));
if (iResponseLen != sizeof(ListResponse) ||
(int)ntohl(ListResponse.m_MessageBase.m_iSignature) != (int)NZBMESSAGE_SIGNATURE ||
ntohl(ListResponse.m_MessageBase.m_iStructSize) != sizeof(ListResponse))
{
return false;
}
char* pBuf = NULL;
if (ntohl(ListResponse.m_iTrailingDataLength) > 0)
{
pBuf = (char*)malloc(ntohl(ListResponse.m_iTrailingDataLength));
if (!connection.RecvAll(pBuf, ntohl(ListResponse.m_iTrailingDataLength)))
{
free(pBuf);
return false;
}
}
connection.Disconnect();
if (m_bSummary)
{
m_bPause = ntohl(ListResponse.m_bServerPaused);
m_lRemainingSize = JoinInt64(ntohl(ListResponse.m_iRemainingSizeHi), ntohl(ListResponse.m_iRemainingSizeLo));
m_fCurrentDownloadSpeed = ntohl(ListResponse.m_iDownloadRate) / 1024.0;
m_fDownloadLimit = ntohl(ListResponse.m_iDownloadLimit) / 1024.0;
m_iThreadCount = ntohl(ListResponse.m_iThreadCount);
m_iParJobCount = ntohl(ListResponse.m_iParJobCount);
m_iUpTimeSec = ntohl(ListResponse.m_iUpTimeSec);
m_iDnTimeSec = ntohl(ListResponse.m_iDownloadTimeSec);
m_bStandBy = ntohl(ListResponse.m_bServerStandBy);
m_iAllBytes = JoinInt64(ntohl(ListResponse.m_iDownloadedBytesHi), ntohl(ListResponse.m_iDownloadedBytesLo));
}
if (m_bFileList && ntohl(ListResponse.m_iTrailingDataLength) > 0)
{
char* pBufPtr = (char*)pBuf;
for (unsigned int i = 0; i < ntohl(ListResponse.m_iNrTrailingEntries); i++)
{
SNZBListResponseEntry* pListAnswer = (SNZBListResponseEntry*) pBufPtr;
char* szNZBFilename = pBufPtr + sizeof(SNZBListResponseEntry);
char* szSubject = pBufPtr + sizeof(SNZBListResponseEntry) + ntohl(pListAnswer->m_iNZBFilenameLen);
char* szFileName = pBufPtr + sizeof(SNZBListResponseEntry) + ntohl(pListAnswer->m_iNZBFilenameLen) + ntohl(pListAnswer->m_iSubjectLen);
char* szDestDir = pBufPtr + sizeof(SNZBListResponseEntry) + ntohl(pListAnswer->m_iNZBFilenameLen) + ntohl(pListAnswer->m_iSubjectLen) + ntohl(pListAnswer->m_iFilenameLen);
FileInfo* pFileInfo = new FileInfo();
pFileInfo->SetID(ntohl(pListAnswer->m_iID));
pFileInfo->SetSize(JoinInt64(ntohl(pListAnswer->m_iFileSizeHi), ntohl(pListAnswer->m_iFileSizeLo)));
pFileInfo->SetRemainingSize(JoinInt64(ntohl(pListAnswer->m_iRemainingSizeHi), ntohl(pListAnswer->m_iRemainingSizeLo)));
pFileInfo->SetPaused(ntohl(pListAnswer->m_bPaused));
pFileInfo->SetNZBFilename(szNZBFilename);
pFileInfo->SetSubject(szSubject);
pFileInfo->SetFilename(szFileName);
pFileInfo->SetFilenameConfirmed(ntohl(pListAnswer->m_bFilenameConfirmed));
pFileInfo->SetDestDir(szDestDir);
m_RemoteQueue.push_back(pFileInfo);
pBufPtr += sizeof(SNZBListResponseEntry) + ntohl(pListAnswer->m_iNZBFilenameLen) +
ntohl(pListAnswer->m_iSubjectLen) + ntohl(pListAnswer->m_iFilenameLen) + ntohl(pListAnswer->m_iDestDirLen);
}
}
if (pBuf)
{
free(pBuf);
}
return true;
}
bool Frontend::RequestPauseUnpause(bool bPause)
{
RemoteClient client;
client.SetVerbose(false);
return client.RequestServerPauseUnpause(bPause);
}
bool Frontend::RequestSetDownloadRate(float fRate)
{
RemoteClient client;
client.SetVerbose(false);
return client.RequestServerSetDownloadRate(fRate);
}
bool Frontend::RequestDumpDebug()
{
RemoteClient client;
client.SetVerbose(false);
return client.RequestServerDumpDebug();
}
bool Frontend::RequestEditQueue(int iAction, int iOffset, int iID)
{
RemoteClient client;
client.SetVerbose(false);
return client.RequestServerEditQueue(iAction, iOffset, &iID, 1, false);
}

85
Frontend.h Normal file
View File

@@ -0,0 +1,85 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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$
*
*/
#ifndef FRONTEND_H
#define FRONTEND_H
#include "Thread.h"
#include "Log.h"
#include "DownloadInfo.h"
#include "MessageBase.h"
#include "QueueEditor.h"
class Frontend : public Thread
{
private:
Log::Messages m_RemoteMessages;
DownloadQueue m_RemoteQueue;
bool RequestMessages();
bool RequestFileList();
protected:
bool m_bSummary;
bool m_bFileList;
unsigned int m_iNeededLogEntries;
unsigned int m_iNeededLogFirstID;
int m_iUpdateInterval;
// summary
float m_fCurrentDownloadSpeed;
long long m_lRemainingSize;
bool m_bPause;
float m_fDownloadLimit;
int m_iThreadCount;
int m_iParJobCount;
int m_iUpTimeSec;
int m_iDnTimeSec;
long long m_iAllBytes;
bool m_bStandBy;
bool PrepareData();
void FreeData();
Log::Messages* LockMessages();
void UnlockMessages();
DownloadQueue* LockQueue();
void UnlockQueue();
bool IsRemoteMode();
void InitMessageBase(SNZBRequestBase* pMessageBase, int iRequest, int iSize);
void ServerPauseUnpause(bool bPause);
bool RequestPauseUnpause(bool bPause);
void ServerSetDownloadRate(float fRate);
bool RequestSetDownloadRate(float fRate);
void ServerDumpDebug();
bool RequestDumpDebug();
bool ServerEditQueue(QueueEditor::EEditAction eAction, int iOffset, int iEntry);
bool RequestEditQueue(int iAction, int iOffset, int iID);
public:
Frontend();
};
#endif

167
INSTALL Normal file
View File

@@ -0,0 +1,167 @@
Basic Installation
==================
These are generic installation instructions.
The `configure' shell script attempts to guess correct values for
various system-dependent variables used during compilation. It uses
those values to create a `Makefile' in each directory of the package.
It may also create one or more `.h' files containing system-dependent
definitions. Finally, it creates a shell script `config.status' that
you can run in the future to recreate the current configuration, a file
`config.cache' that saves the results of its tests to speed up
reconfiguring, and a file `config.log' containing compiler output
(useful mainly for debugging `configure').
If you need to do unusual things to compile the package, please try
to figure out how `configure' could check whether to do them, and mail
diffs or instructions to the address given in the `README' so they can
be considered for the next release. If at some point `config.cache'
contains results you don't want to keep, you may remove or edit it.
The file `configure.in' is used to create `configure' by a program
called `autoconf'. You only need `configure.in' if you want to change
it or regenerate `configure' using a newer version of `autoconf'.
The simplest way to compile this package is:
1. `cd' to the directory containing the package's source code and type
`./configure' to configure the package for your system. If you're
using `csh' on an old version of System V, you might need to type
`sh ./configure' instead to prevent `csh' from trying to execute
`configure' itself.
Running `configure' takes a while. While running, it prints some
messages telling which features it is checking for.
2. Type `make' to compile the package.
3. Type `make install' to install the programs and any data files and
documentation.
4. You can remove the program binaries and object files from the
source code directory by typing `make clean'.
Compilers and Options
=====================
Some systems require unusual options for compilation or linking that
the `configure' script does not know about. You can give `configure'
initial values for variables by setting them in the environment. Using
a Bourne-compatible shell, you can do that on the command line like
this:
CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure
Or on systems that have the `env' program, you can do it like this:
env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure
Compiling For Multiple Architectures
====================================
You can compile the package for more than one kind of computer at the
same time, by placing the object files for each architecture in their
own directory. To do this, you must use a version of `make' that
supports the `VPATH' variable, such as GNU `make'. `cd' to the
directory where you want the object files and executables to go and run
the `configure' script. `configure' automatically checks for the
source code in the directory that `configure' is in and in `..'.
If you have to use a `make' that does not supports the `VPATH'
variable, you have to compile the package for one architecture at a time
in the source code directory. After you have installed the package for
one architecture, use `make distclean' before reconfiguring for another
architecture.
Installation Names
==================
By default, `make install' will install the package's files in
`/usr/local/bin', `/usr/local/man', etc. You can specify an
installation prefix other than `/usr/local' by giving `configure' the
option `--prefix=PATH'.
You can specify separate installation prefixes for
architecture-specific files and architecture-independent files. If you
give `configure' the option `--exec-prefix=PATH', the package will use
PATH as the prefix for installing programs and libraries.
Documentation and other data files will still use the regular prefix.
If the package supports it, you can cause programs to be installed
with an extra prefix or suffix on their names by giving `configure' the
option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
Optional Features
=================
Some packages pay attention to `--enable-FEATURE' options to
`configure', where FEATURE indicates an optional part of the package.
They may also pay attention to `--with-PACKAGE' options, where PACKAGE
is something like `gnu-as' or `x' (for the X Window System). The
`README' should mention any `--enable-' and `--with-' options that the
package recognizes.
For packages that use the X Window System, `configure' can usually
find the X include and library files automatically, but if it doesn't,
you can use the `configure' options `--x-includes=DIR' and
`--x-libraries=DIR' to specify their locations.
Specifying the System Type
==========================
There may be some features `configure' can not figure out
automatically, but needs to determine by the type of host the package
will run on. Usually `configure' can figure that out, but if it prints
a message saying it can not guess the host type, give it the
`--host=TYPE' option. TYPE can either be a short name for the system
type, such as `sun4', or a canonical name with three fields:
CPU-COMPANY-SYSTEM
See the file `config.sub' for the possible values of each field. If
`config.sub' isn't included in this package, then this package doesn't
need to know the host type.
If you are building compiler tools for cross-compiling, you can also
use the `--target=TYPE' option to select the type of system they will
produce code for and the `--build=TYPE' option to select the type of
system on which you are compiling the package.
Sharing Defaults
================
If you want to set default values for `configure' scripts to share,
you can create a site shell script called `config.site' that gives
default values for variables like `CC', `cache_file', and `prefix'.
`configure' looks for `PREFIX/share/config.site' if it exists, then
`PREFIX/etc/config.site' if it exists. Or, you can set the
`CONFIG_SITE' environment variable to the location of the site script.
A warning: not all `configure' scripts look for a site script.
Operation Controls
==================
`configure' recognizes the following options to control how it
operates.
`--cache-file=FILE'
Use and save the results of the tests in FILE instead of
`./config.cache'. Set FILE to `/dev/null' to disable caching, for
debugging `configure'.
`--help'
Print a summary of the options to `configure', and exit.
`--quiet'
`--silent'
`-q'
Do not print messages saying which checks are being made.
`--srcdir=DIR'
Look for the package's source code in directory DIR. Usually
`configure' can determine that directory automatically.
`--version'
Print the version of Autoconf used to generate the `configure'
script, and exit.
`configure' also accepts some other, not widely useful, options.

334
Log.cpp Normal file
View File

@@ -0,0 +1,334 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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 <sys/stat.h>
#include <stdarg.h>
#include "nzbget.h"
#include "Options.h"
#include "Log.h"
#include "Util.h"
extern Options* g_pOptions;
Log::Log()
{
m_Messages.clear();
m_iIDGen = 0;
m_szLogFilename = NULL;
#ifdef DEBUG
struct stat buffer;
m_bExtraDebug = !stat("extradebug", &buffer);
#endif
}
Log::~Log()
{
for (Messages::iterator it = m_Messages.begin(); it != m_Messages.end(); it++)
{
delete *it;
}
m_Messages.clear();
if (m_szLogFilename)
{
free(m_szLogFilename);
}
}
void Log::Filelog(const char* msg, ...)
{
if (
(g_pOptions && g_pOptions->GetCreateLog() && g_pOptions->GetLogFile())
#ifdef DEBUG
|| (m_szLogFilename && m_bExtraDebug)
#endif
)
{
if (!m_szLogFilename)
{
m_szLogFilename = strdup(g_pOptions->GetLogFile());
}
char tmp2[1024];
va_list ap;
va_start(ap, msg);
vsnprintf(tmp2, 1024, msg, ap);
tmp2[1024-1] = '\0';
va_end(ap);
time_t rawtime;
time(&rawtime);
char szTime[50];
#ifdef HAVE_CTIME_R_3
ctime_r(&rawtime, szTime, 50);
#else
ctime_r(&rawtime, szTime);
#endif
szTime[50-1] = '\0';
szTime[strlen(szTime) - 1] = '\0'; // trim LF
FILE* file = fopen(m_szLogFilename, "a+");
if (file)
{
#ifdef WIN32
unsigned long iThreadId = GetCurrentThreadId();
#else
unsigned long iThreadId = (unsigned long)pthread_self();
#endif
#ifdef DEBUG
fprintf(file, "%s\t%lu\t%s\n", szTime, iThreadId, tmp2);
#else
fprintf(file, "%s\t%s\n", szTime, tmp2);
#endif
fclose(file);
}
else
{
perror(m_szLogFilename);
}
}
}
#undef debug
#ifdef HAVE_VARIADIC_MACROS
void debug(const char* szFilename, const char* szFuncname, int iLineNr, const char* msg, ...)
#else
void debug(const char* msg, ...)
#endif
{
#ifdef DEBUG
char tmp1[1024];
va_list ap;
va_start(ap, msg);
vsnprintf(tmp1, 1024, msg, ap);
tmp1[1024-1] = '\0';
va_end(ap);
char tmp2[1024];
#ifdef HAVE_VARIADIC_MACROS
if (szFuncname)
{
snprintf(tmp2, 1024, "%s (%s:%i:%s)", tmp1, BaseFileName(szFilename), iLineNr, szFuncname);
}
else
{
snprintf(tmp2, 1024, "%s (%s:%i)", tmp1, BaseFileName(szFilename), iLineNr);
}
#else
snprintf(tmp2, 1024, "%s", tmp1);
#endif
tmp2[1024-1] = '\0';
g_pLog->m_mutexLog.Lock();
if (!g_pOptions)
{
if (g_pLog->m_bExtraDebug)
{
printf("%s\n", tmp2);
g_pLog->Filelog("DEBUG\t%s", tmp2);
}
g_pLog->m_mutexLog.Unlock();
return;
}
Options::EMessageTarget eMessageTarget = g_pOptions->GetDebugTarget();
if (eMessageTarget == Options::mtLog || eMessageTarget == Options::mtBoth)
{
g_pLog->Filelog("DEBUG\t%s", tmp2);
}
if (eMessageTarget == Options::mtScreen || eMessageTarget == Options::mtBoth)
{
g_pLog->AppendMessage(Message::mkDebug, tmp2);
}
g_pLog->m_mutexLog.Unlock();
#endif
}
void error(const char* msg, ...)
{
char tmp2[1024];
va_list ap;
va_start(ap, msg);
vsnprintf(tmp2, 1024, msg, ap);
tmp2[1024-1] = '\0';
va_end(ap);
g_pLog->m_mutexLog.Lock();
Options::EMessageTarget eMessageTarget = g_pOptions->GetErrorTarget();
if (eMessageTarget == Options::mtLog || eMessageTarget == Options::mtBoth)
{
g_pLog->Filelog("ERROR\t%s", tmp2);
}
if (eMessageTarget == Options::mtScreen || eMessageTarget == Options::mtBoth)
{
g_pLog->AppendMessage(Message::mkError, tmp2);
}
g_pLog->m_mutexLog.Unlock();
}
void warn(const char* msg, ...)
{
char tmp2[1024];
va_list ap;
va_start(ap, msg);
vsnprintf(tmp2, 1024, msg, ap);
tmp2[1024-1] = '\0';
va_end(ap);
g_pLog->m_mutexLog.Lock();
Options::EMessageTarget eMessageTarget = g_pOptions->GetWarningTarget();
if (eMessageTarget == Options::mtLog || eMessageTarget == Options::mtBoth)
{
g_pLog->Filelog("WARNING\t%s", tmp2);
}
if (eMessageTarget == Options::mtScreen || eMessageTarget == Options::mtBoth)
{
g_pLog->AppendMessage(Message::mkWarning, tmp2);
}
g_pLog->m_mutexLog.Unlock();
}
void info(const char* msg, ...)
{
char tmp2[1024];
va_list ap;
va_start(ap, msg);
vsnprintf(tmp2, 1024, msg, ap);
tmp2[1024-1] = '\0';
va_end(ap);
g_pLog->m_mutexLog.Lock();
Options::EMessageTarget eMessageTarget = g_pOptions->GetInfoTarget();
if (eMessageTarget == Options::mtLog || eMessageTarget == Options::mtBoth)
{
g_pLog->Filelog("INFO\t%s", tmp2);
}
if (eMessageTarget == Options::mtScreen || eMessageTarget == Options::mtBoth)
{
g_pLog->AppendMessage(Message::mkInfo, tmp2);
}
g_pLog->m_mutexLog.Unlock();
}
void abort(const char* msg, ...)
{
char tmp2[1024];
va_list ap;
va_start(ap, msg);
vsnprintf(tmp2, 1024, msg, ap);
tmp2[1024-1] = '\0';
va_end(ap);
g_pLog->m_mutexLog.Lock();
printf("\n%s", tmp2);
g_pLog->Filelog(tmp2);
g_pLog->m_mutexLog.Unlock();
exit(-1);
}
//************************************************************
// Message
Message::Message(unsigned int iID, EKind eKind, time_t tTime, const char* szText)
{
m_iID = iID;
m_eKind = eKind;
m_tTime = tTime;
if (szText)
{
m_szText = strdup(szText);
}
else
{
m_szText = NULL;
}
}
Message::~ Message()
{
if (m_szText)
{
free(m_szText);
}
}
void Log::AppendMessage(Message::EKind eKind, const char * szText)
{
Message* pMessage = new Message(++m_iIDGen, eKind, time(NULL), szText);
m_Messages.push_back(pMessage);
while (m_Messages.size() > (unsigned int)g_pOptions->GetLogBufferSize())
{
Message* pMessage = m_Messages.front();
delete pMessage;
m_Messages.pop_front();
}
}
Log::Messages* Log::LockMessages()
{
m_mutexLog.Lock();
return &m_Messages;
}
void Log::UnlockMessages()
{
m_mutexLog.Unlock();
}
void Log::ResetLog()
{
remove(g_pOptions->GetLogFile());
}

118
Log.h Normal file
View File

@@ -0,0 +1,118 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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$
*
*/
#ifndef LOG_H
#define LOG_H
#include <deque>
#include <time.h>
#include "Thread.h"
void error(const char* msg, ...);
void warn(const char* msg, ...);
void info(const char* msg, ...);
void abort(const char* msg, ...);
#ifdef HAVE_VARIADIC_MACROS
void debug(const char* szFilename, const char* szFuncname, int iLineNr, const char* msg, ...);
#else
void debug(const char* msg, ...);
#endif
class Message
{
public:
enum EKind
{
mkInfo,
mkWarning,
mkError,
mkDebug
};
private:
unsigned int m_iID;
EKind m_eKind;
time_t m_tTime;
char* m_szText;
public:
Message(unsigned int iID, EKind eKind, time_t tTime, const char* szText);
~Message();
unsigned int GetID() { return m_iID; }
EKind GetKind() { return m_eKind; }
time_t GetTime() { return m_tTime; }
const char* GetText() { return m_szText; }
};
class Log
{
public:
typedef std::deque<Message*> Messages;
private:
Mutex m_mutexLog;
Messages m_Messages;
char* m_szLogFilename;
unsigned int m_iIDGen;
#ifdef DEBUG
bool m_bExtraDebug;
#endif
void Filelog(const char* msg, ...);
void AppendMessage(Message::EKind eKind, const char* szText);
friend void error(const char* msg, ...);
friend void warn(const char* msg, ...);
friend void info(const char* msg, ...);
friend void abort(const char* msg, ...);
#ifdef HAVE_VARIADIC_MACROS
friend void debug(const char* szFilename, const char* szFuncname, int iLineNr, const char* msg, ...);
#else
friend void debug(const char* msg, ...);
#endif
public:
Log();
~Log();
Messages* LockMessages();
void UnlockMessages();
void ResetLog();
};
#ifdef HAVE_VARIADIC_MACROS
#ifdef DEBUG
#define debug(...) debug(__FILE__, FUNCTION_MACRO_NAME, __LINE__, __VA_ARGS__)
#else
#define debug(...) do { } while(0)
#endif
#endif
extern Log* g_pLog;
#endif

View File

@@ -1,8 +1,8 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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
@@ -15,15 +15,43 @@
* 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/>.
* 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 <stdio.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include "nzbget.h"
#include "Util.h"
#include "LoggableFrontend.h"
#include "Log.h"
LoggableFrontend::LoggableFrontend()
{
debug("Creating LoggableFrontend");
m_iNeededLogEntries = 0;
m_bSummary = false;
m_bFileList = false;
}
void LoggableFrontend::Run()
{
debug("Entering LoggableFrontend-loop");
@@ -31,7 +59,7 @@ void LoggableFrontend::Run()
while (!IsStopped())
{
Update();
Wait(m_updateInterval);
usleep(m_iUpdateInterval * 1000);
}
// Printing the last messages
Update();
@@ -51,24 +79,23 @@ void LoggableFrontend::Update()
BeforePrint();
Log::Messages* pMessages = LockMessages();
if (!pMessages->empty())
{
GuardedMessageList messages = GuardMessages();
if (!messages->empty())
Message* pFirstMessage = pMessages->front();
int iStart = m_iNeededLogFirstID - pFirstMessage->GetID() + 1;
if (iStart < 0)
{
Message& firstMessage = messages->front();
int start = m_neededLogFirstId - firstMessage.GetId() + 1;
if (start < 0)
{
PrintSkip();
start = 0;
}
for (uint32 i = (uint32)start; i < messages->size(); i++)
{
PrintMessage(messages->at(i));
m_neededLogFirstId = messages->at(i).GetId();
}
PrintSkip();
iStart = 0;
}
for (unsigned int i = (unsigned int)iStart; i < pMessages->size(); i++)
{
PrintMessage((*pMessages)[i]);
m_iNeededLogFirstID = (*pMessages)[i]->GetID();
}
}
UnlockMessages();
PrintStatus();
@@ -77,16 +104,15 @@ void LoggableFrontend::Update()
fflush(stdout);
}
void LoggableFrontend::PrintMessage(Message& message)
void LoggableFrontend::PrintMessage(Message * pMessage)
{
#ifdef WIN32
CString cmsg = message.GetText();
CharToOem(cmsg, cmsg);
const char* msg = cmsg;
char* msg = strdup(pMessage->GetText());
CharToOem(msg, msg);
#else
const char* msg = message.GetText();
const char* msg = pMessage->GetText();
#endif
switch (message.GetKind())
switch (pMessage->GetKind())
{
case Message::mkDebug:
printf("[DEBUG] %s\n", msg);
@@ -100,10 +126,10 @@ void LoggableFrontend::PrintMessage(Message& message)
case Message::mkInfo:
printf("[INFO] %s\n", msg);
break;
case Message::mkDetail:
printf("[DETAIL] %s\n", msg);
break;
}
#ifdef WIN32
free(msg);
#endif
}
void LoggableFrontend::PrintSkip()

View File

@@ -1,8 +1,8 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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
@@ -15,7 +15,12 @@
* 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/>.
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Revision$
* $Date$
*
*/
@@ -28,13 +33,16 @@
class LoggableFrontend : public Frontend
{
protected:
virtual void Run();
virtual void Update();
virtual void BeforePrint() {};
virtual void BeforeExit() {};
virtual void PrintMessage(Message& message);
virtual void PrintStatus() {};
virtual void PrintSkip();
virtual void Run();
virtual void Update();
virtual void BeforePrint() {};
virtual void BeforeExit() {};
virtual void PrintMessage(Message* pMessage);
virtual void PrintStatus() {};
virtual void PrintSkip();
public:
LoggableFrontend();
};
#endif

View File

@@ -1,554 +1,18 @@
#
# 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/>.
#
bin_PROGRAMS = nzbget
nzbget_SOURCES = ArticleDownloader.cpp ArticleDownloader.h ColoredFrontend.cpp \
ColoredFrontend.h Connection.cpp Connection.h Decoder.cpp Decoder.h DiskState.cpp \
DiskState.h DownloadInfo.cpp DownloadInfo.h Frontend.cpp Frontend.h Log.cpp Log.h \
LoggableFrontend.cpp LoggableFrontend.h MessageBase.h NCursesFrontend.cpp NCursesFrontend.h \
NNTPConnection.cpp NNTPConnection.h NZBFile.cpp NZBFile.h NetAddress.cpp NetAddress.h \
NewsServer.cpp NewsServer.h Observer.cpp Observer.h Options.cpp Options.h \
ParChecker.cpp ParChecker.h PrePostProcessor.cpp PrePostProcessor.h \
QueueCoordinator.cpp QueueCoordinator.h QueueEditor.cpp QueueEditor.h RemoteClient.cpp \
RemoteClient.h RemoteServer.cpp RemoteServer.h ServerPool.cpp ServerPool.h Thread.cpp \
Thread.h Util.cpp Util.h nzbget.cpp nzbget.h
nzbget_SOURCES = \
daemon/connect/Connection.cpp \
daemon/connect/Connection.h \
daemon/connect/TlsSocket.cpp \
daemon/connect/TlsSocket.h \
daemon/connect/WebDownloader.cpp \
daemon/connect/WebDownloader.h \
daemon/extension/FeedScript.cpp \
daemon/extension/FeedScript.h \
daemon/extension/CommandScript.cpp \
daemon/extension/CommandScript.h \
daemon/extension/NzbScript.cpp \
daemon/extension/NzbScript.h \
daemon/extension/PostScript.cpp \
daemon/extension/PostScript.h \
daemon/extension/QueueScript.cpp \
daemon/extension/QueueScript.h \
daemon/extension/ScanScript.cpp \
daemon/extension/ScanScript.h \
daemon/extension/SchedulerScript.cpp \
daemon/extension/SchedulerScript.h \
daemon/extension/ScriptConfig.cpp \
daemon/extension/ScriptConfig.h \
daemon/feed/FeedCoordinator.cpp \
daemon/feed/FeedCoordinator.h \
daemon/feed/FeedFile.cpp \
daemon/feed/FeedFile.h \
daemon/feed/FeedFilter.cpp \
daemon/feed/FeedFilter.h \
daemon/feed/FeedInfo.cpp \
daemon/feed/FeedInfo.h \
daemon/frontend/ColoredFrontend.cpp \
daemon/frontend/ColoredFrontend.h \
daemon/frontend/Frontend.cpp \
daemon/frontend/Frontend.h \
daemon/frontend/LoggableFrontend.cpp \
daemon/frontend/LoggableFrontend.h \
daemon/frontend/NCursesFrontend.cpp \
daemon/frontend/NCursesFrontend.h \
daemon/main/CommandLineParser.cpp \
daemon/main/CommandLineParser.h \
daemon/main/DiskService.cpp \
daemon/main/DiskService.h \
daemon/main/Maintenance.cpp \
daemon/main/Maintenance.h \
daemon/main/nzbget.cpp \
daemon/main/nzbget.h \
daemon/main/Options.cpp \
daemon/main/Options.h \
daemon/main/WorkState.cpp \
daemon/main/WorkState.h \
daemon/main/Scheduler.cpp \
daemon/main/Scheduler.h \
daemon/main/StackTrace.cpp \
daemon/main/StackTrace.h \
daemon/nntp/ArticleDownloader.cpp \
daemon/nntp/ArticleDownloader.h \
daemon/nntp/ArticleWriter.cpp \
daemon/nntp/ArticleWriter.h \
daemon/nntp/Decoder.cpp \
daemon/nntp/Decoder.h \
daemon/nntp/NewsServer.cpp \
daemon/nntp/NewsServer.h \
daemon/nntp/NntpConnection.cpp \
daemon/nntp/NntpConnection.h \
daemon/nntp/ServerPool.cpp \
daemon/nntp/ServerPool.h \
daemon/nntp/StatMeter.cpp \
daemon/nntp/StatMeter.h \
daemon/postprocess/Cleanup.cpp \
daemon/postprocess/Cleanup.h \
daemon/postprocess/DupeMatcher.cpp \
daemon/postprocess/DupeMatcher.h \
daemon/postprocess/ParChecker.cpp \
daemon/postprocess/ParChecker.h \
daemon/postprocess/ParParser.cpp \
daemon/postprocess/ParParser.h \
daemon/postprocess/ParRenamer.cpp \
daemon/postprocess/ParRenamer.h \
daemon/postprocess/PrePostProcessor.cpp \
daemon/postprocess/PrePostProcessor.h \
daemon/postprocess/RarRenamer.cpp \
daemon/postprocess/RarRenamer.h \
daemon/postprocess/RarReader.cpp \
daemon/postprocess/RarReader.h \
daemon/postprocess/Rename.cpp \
daemon/postprocess/Rename.h \
daemon/postprocess/Repair.cpp \
daemon/postprocess/Repair.h \
daemon/postprocess/Unpack.cpp \
daemon/postprocess/Unpack.h \
daemon/postprocess/DirectUnpack.cpp \
daemon/postprocess/DirectUnpack.h \
daemon/queue/DirectRenamer.cpp \
daemon/queue/DirectRenamer.h \
daemon/queue/DiskState.cpp \
daemon/queue/DiskState.h \
daemon/queue/DownloadInfo.cpp \
daemon/queue/DownloadInfo.h \
daemon/queue/DupeCoordinator.cpp \
daemon/queue/DupeCoordinator.h \
daemon/queue/HistoryCoordinator.cpp \
daemon/queue/HistoryCoordinator.h \
daemon/queue/NzbFile.cpp \
daemon/queue/NzbFile.h \
daemon/queue/QueueCoordinator.cpp \
daemon/queue/QueueCoordinator.h \
daemon/queue/QueueEditor.cpp \
daemon/queue/QueueEditor.h \
daemon/queue/Scanner.cpp \
daemon/queue/Scanner.h \
daemon/queue/UrlCoordinator.cpp \
daemon/queue/UrlCoordinator.h \
daemon/remote/BinRpc.cpp \
daemon/remote/BinRpc.h \
daemon/remote/MessageBase.h \
daemon/remote/RemoteClient.cpp \
daemon/remote/RemoteClient.h \
daemon/remote/RemoteServer.cpp \
daemon/remote/RemoteServer.h \
daemon/remote/WebServer.cpp \
daemon/remote/WebServer.h \
daemon/remote/XmlRpc.cpp \
daemon/remote/XmlRpc.h \
daemon/util/Log.cpp \
daemon/util/Log.h \
daemon/util/NString.cpp \
daemon/util/NString.h \
daemon/util/Container.h \
daemon/util/Observer.cpp \
daemon/util/Observer.h \
daemon/util/Script.cpp \
daemon/util/Script.h \
daemon/util/Thread.cpp \
daemon/util/Thread.h \
daemon/util/Service.cpp \
daemon/util/Service.h \
daemon/util/FileSystem.cpp \
daemon/util/FileSystem.h \
daemon/util/Util.cpp \
daemon/util/Util.h \
daemon/nserv/NServMain.h \
daemon/nserv/NServMain.cpp \
daemon/nserv/NServFrontend.h \
daemon/nserv/NServFrontend.cpp \
daemon/nserv/NntpServer.h \
daemon/nserv/NntpServer.cpp \
daemon/nserv/NzbGenerator.h \
daemon/nserv/NzbGenerator.cpp \
daemon/nserv/YEncoder.h \
daemon/nserv/YEncoder.cpp \
code_revision.cpp
if WITH_PAR2
nzbget_SOURCES += \
lib/par2/commandline.cpp \
lib/par2/commandline.h \
lib/par2/crc.cpp \
lib/par2/crc.h \
lib/par2/creatorpacket.cpp \
lib/par2/creatorpacket.h \
lib/par2/criticalpacket.cpp \
lib/par2/criticalpacket.h \
lib/par2/datablock.cpp \
lib/par2/datablock.h \
lib/par2/descriptionpacket.cpp \
lib/par2/descriptionpacket.h \
lib/par2/diskfile.cpp \
lib/par2/diskfile.h \
lib/par2/filechecksummer.cpp \
lib/par2/filechecksummer.h \
lib/par2/galois.cpp \
lib/par2/galois.h \
lib/par2/letype.h \
lib/par2/mainpacket.cpp \
lib/par2/mainpacket.h \
lib/par2/md5.cpp \
lib/par2/md5.h \
lib/par2/par2cmdline.h \
lib/par2/par2fileformat.cpp \
lib/par2/par2fileformat.h \
lib/par2/par2repairer.cpp \
lib/par2/par2repairer.h \
lib/par2/par2repairersourcefile.cpp \
lib/par2/par2repairersourcefile.h \
lib/par2/parheaders.cpp \
lib/par2/parheaders.h \
lib/par2/recoverypacket.cpp \
lib/par2/recoverypacket.h \
lib/par2/reedsolomon.cpp \
lib/par2/reedsolomon.h \
lib/par2/verificationhashtable.cpp \
lib/par2/verificationhashtable.h \
lib/par2/verificationpacket.cpp \
lib/par2/verificationpacket.h
endif
# Simd decoder and Crc32
nzbget_SOURCES += \
lib/yencode/YEncode.h \
lib/yencode/SimdInit.cpp \
lib/yencode/SimdDecoder.cpp \
lib/yencode/ScalarDecoder.cpp \
lib/yencode/Sse2Decoder.cpp \
lib/yencode/Ssse3Decoder.cpp \
lib/yencode/PclmulCrc.cpp \
lib/yencode/NeonDecoder.cpp \
lib/yencode/AcleCrc.cpp \
lib/yencode/SliceCrc.cpp
lib/yencode/Sse2Decoder.$(OBJEXT) : CXXFLAGS+=$(SSE2_CXXFLAGS)
lib/yencode/Ssse3Decoder.$(OBJEXT) : CXXFLAGS+=$(SSSE3_CXXFLAGS)
lib/yencode/PclmulCrc.$(OBJEXT) : CXXFLAGS+=$(PCLMUL_CXXFLAGS)
lib/yencode/NeonDecoder.$(OBJEXT) : CXXFLAGS+=$(NEON_CXXFLAGS)
lib/yencode/AcleCrc.$(OBJEXT) : CXXFLAGS+=$(ACLECRC_CXXFLAGS)
AM_CPPFLAGS = \
-I$(srcdir)/daemon/connect \
-I$(srcdir)/daemon/extension \
-I$(srcdir)/daemon/feed \
-I$(srcdir)/daemon/frontend \
-I$(srcdir)/daemon/main \
-I$(srcdir)/daemon/nntp \
-I$(srcdir)/daemon/postprocess \
-I$(srcdir)/daemon/queue \
-I$(srcdir)/daemon/remote \
-I$(srcdir)/daemon/util \
-I$(srcdir)/daemon/nserv \
-I$(srcdir)/lib/par2 \
-I$(srcdir)/lib/yencode
if WITH_TESTS
nzbget_SOURCES += \
lib/catch/catch.h \
tests/suite/TestMain.cpp \
tests/suite/TestMain.h \
tests/suite/TestUtil.cpp \
tests/suite/TestUtil.h \
tests/main/CommandLineParserTest.cpp \
tests/main/OptionsTest.cpp \
tests/feed/FeedFilterTest.cpp \
tests/postprocess/DupeMatcherTest.cpp \
tests/postprocess/RarRenamerTest.cpp \
tests/postprocess/RarReaderTest.cpp \
tests/postprocess/DirectUnpackTest.cpp \
tests/queue/NzbFileTest.cpp \
tests/nntp/ServerPoolTest.cpp \
tests/util/FileSystemTest.cpp \
tests/util/NStringTest.cpp \
tests/util/UtilTest.cpp
if WITH_PAR2
nzbget_SOURCES += \
tests/postprocess/ParCheckerTest.cpp \
tests/postprocess/ParRenamerTest.cpp
endif
AM_CPPFLAGS += \
-I$(srcdir)/lib/catch \
-I$(srcdir)/tests/suite
endif
EXTRA_DIST = \
$(windows_FILES) \
$(osx_FILES) \
$(linux_FILES) \
$(testdata_FILES) \
$(par2doc_FILES)
windows_FILES = \
daemon/windows/StdAfx.cpp \
daemon/windows/WinService.cpp \
daemon/windows/WinService.h \
daemon/windows/WinConsole.cpp \
daemon/windows/WinConsole.h \
nzbget.vcxproj \
windows/nzbget-command-shell.bat \
windows/install-update.bat \
windows/README-WINDOWS.txt \
windows/package-info.json \
windows/resources/mainicon.ico \
windows/resources/nzbget.rc \
windows/resources/resource.h \
windows/resources/trayicon_idle.ico \
windows/resources/trayicon_paused.ico \
windows/resources/trayicon_working.ico \
windows/resources/install.bmp \
windows/resources/uninstall.bmp \
windows/nzbget-setup.nsi
osx_FILES = \
osx/App_Prefix.pch \
osx/NZBGet-Info.plist \
osx/DaemonController.h \
osx/DaemonController.m \
osx/MainApp.h \
osx/MainApp.m \
osx/MainApp.xib \
osx/PFMoveApplication.h \
osx/PFMoveApplication.m \
osx/PreferencesDialog.h \
osx/PreferencesDialog.m \
osx/PreferencesDialog.xib \
osx/RPC.h \
osx/RPC.m \
osx/WebClient.h \
osx/WebClient.m \
osx/WelcomeDialog.h \
osx/WelcomeDialog.m \
osx/WelcomeDialog.xib \
osx/NZBGet.xcodeproj/project.pbxproj \
osx/Resources/Images/mainicon.icns \
osx/Resources/Images/statusicon.png \
osx/Resources/Images/statusicon@2x.png \
osx/Resources/licenses/license-bootstrap.txt \
osx/Resources/licenses/license-jquery-GPL.txt \
osx/Resources/licenses/license-jquery-MIT.txt \
osx/Resources/Credits.rtf \
osx/Resources/Localizable.strings \
osx/Resources/Welcome.rtf
linux_FILES = \
linux/installer.sh \
linux/install-update.sh \
linux/package-info.json \
linux/build-info.txt \
linux/build-nzbget \
linux/build-unpack \
linux/build-toolchain-android \
linux/build-toolchain-freebsd
doc_FILES = \
README \
ChangeLog \
COPYING
par2doc_FILES = \
lib/par2/AUTHORS \
lib/par2/README
exampleconf_FILES = \
nzbget.conf
webui_FILES = \
webui/index.html \
webui/index.js \
webui/downloads.js \
webui/edit.js \
webui/fasttable.js \
webui/history.js \
webui/messages.js \
webui/status.js \
webui/style.css \
webui/upload.js \
webui/util.js \
webui/config.js \
webui/feed.js \
webui/lib/bootstrap.js \
webui/lib/bootstrap.min.js \
webui/lib/bootstrap.css \
webui/lib/jquery.js \
webui/lib/jquery.min.js \
webui/lib/raphael.js \
webui/lib/raphael.min.js \
webui/lib/elycharts.js \
webui/lib/elycharts.min.js \
webui/img/icons.png \
webui/img/icons-2x.png \
webui/img/transmit.gif \
webui/img/transmit-file.gif \
webui/img/favicon.ico \
webui/img/download-anim-green-2x.png \
webui/img/download-anim-orange-2x.png \
webui/img/transmit-reload-2x.gif \
webui/img/favicon-256x256-opaque.png \
webui/img/favicon-256x256.png
scripts_FILES = \
scripts/EMail.py \
scripts/Logger.py
testdata_FILES = \
tests/testdata/dupematcher1/testfile.part01.rar \
tests/testdata/dupematcher1/testfile.part24.rar \
tests/testdata/dupematcher2/testfile.part04.rar \
tests/testdata/dupematcher2/testfile.part43.rar \
tests/testdata/nzbfile/dotless.nzb \
tests/testdata/nzbfile/dotless.txt \
tests/testdata/nzbfile/plain.nzb \
tests/testdata/nzbfile/plain.txt \
tests/testdata/parchecker/crc.txt \
tests/testdata/parchecker/testfile.dat \
tests/testdata/parchecker/testfile.nfo \
tests/testdata/parchecker/testfile.par2 \
tests/testdata/parchecker/testfile.vol00+1.PAR2 \
tests/testdata/parchecker/testfile.vol01+2.PAR2 \
tests/testdata/parchecker/testfile.vol03+3.PAR2 \
tests/testdata/parchecker2/crc.txt \
tests/testdata/parchecker2/testfile.7z.001 \
tests/testdata/parchecker2/testfile.7z.002 \
tests/testdata/parchecker2/testfile.7z.003 \
tests/testdata/parchecker2/testfile.7z.par2 \
tests/testdata/parchecker2/testfile.7z.vol0+1.PAR2 \
tests/testdata/parchecker2/testfile.7z.vol1+2.PAR2 \
tests/testdata/parchecker2/testfile.7z.vol3+3.PAR2 \
tests/testdata/rarrenamer/testfile3.part01.rar \
tests/testdata/rarrenamer/testfile3.part02.rar \
tests/testdata/rarrenamer/testfile3.part03.rar \
tests/testdata/rarrenamer/testfile5.part01.rar \
tests/testdata/rarrenamer/testfile5.part02.rar \
tests/testdata/rarrenamer/testfile5.part03.rar \
tests/testdata/rarrenamer/testfile3oldnam.rar \
tests/testdata/rarrenamer/testfile3oldnam.r00 \
tests/testdata/rarrenamer/testfile3oldnam.r01 \
tests/testdata/rarrenamer/testfile3encdata.part01.rar \
tests/testdata/rarrenamer/testfile3encdata.part02.rar \
tests/testdata/rarrenamer/testfile3encdata.part03.rar \
tests/testdata/rarrenamer/testfile3encnam.part01.rar \
tests/testdata/rarrenamer/testfile3encnam.part02.rar \
tests/testdata/rarrenamer/testfile3encnam.part03.rar \
tests/testdata/rarrenamer/testfile5encdata.part01.rar \
tests/testdata/rarrenamer/testfile5encdata.part02.rar \
tests/testdata/rarrenamer/testfile5encdata.part03.rar \
tests/testdata/rarrenamer/testfile5encnam.part01.rar \
tests/testdata/rarrenamer/testfile5encnam.part02.rar \
tests/testdata/rarrenamer/testfile5encnam.part03.rar
# Install
dist_doc_DATA = $(doc_FILES)
exampleconfdir = $(datadir)/nzbget
dist_exampleconf_DATA = $(exampleconf_FILES)
webuidir = $(datadir)/nzbget
nobase_dist_webui_DATA = $(webui_FILES)
scriptsdir = $(datadir)/nzbget
nobase_dist_scripts_SCRIPTS = $(scripts_FILES)
# Note about "sed":
# We need to make some changes in installed files.
# On Linux "sed" has option "-i" for in-place-edit. Unfortunateley the BSD version of "sed"
# has incompatible syntax. To solve the problem we perform in-place-edit in three steps:
# 1) copy the original file to original.temp (delete existing original.temp, if any);
# 2) sed < original.temp > original
# 3) delete original.temp
# These steps ensure that the output file has the same permissions as the original file.
# Prepare example configuration file
install-data-hook:
rm -f "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp"
cp "$(DESTDIR)$(exampleconfdir)/nzbget.conf" "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp"
sed 's:^ConfigTemplate=:ConfigTemplate=$(exampleconfdir)/nzbget.conf:' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf"
sed 's:configuration file (typically installed:configuration file (installed:' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp"
sed 's:/usr/local/share/nzbget/nzbget.conf):$(exampleconfdir)/nzbget.conf):' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf"
sed 's:^WebDir=:WebDir=$(webuidir)/webui:' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp"
sed 's:typically installed to /usr/local/share/nzbget/scripts:installed to $(scriptsdir)/scripts:' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf"
rm "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp"
# Install configuration files into /etc
# (only if they do not exist there to prevent override by update)
install-conf:
if test ! -f "$(DESTDIR)$(sysconfdir)/nzbget.conf" ; then \
$(mkinstalldirs) "$(DESTDIR)$(sysconfdir)" ; \
cp "$(DESTDIR)$(exampleconfdir)/nzbget.conf" "$(DESTDIR)$(sysconfdir)/nzbget.conf" ; \
fi
uninstall-conf:
rm -f "$(DESTDIR)$(sysconfdir)/nzbget.conf"
# Determining git revision:
# 1) If directory ".git" exists we take revision from git log.
# File is recreated only if revision number was changed.
# 2) If directory ".git" doesn't exists we keep and reuse file "code_revision.cpp",
# which was possibly created early.
# 3) If neither directory ".git" nor file "code_revision.cpp" are available
# we create new file "code_revision.c" with empty revision number.
code_revision.cpp: FORCE
@ if test -d ./.git ; then \
B=`git branch | sed -n -e 's/^\* \(.*\)/\1/p'`; \
M=`git status --porcelain` ; \
if test "$$M" != "" ; then \
M="M" ; \
fi ; \
if test "$$B" = "master" ; then \
V="$$M" ; \
elif test "$$B" = "develop" ; then \
V=`git rev-list HEAD | wc -l | xargs` ; \
V="$${V}$$M" ; \
else \
V=`git rev-list HEAD | wc -l | xargs` ; \
V="$${V}$$M ($$B)" ; \
fi ; \
H=`test -f ./code_revision.cpp && head -n 1 code_revision.cpp`; \
if test "/* $$V */" != "$$H" ; then \
( \
echo "/* $$V */" ;\
echo "/* This file is automatically regenerated on each build. Do not edit it. */" ;\
echo "#include \"nzbget.h\"" ;\
echo "const char* code_revision(void)" ;\
echo "{" ;\
echo " const char* revision = \"$$V\";" ;\
echo " return revision;" ;\
echo "}" ;\
) > code_revision.cpp ; \
fi \
elif test -f ./code_revision.cpp ; then \
test "ok, reuse existing file"; \
else \
( \
echo "/* */" ;\
echo "/* This file is automatically regenerated on each build. Do not edit it. */" ;\
echo "#include \"nzbget.h\"" ;\
echo "const char* code_revision(void)" ;\
echo "{" ;\
echo " const char* revision = \"\";" ;\
echo " return revision;" ;\
echo "}" ;\
) > code_revision.cpp ; \
fi
FORCE:
# Ignore "code_revision.cpp" in distcleancheck
distcleancheck_listfiles = \
find . -type f -exec sh -c 'test -f $(srcdir)/$$1 || echo $$1' \
sh '{}' ';'
EXTRA_DIST = nzbget.conf.example \
win32.h NTService.cpp NTService.h \
libpar2-0.2-MSVC8.patch libsigc++-2.0.18-MSVC8.patch \
Makefile.cvs nzbget.kdevelop nzbget.sln nzbget.vcproj
clean-bak: rm *~
# Fix premissions
dist-hook:
find $(distdir)/daemon -type f -print -exec chmod -x {} \;
find $(distdir)/webui -type f -print -exec chmod -x {} \;
find $(distdir)/lib -type f -print -exec chmod -x {} \;
find $(distdir)/tests -type f -print -exec chmod -x {} \;

8
Makefile.cvs Normal file
View File

@@ -0,0 +1,8 @@
default: all
all:
aclocal
autoheader
automake
autoconf

View File

File diff suppressed because it is too large Load Diff

302
MessageBase.h Normal file
View File

@@ -0,0 +1,302 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2005 Bo Cordes Petersen <placebodk@users.sourceforge.net>
* Copyright (C) 2007 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$
*
*/
#ifndef MESSAGEBASE_H
#define MESSAGEBASE_H
static const int32_t NZBMESSAGE_SIGNATURE = 0x6E7A6202; // = "nzb2" (nzb version 2)
static const int NZBREQUESTFILENAMESIZE = 512;
static const int NZBREQUESTPASSWORDSIZE = 32;
/**
* NZBGet communication protocol uses only two basic data types: integer and char.
* Integer values are passed using network byte order (Big-Endian).
* Use function "htonl" and "ntohl" to convert integers to/from machine
' (host) byte order.
* All char-strings must end with NULL-char.
*/
// The pack-directive prevents aligning of structs.
// This makes them more portable and allows to use together servers and clients
// compiled on different cpu architectures
#ifdef HAVE_PRAGMA_PACK
#pragma pack(1)
#endif
// Possible values for field "m_iType" of struct "SNZBRequestBase":
enum eRemoteRequest
{
eRemoteRequestDownload = 1,
eRemoteRequestPauseUnpause,
eRemoteRequestList,
eRemoteRequestSetDownloadRate,
eRemoteRequestDumpDebug,
eRemoteRequestEditQueue,
eRemoteRequestLog,
eRemoteRequestShutdown,
eRemoteRequestVersion
};
// Possible values for field "m_iAction" of struct "SNZBEditQueueRequest":
// File-Actions affect one file, Group-Actions affect all files in group.
// Group is a list of files, added to queue from one NZB-File.
enum eRemoteEditAction
{
eRemoteEditActionFileMoveOffset = 1, // move to m_iOffset relative to the current position in queue
eRemoteEditActionFileMoveTop, // move to top of queue
eRemoteEditActionFileMoveBottom, // move to bottom of queue
eRemoteEditActionFilePause, // pause
eRemoteEditActionFileResume, // resume (unpause)
eRemoteEditActionFileDelete, // delete
eRemoteEditActionFilePauseAllPars, // pause only (all) pars (does not affect other files)
eRemoteEditActionFilePauseExtraPars, // pause only (almost all) pars, except main par-file (does not affect other files)
eRemoteEditActionGroupMoveOffset, // move to m_iOffset relative to the current position in queue
eRemoteEditActionGroupMoveTop, // move to top of queue
eRemoteEditActionGroupMoveBottom, // move to bottom of queue
eRemoteEditActionGroupPause, // pause
eRemoteEditActionGroupResume, // resume (unpause)
eRemoteEditActionGroupDelete, // delete
eRemoteEditActionGroupPauseAllPars, // pause only (all) pars (does not affect other files)
eRemoteEditActionGroupPauseExtraPars // pause only (almost all) pars, except main par-file (does not affect other files)
};
// The basic SNZBRequestBase struct, used in all requests
struct SNZBRequestBase
{
int32_t m_iSignature; // Signature must be NZBMESSAGE_SIGNATURE in integer-value
int32_t m_iStructSize; // Size of the entire struct
int32_t m_iType; // Message type, see enum in NZBMessageRequest-namespace
char m_szPassword[NZBREQUESTPASSWORDSIZE]; // Password needs to be in every request
};
// The basic SNZBResposneBase struct, used in all responses
struct SNZBResponseBase
{
int32_t m_iSignature; // Signature must be NZBMESSAGE_SIGNATURE in integer-value
int32_t m_iStructSize; // Size of the entire struct
};
// A download request
struct SNZBDownloadRequest
{
SNZBRequestBase m_MessageBase; // Must be the first in the struct
char m_szFilename[NZBREQUESTFILENAMESIZE]; // Name of nzb-file, may contain full path (local path on client) or only filename
int32_t m_bAddFirst; // 1 - add file to the top of download queue
int32_t m_iTrailingDataLength; // Length of nzb-file in bytes
//char m_szContent[m_iTrailingDataLength]; // variable sized
};
// A download response
struct SNZBDownloadResponse
{
SNZBResponseBase m_MessageBase; // Must be the first in the struct
int32_t m_bSuccess; // 0 - command failed, 1 - command executed successfully
int32_t m_iTrailingDataLength; // Length of Text-string (m_szText), following to this record
//char m_szText[m_iTrailingDataLength]; // variable sized
};
// A list and status request
struct SNZBListRequest
{
SNZBRequestBase m_MessageBase; // Must be the first in the struct
int32_t m_bFileList; // 1 - return file list
int32_t m_bServerState; // 1 - return server state
};
// A list response
struct SNZBListResponse
{
SNZBResponseBase m_MessageBase; // Must be the first in the struct
int32_t m_iEntrySize; // Size of the SNZBListResponseEntry-struct
int32_t m_iRemainingSizeLo; // Remaining size in bytes, Low 32-bits of 64-bit value
int32_t m_iRemainingSizeHi; // Remaining size in bytes, High 32-bits of 64-bit value
int32_t m_iDownloadRate; // Current download speed, in Bytes pro Second
int32_t m_iDownloadLimit; // Current download limit, in Bytes pro Second
int32_t m_bServerPaused; // 1 - server is currently in paused-state
int32_t m_iThreadCount; // Number of threads running
int32_t m_iParJobCount; // Number of ParJobs in Par-Checker queue (including current file)
int32_t m_iUpTimeSec; // Server up time in seconds
int32_t m_iDownloadTimeSec; // Server download time in seconds (up_time - standby_time)
int32_t m_iDownloadedBytesLo; // Amount of data downloaded since server start, Low 32-bits of 64-bit value
int32_t m_iDownloadedBytesHi; // Amount of data downloaded since server start, High 32-bits of 64-bit value
int32_t m_bServerStandBy; // 0 - there are currently downloads running, 1 - no downloads in progress (server paused or all jobs completed)
int32_t m_iNrTrailingEntries; // Number of List-entries, following to this structure
int32_t m_iTrailingDataLength; // Length of all List-entries, following to this structure
// SNZBListResponseEntry m_Entries[m_iNrTrailingEntries] // variable sized
};
// A list response entry
struct SNZBListResponseEntry
{
int32_t m_iID; // Entry-ID
int32_t m_iFileSizeLo; // Filesize in bytes, Low 32-bits of 64-bit value
int32_t m_iFileSizeHi; // Filesize in bytes, High 32-bits of 64-bit value
int32_t m_iRemainingSizeLo; // Remaining size in bytes, Low 32-bits of 64-bit value
int32_t m_iRemainingSizeHi; // Remaining size in bytes, High 32-bits of 64-bit value
int32_t m_bPaused; // 1 - file is paused
int32_t m_bFilenameConfirmed; // 1 - Filename confirmed (read from article body), 0 - Filename parsed from subject (can be changed after reading of article)
int32_t m_iNZBFilenameLen; // Length of NZBFileName-string (m_szNZBFilename), following to this record
int32_t m_iSubjectLen; // Length of Subject-string (m_szSubject), following to this record
int32_t m_iFilenameLen; // Length of Filename-string (m_szFilename), following to this record
int32_t m_iDestDirLen; // Length of DestDir-string (m_szDestDir), following to this record
//char m_szNZBFilename[m_iNZBFilenameLen]; // variable sized, may contain full path (local path on client) or only filename
//char m_szSubject[m_iSubjectLen]; // variable sized
//char m_szFilename[m_iFilenameLen]; // variable sized
//char m_szDestDir[m_iDestDirLen]; // variable sized
};
// A log request
struct SNZBLogRequest
{
SNZBRequestBase m_MessageBase; // Must be the first in the struct
int32_t m_iIDFrom; // Only one of these two parameters
int32_t m_iLines; // can be set. The another one must be set to "0".
};
// A log response
struct SNZBLogResponse
{
SNZBResponseBase m_MessageBase; // Must be the first in the struct
int32_t m_iEntrySize; // Size of the SNZBLogResponseEntry-struct
int32_t m_iNrTrailingEntries; // Number of Log-entries, following to this structure
int32_t m_iTrailingDataLength; // Length of all Log-entries, following to this structure
// SNZBLogResponseEntry m_Entries[m_iNrTrailingEntries] // variable sized
};
// A log response entry
struct SNZBLogResponseEntry
{
int32_t m_iID; // ID of Log-entry
int32_t m_iKind; // see Message::Kind in "Log.h"
int32_t m_tTime; // time since the Epoch (00:00:00 UTC, January 1, 1970), measured in seconds.
int32_t m_iTextLen; // Length of Text-string (m_szText), following to this record
//char m_szText[m_iTextLen]; // variable sized
};
// A Pause/Unpause request
struct SNZBPauseUnpauseRequest
{
SNZBRequestBase m_MessageBase; // Must be the first in the struct
int32_t m_bPause; // 1 - server must be paused, 0 - server must be unpaused
};
// A Pause/Unpause response
struct SNZBPauseUnpauseResponse
{
SNZBResponseBase m_MessageBase; // Must be the first in the struct
int32_t m_bSuccess; // 0 - command failed, 1 - command executed successfully
int32_t m_iTrailingDataLength; // Length of Text-string (m_szText), following to this record
//char m_szText[m_iTrailingDataLength]; // variable sized
};
// Request setting the download rate
struct SNZBSetDownloadRateRequest
{
SNZBRequestBase m_MessageBase; // Must be the first in the struct
int32_t m_iDownloadRate; // Speed limit, in Bytes pro Second
};
// A setting download rate response
struct SNZBSetDownloadRateResponse
{
SNZBResponseBase m_MessageBase; // Must be the first in the struct
int32_t m_bSuccess; // 0 - command failed, 1 - command executed successfully
int32_t m_iTrailingDataLength; // Length of Text-string (m_szText), following to this record
//char m_szText[m_iTrailingDataLength]; // variable sized
};
// An edit queue request
struct SNZBEditQueueRequest
{
SNZBRequestBase m_MessageBase; // Must be the first in the struct
int32_t m_iAction; // Action to be executed, see enum in NZBMessageRequest-namespace
int32_t m_iOffset; // Offset to move (for m_iAction = 0)
int32_t m_bSmartOrder; // For Move-Actions: 0 - execute action for each ID in order they are placed in array;
// 1 - smart execute to ensure that the relative order of all affected IDs are not changed.
int32_t m_iNrTrailingEntries; // Number of ID-entries, following to this structure
int32_t m_iTrailingDataLength; // Length of all ID-entries, following to this structure
//int32_t m_iIDs[m_iNrTrailingEntries]; // variable sized array of IDs. For File-Actions - ID of file, for Group-Actions - ID of any file belonging to group
};
// An edit queue response
struct SNZBEditQueueResponse
{
SNZBResponseBase m_MessageBase; // Must be the first in the struct
int32_t m_bSuccess; // 0 - command failed, 1 - command executed successfully
int32_t m_iTrailingDataLength; // Length of Text-string (m_szText), following to this record
//char m_szText[m_iTrailingDataLength]; // variable sized
};
// Request dumping of debug info
struct SNZBDumpDebugRequest
{
SNZBRequestBase m_MessageBase; // Must be the first in the struct
};
// Dumping of debug response
struct SNZBDumpDebugResponse
{
SNZBResponseBase m_MessageBase; // Must be the first in the struct
int32_t m_bSuccess; // 0 - command failed, 1 - command executed successfully
int32_t m_iTrailingDataLength; // Length of Text-string (m_szText), following to this record
//char m_szText[m_iTrailingDataLength]; // variable sized
};
// Shutdown server request
struct SNZBShutdownRequest
{
SNZBRequestBase m_MessageBase; // Must be the first in the struct
};
// Shutdown server response
struct SNZBShutdownResponse
{
SNZBResponseBase m_MessageBase; // Must be the first in the struct
int32_t m_bSuccess; // 0 - command failed, 1 - command executed successfully
int32_t m_iTrailingDataLength; // Length of Text-string (m_szText), following to this record
//char m_szText[m_iTrailingDataLength]; // variable sized
};
// Server version request
struct SNZBVersionRequest
{
SNZBRequestBase m_MessageBase; // Must be the first in the struct
};
// Server version response
struct SNZBVersionResponse
{
SNZBResponseBase m_MessageBase; // Must be the first in the struct
int32_t m_bSuccess; // 0 - command failed, 1 - command executed successfully
int32_t m_iTrailingDataLength; // Length of Text-string (m_szText), following to this record
//char m_szText[m_iTrailingDataLength]; // variable sized
};
#ifdef HAVE_PRAGMA_PACK
#pragma pack()
#endif
#endif

1370
NCursesFrontend.cpp Normal file
View File

File diff suppressed because it is too large Load Diff

147
NCursesFrontend.h Normal file
View File

@@ -0,0 +1,147 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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$
*
*/
#ifndef NCURSESFRONTEND_H
#define NCURSESFRONTEND_H
#ifndef DISABLE_CURSES
#include <vector>
#include "Frontend.h"
#include "Log.h"
#include "DownloadInfo.h"
class NCursesFrontend : public Frontend
{
private:
enum EInputMode
{
eNormal,
eEditQueue,
eDownloadRate
};
class GroupInfo
{
private:
int m_iID;
char* m_szNZBFilename;
public:
int m_iFileCount;
long long m_lSize;
long long m_lRemainingSize;
long long m_lPausedSize;
int m_iParCount;
public:
GroupInfo(int iID, const char* szNZBFilename);
~GroupInfo();
int GetID() { return m_iID; }
const char* GetNZBFilename() { return m_szNZBFilename; }
long long GetSize() { return m_lSize; }
long long GetRemainingSize() { return m_lRemainingSize; }
long long GetPausedSize() { return m_lPausedSize; }
};
typedef std::deque<GroupInfo*> GroupQueue;
bool m_bUseColor;
int m_iDataUpdatePos;
int m_iScreenHeight;
int m_iScreenWidth;
int m_iQueueWinTop;
int m_iQueueWinHeight;
int m_iQueueWinClientHeight;
int m_iMessagesWinTop;
int m_iMessagesWinHeight;
int m_iMessagesWinClientHeight;
int m_iSelectedQueueEntry;
int m_iLastEditEntry;
bool m_bLastPausePars;
int m_iQueueScrollOffset;
GroupQueue m_groupQueue;
// Inputting numbres
int m_iInputNumberIndex;
int m_iInputValue;
#ifdef WIN32
CHAR_INFO* m_pScreenBuffer;
CHAR_INFO* m_pOldScreenBuffer;
int m_iScreenBufferSize;
std::vector<WORD> m_ColorAttr;
#else
void* m_pWindow; // WINDOW*
#endif
EInputMode m_eInputMode;
bool m_bShowNZBname;
bool m_bShowTimestamp;
bool m_bGroupFiles;
float m_QueueWindowPercentage;
#ifdef WIN32
void init_pair(int iColorNumber, WORD wForeColor, WORD wBackColor);
#endif
void PlotLine(const char * szString, int iRow, int iPos, int iColorPair);
void PlotText(const char * szString, int iRow, int iPos, int iColorPair, bool bBlink);
void PrintMessages();
void PrintQueue();
void PrintFileQueue();
void PrintFilename(FileInfo* pFileInfo, int iRow, bool bSelected);
void PrintGroupQueue();
void PrintGroupname(GroupInfo * pGroupInfo, int iRow, bool bSelected);
void PrepareGroupQueue();
void PrintTopHeader(char* szHeader, int iLineNr, bool bUpTime);
void ClearGroupQueue();
int PrintMessage(Message* Msg, int iRow, int iMaxLines);
void PrintKeyInputBar();
void PrintStatus();
void UpdateInput();
void Update();
void SetCurrentQueueEntry(int iEntry);
void CalcWindowSizes();
void FormatFileSize(char* szBuffer, int iBufLen, long long lFileSize);
void RefreshScreen();
int ReadConsoleKey();
int CalcQueueSize();
void NeedUpdateData();
bool EditQueue(QueueEditor::EEditAction eAction, int iOffset);
protected:
virtual void Run();
public:
NCursesFrontend();
virtual ~NCursesFrontend();
};
#endif
#endif

0
NEWS Normal file
View File

280
NNTPConnection.cpp Normal file
View File

@@ -0,0 +1,280 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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 "nzbget.h"
#include "Log.h"
#include "NNTPConnection.h"
#include "Connection.h"
#include "NewsServer.h"
static const int CONNECTION_LINEBUFFER_SIZE = 1024*10;
NNTPConnection::NNTPConnection(NewsServer* server) : Connection(server)
{
m_szActiveGroup = NULL;
m_szLineBuf = (char*)malloc(CONNECTION_LINEBUFFER_SIZE);
m_bAuthError = false;
}
NNTPConnection::~NNTPConnection()
{
if (m_szActiveGroup)
{
free(m_szActiveGroup);
m_szActiveGroup = NULL;
}
free(m_szLineBuf);
}
char* NNTPConnection::Request(char* req)
{
if (!req)
{
return NULL;
}
m_bAuthError = false;
WriteLine(req);
char* answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL);
if (!answer)
{
return NULL;
}
if (!strncmp(answer, "480", 3))
{
debug("%s requested authorization", m_pNetAddress->GetHost());
//authentication required!
if (!Authenticate())
{
m_bAuthError = true;
return NULL;
}
//try again
WriteLine(req);
answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL);
return answer;
}
return answer;
}
bool NNTPConnection::Authenticate()
{
if (!((NewsServer*)m_pNetAddress)->GetUser() ||
!((NewsServer*)m_pNetAddress)->GetPassword())
{
return false;
}
return AuthInfoUser();
}
bool NNTPConnection::AuthInfoUser(int iRecur)
{
if (iRecur > 10)
{
return false;
}
char tmp[1024];
snprintf(tmp, 1024, "AUTHINFO USER %s\r\n", ((NewsServer*)m_pNetAddress)->GetUser());
tmp[1024-1] = '\0';
WriteLine(tmp);
char* answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL);
if (!answer)
{
ReportError("authorization for %s failed: Connection closed by remote host.", m_pNetAddress->GetHost(), 0);
return false;
}
if (!strncmp(answer, "281", 3))
{
debug("authorization for %s successful", m_pNetAddress->GetHost());
return true;
}
else if (!strncmp(answer, "381", 3))
{
return AuthInfoPass(++iRecur);
}
else if (!strncmp(answer, "480", 3))
{
return AuthInfoUser(++iRecur);
}
if (char* p = strrchr(answer, '\r')) *p = '\0'; // remove last CRLF from error message
if (GetStatus() != csCancelled)
{
error("authorization for %s failed (Answer: %s)", m_pNetAddress->GetHost(), answer);
}
return false;
}
bool NNTPConnection::AuthInfoPass(int iRecur)
{
if (iRecur > 10)
{
return false;
}
char tmp[1024];
snprintf(tmp, 1024, "AUTHINFO PASS %s\r\n", ((NewsServer*)m_pNetAddress)->GetPassword());
tmp[1024-1] = '\0';
WriteLine(tmp);
char* answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL);
if (!answer)
{
ReportError("authorization for %s failed: Connection closed by remote host.", m_pNetAddress->GetHost(), 0);
return false;
}
else if (!strncmp(answer, "2", 1))
{
debug("authorization for %s successful", m_pNetAddress->GetHost());
return true;
}
else if (!strncmp(answer, "381", 3))
{
return AuthInfoPass(++iRecur);
}
if (char* p = strrchr(answer, '\r')) *p = '\0'; // remove last CRLF from error message
if (GetStatus() != csCancelled)
{
error("authorization for %s failed (Answer: %s)", m_pNetAddress->GetHost(), answer);
}
return false;
}
bool NNTPConnection::JoinGroup(char* grp)
{
if ((m_szActiveGroup) && (!strcmp(m_szActiveGroup, grp)))
{
// already in group
return true;
}
char tmp[1024];
snprintf(tmp, 1024, "GROUP %s\r\n", grp);
tmp[1024-1] = '\0';
char* answer = Request(tmp);
if (m_bAuthError)
{
return false;
}
if ((answer) && (!strncmp(answer, "2", 1)))
{
debug("Changed group to %s on %s", grp, GetServer()->GetHost());
if (m_szActiveGroup)
{
free(m_szActiveGroup);
}
m_szActiveGroup = strdup(grp);
return true;
}
if (GetStatus() != csCancelled)
{
if (!answer)
{
warn("Error changing group on %s: Connection closed by remote host.",
GetServer()->GetHost());
}
else
{
warn("Error changing group on %s to %s: Answer was \"%s\".",
GetServer()->GetHost(), grp, answer);
}
}
return false;
}
int NNTPConnection::DoConnect()
{
debug("Opening connection to %s", GetServer()->GetHost());
int res = Connection::DoConnect();
if (res < 0)
{
return res;
}
char* answer = DoReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL);
if (!answer)
{
ReportError("Connection to %s failed: Connection closed by remote host.", m_pNetAddress->GetHost(), 0);
return -1;
}
if (strncmp(answer, "2", 1))
{
error("Connection to %s failed. Answer: ", m_pNetAddress->GetHost(), answer);
return -1;
}
debug("Connection to %s established", GetServer()->GetHost());
return 0;
}
int NNTPConnection::DoDisconnect()
{
if (m_eStatus == csConnected)
{
Request("quit\r\n");
if (m_szActiveGroup)
{
free(m_szActiveGroup);
m_szActiveGroup = NULL;
}
}
return Connection::DoDisconnect();
}

58
NNTPConnection.h Normal file
View File

@@ -0,0 +1,58 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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$
*
*/
#ifndef NNTPCONNECTION_H
#define NNTPCONNECTION_H
#include "NewsServer.h"
#include "Connection.h"
class NNTPConnection : public Connection
{
private:
char* m_szActiveGroup;
char* m_szLineBuf;
bool m_bAuthError;
virtual int DoConnect();
virtual int DoDisconnect();
void Clear();
public:
NNTPConnection(NewsServer* server);
~NNTPConnection();
NewsServer* GetNewsServer() { return(NewsServer*)m_pNetAddress; }
char* Request(char* req);
bool Authenticate();
bool AuthInfoUser(int iRecur = 0);
bool AuthInfoPass(int iRecur = 0);
bool JoinGroup(char* grp);
bool GetAuthError() { return m_bAuthError; }
};
#endif

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
* This file if part of nzbget
*
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007 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
@@ -14,19 +14,32 @@
* 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/>.
* 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
#include "win32.h"
#include <string.h>
#include "nzbget.h"
#include "Log.h"
#include "WinService.h"
#include "NTService.h"
extern void ExitProc();
RunProc Run = nullptr;
RunProc Run = NULL;
char* strServiceName = "NZBGet";
SERVICE_STATUS_HANDLE nServiceStatusHandle;
SERVICE_STATUS_HANDLE nServiceStatusHandle;
DWORD nServiceCurrentStatus;
BOOL UpdateServiceStatus(DWORD dwCurrentState, DWORD dwWaitHint)
@@ -56,7 +69,7 @@ BOOL UpdateServiceStatus(DWORD dwCurrentState, DWORD dwWaitHint)
void ServiceCtrlHandler(DWORD nControlCode)
{
switch(nControlCode)
{
{
case SERVICE_CONTROL_SHUTDOWN:
case SERVICE_CONTROL_STOP:
nServiceCurrentStatus = SERVICE_STOP_PENDING;
@@ -89,7 +102,7 @@ void ServiceMain(DWORD argc, LPTSTR *argv)
{
return;
}
Run();
UpdateServiceStatus(SERVICE_STOPPED, 0);
@@ -102,7 +115,7 @@ void StartService(RunProc RunProcPtr)
SERVICE_TABLE_ENTRY servicetable[]=
{
{strServiceName,(LPSERVICE_MAIN_FUNCTION)ServiceMain},
{nullptr,nullptr}
{NULL,NULL}
};
BOOL success = StartServiceCtrlDispatcher(servicetable);
if(!success)
@@ -119,18 +132,16 @@ void InstallService(int argc, char *argv[])
printf("Could not install service\n");
return;
}
char exeName[1024];
GetModuleFileName(nullptr, exeName, 1024);
exeName[1024-1] = '\0';
BString<1024> cmdLine("%s -D", exeName);
char szCmdLine[1024];
snprintf(szCmdLine, 1024, "%s -D", argv[0]);
szCmdLine[1024-1] = '\0';
SC_HANDLE hService = CreateService(scm, strServiceName,
strServiceName,
SERVICE_ALL_ACCESS,SERVICE_WIN32_OWN_PROCESS,SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
cmdLine,
szCmdLine,
0,0,0,0,0);
if(!hService)
{
@@ -152,7 +163,7 @@ void UnInstallService()
printf("Could not uninstall service\n");
return;
}
SC_HANDLE hService = OpenService(scm, strServiceName, STANDARD_RIGHTS_REQUIRED);
if(!hService)
{
@@ -185,24 +196,3 @@ void InstallUninstallServiceCheck(int argc, char *argv[])
exit(0);
}
}
bool IsServiceRunning()
{
SC_HANDLE scm = OpenSCManager(0, 0, 0);
if (!scm)
{
return false;
}
SC_HANDLE hService = OpenService(scm, "NZBGet", SERVICE_QUERY_STATUS);
SERVICE_STATUS ServiceStatus;
bool running = false;
if (hService && QueryServiceStatus(hService, &ServiceStatus))
{
running = ServiceStatus.dwCurrentState != SERVICE_STOPPED;
}
CloseServiceHandle(scm);
return running;
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
* This file if part of nzbget
*
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007 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
@@ -14,17 +14,21 @@
* 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/>.
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Revision$
* $Date$
*
*/
#ifndef WINSERVICE_H
#define WINSERVICE_H
#ifndef NTSERVICE_H
#define NTSERVICE_H
typedef void (*RunProc)(void);
void InstallUninstallServiceCheck(int argc, char *argv[]);
void StartService(RunProc RunProcPtr);
bool IsServiceRunning();
#endif

582
NZBFile.cpp Normal file
View File

@@ -0,0 +1,582 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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 <string.h>
#include <list>
#ifdef WIN32
#include <comutil.h>
#import "MSXML.dll" named_guids
using namespace MSXML;
#else
#include <libxml/parser.h>
#include <libxml/xmlreader.h>
#endif
#include "nzbget.h"
#include "NZBFile.h"
#include "Log.h"
#include "DownloadInfo.h"
#include "Options.h"
#include "DiskState.h"
#include "Util.h"
extern Options* g_pOptions;
extern DiskState* g_pDiskState;
NZBFile::NZBFile(const char* szFileName)
{
debug("Creating NZBFile");
m_szFileName = strdup(szFileName);
m_FileInfos.clear();
}
NZBFile::~NZBFile()
{
debug("Destroying NZBFile");
// Cleanup
if (m_szFileName)
{
free(m_szFileName);
}
for (FileInfos::iterator it = m_FileInfos.begin(); it != m_FileInfos.end(); it++)
{
delete *it;
}
m_FileInfos.clear();
}
void NZBFile::LogDebugInfo()
{
debug(" NZBFile %s", m_szFileName);
}
void NZBFile::DetachFileInfos()
{
m_FileInfos.clear();
}
NZBFile* NZBFile::CreateFromBuffer(const char* szFileName, const char* szBuffer, int iSize)
{
return Create(szFileName, szBuffer, iSize, true);
}
NZBFile* NZBFile::CreateFromFile(const char* szFileName)
{
return Create(szFileName, NULL, 0, false);
}
void NZBFile::AddArticle(FileInfo* pFileInfo, ArticleInfo* pArticleInfo)
{
// make Article-List big enough
while ((int)pFileInfo->GetArticles()->size() < pArticleInfo->GetPartNumber())
pFileInfo->GetArticles()->push_back(NULL);
(*pFileInfo->GetArticles())[pArticleInfo->GetPartNumber() - 1] = pArticleInfo;
}
void NZBFile::AddFileInfo(FileInfo* pFileInfo)
{
// deleting empty articles
FileInfo::Articles* pArticles = pFileInfo->GetArticles();
int i = 0;
for (FileInfo::Articles::iterator it = pArticles->begin(); it != pArticles->end();)
{
if (*it == NULL)
{
pArticles->erase(it);
it = pArticles->begin() + i;
}
else
{
it++;
i++;
}
}
if (!pArticles->empty())
{
ParseSubject(pFileInfo);
BuildDestDirName(pFileInfo);
m_FileInfos.push_back(pFileInfo);
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
{
g_pDiskState->SaveFile(pFileInfo);
pFileInfo->ClearArticles();
}
}
else
{
delete pFileInfo;
}
}
void NZBFile::ParseSubject(FileInfo* pFileInfo)
{
// tokenize subject, considering spaces as separators and quotation
// marks as non separatable token delimiters.
// then take the last token containing dot (".") as a filename
typedef std::list<char*> TokenList;
TokenList tokens;
tokens.clear();
// tokenizing
char* p = (char*)pFileInfo->GetSubject();
char* start = p;
bool quot = false;
while (true)
{
char ch = *p;
bool sep = (ch == '\"') || (!quot && ch == ' ') || (ch == '\0');
if (sep)
{
// end of token
int len = p - start;
if (len > 0)
{
char* token = (char*)malloc(len + 1);
strncpy(token, start, len);
token[len] = '\0';
tokens.push_back(token);
}
start = p;
if (ch != '\"' || quot)
{
start++;
}
quot = *start == '\"';
if (quot)
{
start++;
char* q = strchr(start, '\"');
if (q)
{
p = q - 1;
}
else
{
quot = false;
}
}
}
if (ch == '\0')
{
break;
}
p++;
}
if (!tokens.empty())
{
// finding the best candidate for being a filename
char* besttoken = tokens.back();
for (TokenList::reverse_iterator it = tokens.rbegin(); it != tokens.rend(); it++)
{
char* s = *it;
char* p = strchr(s, '.');
if (p && (p[1] != '\0'))
{
besttoken = s;
break;
}
}
pFileInfo->SetFilename(besttoken);
// free mem
for (TokenList::iterator it = tokens.begin(); it != tokens.end(); it++)
{
free(*it);
}
}
else
{
// subject is empty or contains only separators?
debug("Could not extract Filename from Subject: %s. Using Subject as Filename", pFileInfo->GetSubject());
pFileInfo->SetFilename(pFileInfo->GetSubject());
}
pFileInfo->MakeValidFilename();
}
void NZBFile::BuildDestDirName(FileInfo* pFileInfo)
{
char szBuffer[1024];
if (g_pOptions->GetAppendNZBDir())
{
char szNiceNZBName[1024];
pFileInfo->GetNiceNZBName(szNiceNZBName, 1024);
snprintf(szBuffer, 1024, "%s%s", g_pOptions->GetDestDir(), szNiceNZBName);
szBuffer[1024-1] = '\0';
}
else
{
strncpy(szBuffer, g_pOptions->GetDestDir(), 1024);
szBuffer[1024-1] = '\0'; // trim the last slash, always returned by GetDestDir()
}
pFileInfo->SetDestDir(szBuffer);
}
/**
* Check if the parsing of subject was correct
*/
void NZBFile::CheckFilenames()
{
for (FileInfos::iterator it = m_FileInfos.begin(); it != m_FileInfos.end(); it++)
{
FileInfo* pFileInfo1 = *it;
int iDupe = 0;
for (FileInfos::iterator it2 = it + 1; it2 != m_FileInfos.end(); it2++)
{
FileInfo* pFileInfo2 = *it2;
if (!strcmp(pFileInfo1->GetFilename(), pFileInfo2->GetFilename()) &&
strcmp(pFileInfo1->GetSubject(), pFileInfo2->GetSubject()))
{
iDupe++;
}
}
// If more than two files have the same parsed filename but different subjects,
// this means, that the parsing was not correct.
// in this case we take subjects as filenames to prevent
// false "duplicate files"-alarm.
// It's Ok for just two files to have the same filename, this is
// an often case by posting-errors to repost bad files
if (iDupe > 2 || (iDupe == 2 && m_FileInfos.size() == 2))
{
for (FileInfos::iterator it2 = it; it2 != m_FileInfos.end(); it2++)
{
FileInfo* pFileInfo2 = *it2;
pFileInfo2->SetFilename(pFileInfo2->GetSubject());
pFileInfo2->MakeValidFilename();
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
{
g_pDiskState->LoadArticles(pFileInfo2);
g_pDiskState->SaveFile(pFileInfo2);
pFileInfo2->ClearArticles();
}
}
}
}
}
#ifdef WIN32
NZBFile* NZBFile::Create(const char* szFileName, const char* szBuffer, int iSize, bool bFromBuffer)
{
CoInitialize(NULL);
HRESULT hr;
MSXML::IXMLDOMDocumentPtr doc;
hr = doc.CreateInstance(MSXML::CLSID_DOMDocument);
if (FAILED(hr))
{
return NULL;
}
// Load the XML document file...
doc->put_resolveExternals(VARIANT_FALSE);
doc->put_validateOnParse(VARIANT_FALSE);
doc->put_async(VARIANT_FALSE);
VARIANT_BOOL success;
if (bFromBuffer)
{
success = doc->loadXML(szBuffer);
}
else
{
// filename needs to be properly encoded
char* szURL = (char*)malloc(strlen(szFileName)*3 + 1);
EncodeURL(szFileName, szURL);
debug("url=\"%s\"", szURL);
_variant_t v(szURL);
free(szURL);
success = doc->load(v);
}
if (success == VARIANT_FALSE)
{
_bstr_t r(doc->GetparseError()->reason);
const char* szErrMsg = r;
error("Error parsing nzb-file: %s", szErrMsg);
return NULL;
}
NZBFile* pFile = new NZBFile(szFileName);
if (pFile->ParseNZB(doc))
{
pFile->CheckFilenames();
}
else
{
delete pFile;
pFile = NULL;
}
return pFile;
}
void NZBFile::EncodeURL(const char* szFilename, char* szURL)
{
while (char ch = *szFilename++)
{
if (('0' <= ch && ch <= '9') ||
('a' <= ch && ch <= 'z') ||
('A' <= ch && ch <= 'Z') )
{
*szURL++ = ch;
}
else
{
*szURL++ = '%';
int a = ch >> 4;
*szURL++ = a > 9 ? a - 10 + 'a' : a + '0';
a = ch & 0xF;
*szURL++ = a > 9 ? a - 10 + 'a' : a + '0';
}
}
*szURL = NULL;
}
bool NZBFile::ParseNZB(IUnknown* nzb)
{
MSXML::IXMLDOMDocumentPtr doc = nzb;
MSXML::IXMLDOMNodePtr root = doc->documentElement;
MSXML::IXMLDOMNodeListPtr fileList = root->selectNodes("/nzb/file");
for (int i = 0; i < fileList->Getlength(); i++)
{
MSXML::IXMLDOMNodePtr node = fileList->Getitem(i);
MSXML::IXMLDOMNodePtr attribute = node->Getattributes()->getNamedItem("subject");
if (!attribute) return false;
_bstr_t subject(attribute->Gettext());
FileInfo* pFileInfo = new FileInfo();
pFileInfo->SetNZBFilename(m_szFileName);
pFileInfo->SetSubject(subject);
MSXML::IXMLDOMNodeListPtr groupList = node->selectNodes("groups/group");
for (int g = 0; g < groupList->Getlength(); g++)
{
MSXML::IXMLDOMNodePtr node = groupList->Getitem(g);
_bstr_t group = node->Gettext();
pFileInfo->GetGroups()->push_back(strdup((const char*)group));
}
MSXML::IXMLDOMNodeListPtr segmentList = node->selectNodes("segments/segment");
for (int g = 0; g < segmentList->Getlength(); g++)
{
MSXML::IXMLDOMNodePtr node = segmentList->Getitem(g);
_bstr_t id = node->Gettext();
char szId[2048];
snprintf(szId, 2048, "<%s>", (const char*)id);
MSXML::IXMLDOMNodePtr attribute = node->Getattributes()->getNamedItem("number");
if (!attribute) return false;
_bstr_t number(attribute->Gettext());
attribute = node->Getattributes()->getNamedItem("bytes");
if (!attribute) return false;
_bstr_t bytes(attribute->Gettext());
int partNumber = atoi(number);
int lsize = atoi(bytes);
ArticleInfo* pArticle = new ArticleInfo();
pArticle->SetPartNumber(partNumber);
pArticle->SetMessageID(szId);
pArticle->SetSize(lsize);
AddArticle(pFileInfo, pArticle);
if (lsize > 0)
{
pFileInfo->SetSize(pFileInfo->GetSize() + lsize);
}
}
AddFileInfo(pFileInfo);
}
return true;
}
#else
NZBFile* NZBFile::Create(const char* szFileName, const char* szBuffer, int iSize, bool bFromBuffer)
{
xmlTextReaderPtr doc;
if (bFromBuffer)
{
doc = xmlReaderForMemory(szBuffer, iSize-1, "", NULL, 0);
}
else
{
doc = xmlReaderForFile(szFileName, NULL, 0);
}
if (!doc)
{
return NULL;
}
NZBFile* pFile = new NZBFile(szFileName);
if (pFile->ParseNZB(doc))
{
pFile->CheckFilenames();
}
else
{
delete pFile;
pFile = NULL;
}
xmlFreeTextReader(doc);
return pFile;
}
bool NZBFile::ParseNZB(void* nzb)
{
FileInfo* pFileInfo = NULL;
xmlTextReaderPtr node = (xmlTextReaderPtr)nzb;
// walk through whole doc and search for segments-tags
int ret = xmlTextReaderRead(node);
while (ret == 1)
{
if (node)
{
xmlChar *name, *value;
name = xmlTextReaderName(node);
if (name == NULL)
{
name = xmlStrdup(BAD_CAST "--");
}
value = xmlTextReaderValue(node);
if (xmlTextReaderNodeType(node) == 1)
{
if (!strcmp("file", (char*)name))
{
pFileInfo = new FileInfo();
pFileInfo->SetNZBFilename(m_szFileName);
while (xmlTextReaderMoveToNextAttribute(node))
{
xmlFree(name);
name = xmlTextReaderName(node);
if (!strcmp("subject",(char*)name))
{
xmlFree(value);
value = xmlTextReaderValue(node);
pFileInfo->SetSubject((char*)value);
}
}
}
else if (!strcmp("segment",(char*)name))
{
long long lsize = -1;
int partNumber = -1;
while (xmlTextReaderMoveToNextAttribute(node))
{
xmlFree(name);
name = xmlTextReaderName(node);
xmlFree(value);
value = xmlTextReaderValue(node);
if (!strcmp("bytes",(char*)name))
{
lsize = atol((char*)value);
}
if (!strcmp("number",(char*)name))
{
partNumber = atol((char*)value);
}
}
if (lsize > 0)
{
pFileInfo->SetSize(pFileInfo->GetSize() + lsize);
}
/* Get the #text part */
ret = xmlTextReaderRead(node);
if (partNumber > 0)
{
// new segment, add it!
xmlFree(value);
value = xmlTextReaderValue(node);
char tmp[2048];
snprintf(tmp, 2048, "<%s>", (char*)value);
ArticleInfo* pArticle = new ArticleInfo();
pArticle->SetPartNumber(partNumber);
pArticle->SetMessageID(tmp);
pArticle->SetSize(lsize);
AddArticle(pFileInfo, pArticle);
}
}
else if (!strcmp("group",(char*)name))
{
ret = xmlTextReaderRead(node);
xmlFree(value);
value = xmlTextReaderValue(node);
pFileInfo->GetGroups()->push_back(strdup((char*)value));
}
}
if (xmlTextReaderNodeType(node) == 15)
{
/* Close the file element, add the new file to file-list */
if (!strcmp("file",(char*)name))
{
AddFileInfo(pFileInfo);
}
}
xmlFree(name);
xmlFree(value);
}
ret = xmlTextReaderRead(node);
}
if (ret != 0)
{
error("Failed to parse nzb-file\n");
return false;
}
return true;
}
#endif

68
NZBFile.h Normal file
View File

@@ -0,0 +1,68 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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$
*
*/
#ifndef NZBFILE_H
#define NZBFILE_H
#include <vector>
#include "DownloadInfo.h"
class NZBFile
{
public:
typedef std::vector<FileInfo*> FileInfos;
private:
FileInfos m_FileInfos;
char* m_szFileName;
NZBFile(const char* szFileName);
void AddArticle(FileInfo* pFileInfo, ArticleInfo* pArticleInfo);
void AddFileInfo(FileInfo* pFileInfo);
void ParseSubject(FileInfo* pFileInfo);
void BuildDestDirName(FileInfo* pFileInfo);
void CheckFilenames();
#ifdef WIN32
bool ParseNZB(IUnknown* nzb);
static void EncodeURL(const char* szFilename, char* szURL);
#else
bool ParseNZB(void* nzb);
#endif
static NZBFile* Create(const char* szFileName, const char* szBuffer, int iSize, bool bFromBuffer);
public:
virtual ~NZBFile();
static NZBFile* CreateFromBuffer(const char* szFileName, const char* szBuffer, int iSize);
static NZBFile* CreateFromFile(const char* szFileName);
const char* GetFileName() const { return m_szFileName; }
FileInfos* GetFileInfos() { return &m_FileInfos; }
void DetachFileInfos();
void LogDebugInfo();
};
#endif

54
NetAddress.cpp Normal file
View File

@@ -0,0 +1,54 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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 "nzbget.h"
#include "NetAddress.h"
NetAddress::NetAddress(const char* szHost, int iPort)
{
m_szHost = NULL;
m_iPort = iPort;
if (szHost)
m_szHost = strdup(szHost);
}
NetAddress::~NetAddress()
{
if (m_szHost)
free(m_szHost);
m_szHost = NULL;
}

View File

@@ -1,8 +1,8 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
* This file if part of nzbget
*
* Copyright (C) 2005 Bo Cordes Petersen <placebodk@sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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
@@ -15,28 +15,30 @@
* 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/>.
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Revision$
* $Date$
*
*/
#ifndef BINRPC_H
#define BINRPC_H
#ifndef NETADDRESS_H
#define NETADDRESS_H
#include "Connection.h"
#include "MessageBase.h"
class BinRpcProcessor
class NetAddress
{
public:
BinRpcProcessor();
void Execute();
void SetConnection(Connection* connection) { m_connection = connection; }
private:
SNzbRequestBase m_messageBase;
Connection* m_connection;
char* m_szHost;
int m_iPort;
void Dispatch();
public:
NetAddress(const char* szHost, int iPort);
virtual ~NetAddress();
const char* GetHost() { return m_szHost; }
int GetPort() { return m_iPort; }
};
#endif

65
NewsServer.cpp Normal file
View File

@@ -0,0 +1,65 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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 "nzbget.h"
#include "NewsServer.h"
#include "Log.h"
NewsServer::NewsServer(const char* host, int port, const char* user, const char* pass, int maxConnections, int level) : NetAddress(host, port)
{
m_szUser = NULL;
m_szPassword = NULL;
m_iLevel = level;
m_iMaxConnections = maxConnections;
if (pass)
{
m_szPassword = strdup(pass);
}
if (user)
{
m_szUser = strdup(user);
}
}
NewsServer::~NewsServer()
{
free(m_szUser);
m_szUser = NULL;
free(m_szPassword);
m_szPassword = NULL;
}

49
NewsServer.h Normal file
View File

@@ -0,0 +1,49 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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$
*
*/
#ifndef NEWSSERVER_H
#define NEWSSERVER_H
#include "NetAddress.h"
class NewsServer : public NetAddress
{
private:
char* m_szUser;
char* m_szPassword;
int m_iMaxConnections;
int m_iLevel;
public:
NewsServer(const char* host, int port, const char* user, const char* pass, int maxConnections, int level);
virtual ~NewsServer();
const char* GetUser() { return m_szUser; }
const char* GetPassword() { return m_szPassword; }
int GetMaxConnections() { return m_iMaxConnections; }
int GetLevel() { return m_iLevel; }
};
#endif

62
Observer.cpp Normal file
View File

@@ -0,0 +1,62 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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 "Observer.h"
#include "Log.h"
Subject::Subject()
{
m_Observers.clear();
}
void Subject::Attach(Observer* Observer)
{
m_Observers.push_back(Observer);
}
void Subject::Detach(Observer* Observer)
{
m_Observers.remove(Observer);
}
void Subject::Notify(void* Aspect)
{
debug("Notifying observers");
for (std::list<Observer*>::iterator it = m_Observers.begin(); it != m_Observers.end(); it++)
{
Observer* Observer = *it;
Observer->Update(this, Aspect);
}
}

View File

@@ -1,8 +1,8 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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
@@ -15,31 +15,39 @@
* 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/>.
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Revision$
* $Date$
*
*/
#ifndef OBSERVER_H
#define OBSERVER_H
#include <list>
class Observer;
class Subject
{
public:
void Attach(Observer* observer);
void Detach(Observer* observer);
void Notify(void* aspect);
private:
std::vector<Observer*> m_observers;
std::list<Observer*> m_Observers;
public:
Subject();
void Attach(Observer* Observer);
void Detach(Observer* Observer);
void Notify(void* Aspect);
};
class Observer
{
protected:
virtual void Update(Subject* caller, void* aspect) = 0;
friend class Subject;
public:
virtual ~Observer() {};
virtual void Update(Subject* Caller, void* Aspect) = 0;
};
#endif

1210
Options.cpp Normal file
View File

File diff suppressed because it is too large Load Diff

249
Options.h Normal file
View File

@@ -0,0 +1,249 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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$
*
*/
#ifndef OPTIONS_H
#define OPTIONS_H
#include <vector>
class Options
{
public:
enum EClientOperation
{
opClientNoOperation,
opClientRequestDownload,
opClientRequestList,
opClientRequestPause,
opClientRequestUnpause,
opClientRequestSetRate,
opClientRequestDumpDebug,
opClientRequestEditQueue,
opClientRequestLog,
opClientRequestShutdown,
opClientRequestVersion
};
enum EMessageTarget
{
mtNone,
mtScreen,
mtLog,
mtBoth
};
enum EDecoder
{
dcNone,
dcUulib,
dcYenc
};
enum EOutputMode
{
omLoggable,
omColored,
omNCurses
};
enum ELoadPars
{
plNone,
plOne,
plAll
};
private:
struct OptEntry
{
char* name;
char* value;
};
std::vector< struct OptEntry > optEntries;
bool m_bConfigInitialized;
// Options
char* m_szConfigFilename;
char* m_szDestDir;
char* m_szTempDir;
char* m_szQueueDir;
char* m_szNzbDir;
EMessageTarget m_eInfoTarget;
EMessageTarget m_eWarningTarget;
EMessageTarget m_eErrorTarget;
EMessageTarget m_eDebugTarget;
EDecoder m_eDecoder;
bool m_bCreateBrokenLog;
bool m_bResetLog;
int m_iConnectionTimeout;
int m_iTerminateTimeout;
bool m_bAppendNZBDir;
bool m_bContinuePartial;
bool m_bRenameBroken;
int m_iRetries;
int m_iRetryInterval;
bool m_bSaveQueue;
bool m_bDupeCheck;
char* m_szServerIP;
char* m_szServerPassword;
int m_szServerPort;
char* m_szLockFile;
char* m_szDaemonUserName;
EOutputMode m_eOutputMode;
bool m_bReloadQueue;
int m_iLogBufferSize;
bool m_bCreateLog;
char* m_szLogFile;
ELoadPars m_eLoadPars;
bool m_bParCheck;
bool m_bParRepair;
char* m_szPostProcess;
bool m_bStrictParName;
bool m_bNoConfig;
int m_iUMask;
int m_iUpdateInterval;
bool m_bCursesNZBName;
bool m_bCursesTime;
bool m_bCursesGroup;
bool m_bCrcCheck;
bool m_bRetryOnCrcError;
int m_iThreadLimit;
bool m_bDirectWrite;
int m_iWriteBufferSize;
int m_iNzbDirInterval;
int m_iNzbDirFileAge;
// Parsed command-line parameters
bool m_bServerMode;
bool m_bDaemonMode;
bool m_bRemoteClientMode;
int m_iEditQueueAction;
int m_iEditQueueOffset;
int* m_pEditQueueIDList;
int m_iEditQueueIDCount;
char* m_szArgFilename;
bool m_bPrintOptions;
bool m_bAddTop;
float m_fSetRate;
int m_iLogLines;
// Current state
bool m_bPause;
float m_fDownloadRate;
EClientOperation m_eClientOperation;
void InitDefault();
void InitOptFile();
void InitCommandLine(int argc, char* argv[]);
void InitOptions();
void InitFileArg(int argc, char* argv[]);
void InitServers();
void CheckOptions();
void PrintUsage(char* com);
void Dump();
int ParseOptionValue(const char* OptName, int argc, const char* argn[], const int argv[]);
const char* GetOption(const char* optname);
void DelOption(const char* optname);
void SetOption(const char* optname, const char* value);
bool SetOptionString(const char* option);
bool ValidateOptionName(const char* optname);
void LoadConfig(const char* configfile);
void CheckDir(char** dir, const char* szOptionName);
void ParseFileIDList(int argc, char* argv[], int optind);
public:
Options(int argc, char* argv[]);
~Options();
// Options
const char* GetDestDir() { return m_szDestDir; }
const char* GetTempDir() { return m_szTempDir; }
const char* GetQueueDir() { return m_szQueueDir; }
const char* GetNzbDir() { return m_szNzbDir; }
bool GetCreateBrokenLog() const { return m_bCreateBrokenLog; }
bool GetResetLog() const { return m_bResetLog; }
EMessageTarget GetInfoTarget() const { return m_eInfoTarget; }
EMessageTarget GetWarningTarget() const { return m_eWarningTarget; }
EMessageTarget GetErrorTarget() const { return m_eErrorTarget; }
EMessageTarget GetDebugTarget() const { return m_eDebugTarget; }
int GetConnectionTimeout() { return m_iConnectionTimeout; }
int GetTerminateTimeout() { return m_iTerminateTimeout; }
EDecoder GetDecoder() { return m_eDecoder; };
bool GetAppendNZBDir() { return m_bAppendNZBDir; }
bool GetContinuePartial() { return m_bContinuePartial; }
bool GetRenameBroken() { return m_bRenameBroken; }
int GetRetries() { return m_iRetries; }
int GetRetryInterval() { return m_iRetryInterval; }
bool GetSaveQueue() { return m_bSaveQueue; }
bool GetDupeCheck() { return m_bDupeCheck; }
char* GetServerIP() { return m_szServerIP; }
char* GetServerPassword() { return m_szServerPassword; }
int GetServerPort() { return m_szServerPort; }
char* GetLockFile() { return m_szLockFile; }
char* GetDaemonUserName() { return m_szDaemonUserName; }
EOutputMode GetOutputMode() { return m_eOutputMode; }
bool GetReloadQueue() { return m_bReloadQueue; }
int GetLogBufferSize() { return m_iLogBufferSize; }
bool GetCreateLog() { return m_bCreateLog; }
char* GetLogFile() { return m_szLogFile; }
ELoadPars GetLoadPars() { return m_eLoadPars; }
bool GetParCheck() { return m_bParCheck; }
bool GetParRepair() { return m_bParRepair; }
const char* GetPostProcess() { return m_szPostProcess; }
bool GetStrictParName() { return m_bStrictParName; }
int GetUMask() { return m_iUMask; }
int GetUpdateInterval() {return m_iUpdateInterval; }
bool GetCursesNZBName() { return m_bCursesNZBName; }
bool GetCursesTime() { return m_bCursesTime; }
bool GetCursesGroup() { return m_bCursesGroup; }
bool GetCrcCheck() { return m_bCrcCheck; }
bool GetRetryOnCrcError() { return m_bRetryOnCrcError; }
int GetThreadLimit() { return m_iThreadLimit; }
bool GetDirectWrite() { return m_bDirectWrite; }
int GetWriteBufferSize() { return m_iWriteBufferSize; }
int GetNzbDirInterval() { return m_iNzbDirInterval; }
int GetNzbDirFileAge() { return m_iNzbDirFileAge; }
// Parsed command-line parameters
bool GetServerMode() { return m_bServerMode; }
bool GetDaemonMode() { return m_bDaemonMode; }
bool GetRemoteClientMode() { return m_bRemoteClientMode; }
EClientOperation GetClientOperation() { return m_eClientOperation; }
int GetEditQueueAction() { return m_iEditQueueAction; }
int GetEditQueueOffset() { return m_iEditQueueOffset; }
int* GetEditQueueIDList() { return m_pEditQueueIDList; }
int GetEditQueueIDCount() { return m_iEditQueueIDCount; }
const char* GetArgFilename() { return m_szArgFilename; }
bool GetAddTop() { return m_bAddTop; }
float GetSetRate() { return m_fSetRate; }
int GetLogLines() { return m_iLogLines; }
// Current state
void SetPause(bool bOnOff) { m_bPause = bOnOff; }
bool GetPause() const { return m_bPause; }
void SetDownloadRate(float fRate) { m_fDownloadRate = fRate; }
float GetDownloadRate() const { return m_fDownloadRate; }
};
#endif

530
ParChecker.cpp Normal file
View File

@@ -0,0 +1,530 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2007 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
#ifndef DISABLE_PARCHECK
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#ifdef WIN32
#include <par2cmdline.h>
#include <par2repairer.h>
#else
#include <libpar2/par2cmdline.h>
#include <libpar2/par2repairer.h>
#endif
#include "nzbget.h"
#include "ParChecker.h"
#include "Log.h"
#include "QueueCoordinator.h"
#include "Options.h"
#include "Util.h"
extern QueueCoordinator* g_pQueueCoordinator;
extern Options* g_pOptions;
const char* Par2CmdLineErrStr[] = { "OK",
"data files are damaged and there is enough recovery data available to repair them",
"data files are damaged and there is insufficient recovery data available to be able to repair them",
"there was something wrong with the command line arguments",
"the PAR2 files did not contain sufficient information about the data files to be able to verify them",
"repair completed but the data files still appear to be damaged",
"an error occured when accessing files",
"internal error occurred",
"out of memory" };
class Repairer : public Par2Repairer
{
friend class ParChecker;
};
ParChecker::ParChecker()
{
debug("Creating ParChecker");
m_eStatus = psUndefined;
m_szParFilename = NULL;
m_szNZBFilename = NULL;
m_szInfoName = NULL;
m_szErrMsg = NULL;
m_QueuedParFiles.clear();
}
ParChecker::~ParChecker()
{
debug("Destroying ParChecker");
if (m_szParFilename)
{
free(m_szParFilename);
}
if (m_szNZBFilename)
{
free(m_szNZBFilename);
}
if (m_szInfoName)
{
free(m_szInfoName);
}
if (m_szErrMsg)
{
free(m_szErrMsg);
}
for (QueuedParFiles::iterator it = m_QueuedParFiles.begin(); it != m_QueuedParFiles.end() ;it++)
{
free(*it);
}
m_QueuedParFiles.clear();
}
void ParChecker::SetParFilename(const char * szParFilename)
{
if (m_szParFilename)
{
free(m_szParFilename);
}
m_szParFilename = strdup(szParFilename);
}
void ParChecker::SetInfoName(const char * szInfoName)
{
if (m_szInfoName)
{
free(m_szInfoName);
}
m_szInfoName = strdup(szInfoName);
}
void ParChecker::SetNZBFilename(const char * szNZBFilename)
{
if (m_szNZBFilename)
{
free(m_szNZBFilename);
}
m_szNZBFilename = strdup(szNZBFilename);
}
void ParChecker::SetStatus(EStatus eStatus)
{
m_eStatus = eStatus;
Notify(NULL);
}
void ParChecker::Run()
{
info("Verifying %s", m_szInfoName);
SetStatus(psWorking);
debug("par: %s", m_szParFilename);
CommandLine commandLine;
const char* argv[] = { "par2", "r", "-q", "-q", m_szParFilename };
if (!commandLine.Parse(5, (char**)argv))
{
error("Could not start par-check for %s. Par-file: %s", m_szInfoName, m_szParFilename);
SetStatus(psFailed);
return;
}
Result res;
Repairer* repairer = new Repairer();
repairer->sig_filename.connect(sigc::mem_fun(*this, &ParChecker::signal_filename));
res = repairer->PreProcess(commandLine);
debug("ParChecker: PreProcess-result=%i", res);
if (res != eSuccess || IsStopped())
{
error("Could not verify %s: ", m_szInfoName, IsStopped() ? "due stopping" : "par2-file could not be processed");
SetStatus(psFailed);
delete repairer;
return;
}
char BufReason[1024];
BufReason[0] = '\0';
if (m_szErrMsg)
{
free(m_szErrMsg);
}
m_szErrMsg = NULL;
m_bRepairNotNeeded = false;
m_bRepairing = false;
res = repairer->Process(commandLine, false);
debug("ParChecker: Process-result=%i", res);
while (!IsStopped() && res == eRepairNotPossible)
{
int missingblockcount = repairer->missingblockcount - repairer->recoverypacketmap.size();
info("Need more %i par-block(s) for %s", missingblockcount, m_szInfoName);
m_mutexQueuedParFiles.Lock();
bool hasMorePars = !m_QueuedParFiles.empty();
m_mutexQueuedParFiles.Unlock();
if (!hasMorePars)
{
int iBlockFound = 0;
bool requested = RequestMorePars(missingblockcount, &iBlockFound);
m_mutexQueuedParFiles.Lock();
hasMorePars = !m_QueuedParFiles.empty();
m_mutexQueuedParFiles.Unlock();
if (!requested && !hasMorePars)
{
snprintf(BufReason, 1024, "not enough par-blocks, %i block(s) needed, but %i block(s) available", missingblockcount, iBlockFound);
BufReason[1024-1] = '\0';
m_szErrMsg = strdup(BufReason);
break;
}
if (!hasMorePars)
{
m_semNeedMoreFiles.Wait();
}
}
if (IsStopped())
{
break;
}
LoadMorePars(repairer);
repairer->UpdateVerificationResults();
m_bRepairing = false;
res = repairer->Process(commandLine, false);
debug("ParChecker: Process-result=%i", res);
}
if (IsStopped())
{
SetStatus(psFailed);
delete repairer;
return;
}
if (res == eSuccess)
{
info("Repair not needed for %s", m_szInfoName);
m_bRepairNotNeeded = true;
}
else if (res == eRepairPossible)
{
if (g_pOptions->GetParRepair())
{
info("Repairing %s", m_szInfoName);
m_bRepairing = true;
res = repairer->Process(commandLine, true);
debug("ParChecker: Process-result=%i", res);
if (res == eSuccess)
{
info("Successfully repaired %s", m_szInfoName);
}
}
else
{
info("Repair possible for %s", m_szInfoName);
res = eSuccess;
}
}
if (res == eSuccess)
{
SetStatus(psFinished);
}
else
{
if (!m_szErrMsg && (int)res >= 0 && (int)res <= 8)
{
m_szErrMsg = strdup(Par2CmdLineErrStr[res]);
}
error("Repair failed for %s: %s", m_szInfoName, m_szErrMsg ? m_szErrMsg : "");
SetStatus(psFailed);
}
delete repairer;
}
bool ParChecker::ParseParFilename(const char * szParFilename, int* iBaseNameLen, int* iBlocks)
{
char szFilename[1024];
strncpy(szFilename, szParFilename, 1024);
szFilename[1024-1] = '\0';
for (char* p = szFilename; *p; p++) *p = tolower(*p); // convert string to lowercase
int iLen = strlen(szFilename);
if (iLen < 6)
{
return false;
}
// find last occurence of ".par2" and trim filename after it
char* szEnd = szFilename;
while (char* p = strstr(szEnd, ".par2")) szEnd = p + 5;
*szEnd = '\0';
iLen = strlen(szFilename);
if (strcasecmp(szFilename + iLen - 5, ".par2"))
{
return false;
}
*(szFilename + iLen - 5) = '\0';
int blockcnt = 0;
char* p = strrchr(szFilename, '.');
if (p && !strncasecmp(p, ".vol", 4))
{
char* b = strchr(p, '+');
if (!b)
{
b = strchr(p, '-');
}
if (b)
{
blockcnt = atoi(b+1);
*p = '\0';
}
}
if (iBaseNameLen)
{
*iBaseNameLen = strlen(szFilename);
}
if (iBlocks)
{
*iBlocks = blockcnt;
}
return true;
}
/**
* Unpause par2-files
* returns true, if the files with required number of blocks were unpaused,
* or false if there are no more files in queue for this collection or not enough blocks
*/
bool ParChecker::RequestMorePars(int iBlockNeeded, int* pBlockFound)
{
DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue();
Blocks blocks;
blocks.clear();
int iBlockFound = 0;
FindPars(pDownloadQueue, &blocks, true, &iBlockFound);
if (iBlockFound == 0 && !g_pOptions->GetStrictParName())
{
FindPars(pDownloadQueue, &blocks, false, &iBlockFound);
}
if (iBlockFound >= iBlockNeeded)
{
char szNZBNiceName[1024];
FileInfo::MakeNiceNZBName(m_szNZBFilename, szNZBNiceName, 1024);
// 1. first unpause all files with par-blocks less or equal iBlockNeeded
// starting from the file with max block count.
// if par-collection was built exponentially and all par-files present,
// this step selects par-files with exact number of blocks we need.
while (iBlockNeeded > 0)
{
BlockInfo* pBestBlockInfo = NULL;
for (Blocks::iterator it = blocks.begin(); it != blocks.end(); it++)
{
BlockInfo* pBlockInfo = *it;
if (pBlockInfo->m_iBlockCount <= iBlockNeeded &&
(!pBestBlockInfo || pBestBlockInfo->m_iBlockCount < pBlockInfo->m_iBlockCount))
{
pBestBlockInfo = pBlockInfo;
}
}
if (pBestBlockInfo)
{
if (pBestBlockInfo->m_pFileInfo->GetPaused())
{
info("Unpausing %s%c%s for par-recovery", szNZBNiceName, (int)PATH_SEPARATOR, pBestBlockInfo->m_pFileInfo->GetFilename());
pBestBlockInfo->m_pFileInfo->SetPaused(false);
}
iBlockNeeded -= pBestBlockInfo->m_iBlockCount;
}
else
{
break;
}
}
// 2. then unpause other files
// this step only needed if the par-collection was built not exponentially
// or not all par-files present (or some of them were corrupted)
// this step is not optimal, but we hope, that the first step will work good
// in most cases and we will not need the second step often
while (iBlockNeeded > 0)
{
BlockInfo* pBlockInfo = blocks.front();
if (pBlockInfo->m_pFileInfo->GetPaused())
{
info("Unpausing %s%c%s for par-recovery", szNZBNiceName, (int)PATH_SEPARATOR, pBlockInfo->m_pFileInfo->GetFilename());
pBlockInfo->m_pFileInfo->SetPaused(false);
}
iBlockNeeded -= pBlockInfo->m_iBlockCount;
}
}
g_pQueueCoordinator->UnlockQueue();
if (pBlockFound)
{
*pBlockFound = iBlockFound;
}
for (Blocks::iterator it = blocks.begin(); it != blocks.end(); it++)
{
delete *it;
}
blocks.clear();
return iBlockNeeded <= 0;
}
void ParChecker::FindPars(DownloadQueue * pDownloadQueue, Blocks * pBlocks, bool bStrictParName, int* pBlockFound)
{
*pBlockFound = 0;
// extract base name from m_szParFilename (trim .par2-extension and possible .vol-part)
char* szBaseParFilename = BaseFileName(m_szParFilename);
char szMainBaseFilename[1024];
int iMainBaseLen = 0;
if (!ParseParFilename(szBaseParFilename, &iMainBaseLen, NULL))
{
// should not happen
error("Internal error: could not parse filename %s", szBaseParFilename);
return;
}
int maxlen = iMainBaseLen < 1024 ? iMainBaseLen : 1024 - 1;
strncpy(szMainBaseFilename, szBaseParFilename, maxlen);
szMainBaseFilename[maxlen] = '\0';
for (char* p = szMainBaseFilename; *p; p++) *p = tolower(*p); // convert string to lowercase
for (DownloadQueue::iterator it = pDownloadQueue->begin(); it != pDownloadQueue->end(); it++)
{
FileInfo* pFileInfo = *it;
int iBlocks = 0;
if (!strcmp(pFileInfo->GetNZBFilename(), m_szNZBFilename) &&
ParseParFilename(pFileInfo->GetFilename(), NULL, &iBlocks) &&
iBlocks > 0)
{
if (bStrictParName)
{
// the pFileInfo->GetFilename() may be not confirmed and may contain
// additional texts if Subject could not be parsed correctly
char szLoFileName[1024];
strncpy(szLoFileName, pFileInfo->GetFilename(), 1024);
szLoFileName[1024-1] = '\0';
for (char* p = szLoFileName; *p; p++) *p = tolower(*p); // convert string to lowercase
char szCandidateFileName[1024];
snprintf(szCandidateFileName, 1024, "%s.par2", szMainBaseFilename);
szCandidateFileName[1024-1] = '\0';
if (!strstr(szLoFileName, szCandidateFileName))
{
snprintf(szCandidateFileName, 1024, "%s.vol", szMainBaseFilename);
szCandidateFileName[1024-1] = '\0';
if (!strstr(szLoFileName, szCandidateFileName))
{
continue;
}
}
}
// if it is a par2-file with blocks and it was from the same NZB-request
// and it belongs to the same file collection (same base name),
// then OK, we can use it
BlockInfo* pBlockInfo = new BlockInfo();
pBlockInfo->m_pFileInfo = pFileInfo;
pBlockInfo->m_iBlockCount = iBlocks;
pBlocks->push_back(pBlockInfo);
*pBlockFound += iBlocks;
}
}
}
void ParChecker::LoadMorePars(void* repairer)
{
m_mutexQueuedParFiles.Lock();
QueuedParFiles moreFiles;
moreFiles.assign(m_QueuedParFiles.begin(), m_QueuedParFiles.end());
m_QueuedParFiles.clear();
m_mutexQueuedParFiles.Unlock();
for (QueuedParFiles::iterator it = moreFiles.begin(); it != moreFiles.end() ;it++)
{
char* szParFilename = *it;
bool loadedOK = ((Repairer*)repairer)->LoadPacketsFromFile(szParFilename);
if (loadedOK)
{
info("File %s successfully loaded for par-check", BaseFileName(szParFilename), m_szInfoName);
}
else
{
info("Could not load file %s for par-check", BaseFileName(szParFilename), m_szInfoName);
}
free(szParFilename);
}
}
void ParChecker::AddParFile(const char * szParFilename)
{
m_mutexQueuedParFiles.Lock();
m_QueuedParFiles.push_back(strdup(szParFilename));
m_semNeedMoreFiles.Post();
m_mutexQueuedParFiles.Unlock();
}
void ParChecker::QueueChanged()
{
m_mutexQueuedParFiles.Lock();
m_semNeedMoreFiles.Post();
m_mutexQueuedParFiles.Unlock();
}
void ParChecker::signal_filename(std::string str)
{
info("%s file %s", m_bRepairing ? "Repairing" : "Verifying", str.c_str());
}
#endif

94
ParChecker.h Normal file
View File

@@ -0,0 +1,94 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2007 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$
*
*/
#ifndef PARCHECKER_H
#define PARCHECKER_H
#ifndef DISABLE_PARCHECK
#include <deque>
#include "Thread.h"
#include "Observer.h"
#include "DownloadInfo.h"
class ParChecker : public Thread, public Subject
{
public:
enum EStatus
{
psUndefined,
psWorking,
psFailed,
psFinished
};
struct BlockInfo
{
FileInfo* m_pFileInfo;
int m_iBlockCount;
};
typedef std::deque<char*> QueuedParFiles;
typedef std::deque<BlockInfo*> Blocks;
private:
char* m_szInfoName;
char* m_szNZBFilename;
char* m_szParFilename;
EStatus m_eStatus;
char* m_szErrMsg;
bool m_bRepairNotNeeded;
QueuedParFiles m_QueuedParFiles;
Mutex m_mutexQueuedParFiles;
Semaphore m_semNeedMoreFiles;
bool m_bRepairing;
bool RequestMorePars(int iBlockNeeded, int* pBlockFound);
void FindPars(DownloadQueue* pDownloadQueue, Blocks* pBlocks, bool bStrictParName, int* pBlockFound);
void LoadMorePars(void* repairer);
void signal_filename(std::string str);
public:
ParChecker();
virtual ~ParChecker();
virtual void Run();
const char* GetParFilename() { return m_szParFilename; }
void SetParFilename(const char* szParFilename);
const char* GetNZBFilename() { return m_szNZBFilename; }
void SetNZBFilename(const char* szNZBFilename);
const char* GetInfoName() { return m_szInfoName; }
void SetInfoName(const char* szInfoName);
void SetStatus(EStatus eStatus);
EStatus GetStatus() { return m_eStatus; }
const char* GetErrMsg() { return m_szErrMsg; }
bool GetRepairNotNeeded() { return m_bRepairNotNeeded; }
void AddParFile(const char* szParFilename);
void QueueChanged();
static bool ParseParFilename(const char* szParFilename, int* iBaseNameLen, int* iBlocks);
};
#endif
#endif

601
PrePostProcessor.cpp Normal file
View File

@@ -0,0 +1,601 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2007 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 <ctype.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include "nzbget.h"
#include "PrePostProcessor.h"
#include "Options.h"
#include "Log.h"
#include "QueueCoordinator.h"
#include "Util.h"
extern QueueCoordinator* g_pQueueCoordinator;
extern Options* g_pOptions;
static const int PARSTATUS_NOT_CHECKED = 0;
static const int PARSTATUS_FAILED = 1;
static const int PARSTATUS_REPAIRED = 2;
static const int PARSTATUS_REPAIR_POSSIBLE = 3;
PrePostProcessor::ParJob::ParJob(const char * szNZBFilename, const char * szParFilename, const char * szInfoName)
{
m_szNZBFilename = strdup(szNZBFilename);
m_szParFilename = strdup(szParFilename);
m_szInfoName = strdup(szInfoName);
}
PrePostProcessor::ParJob::~ ParJob()
{
if (m_szNZBFilename)
{
free(m_szNZBFilename);
}
if (m_szParFilename)
{
free(m_szParFilename);
}
if (m_szInfoName)
{
free(m_szInfoName);
}
}
PrePostProcessor::PrePostProcessor()
{
debug("Creating PrePostProcessor");
m_bHasMoreJobs = false;
m_QueueCoordinatorObserver.owner = this;
g_pQueueCoordinator->Attach(&m_QueueCoordinatorObserver);
m_ParQueue.clear();
#ifndef DISABLE_PARCHECK
m_ParCheckerObserver.owner = this;
m_ParChecker.Attach(&m_ParCheckerObserver);
#endif
}
PrePostProcessor::~PrePostProcessor()
{
debug("Destroying PrePostProcessor");
for (ParQueue::iterator it = m_ParQueue.begin(); it != m_ParQueue.end(); it++)
{
delete *it;
}
}
void PrePostProcessor::Run()
{
debug("Entering PrePostProcessor-loop");
int iNZBDirInterval = 0;
#ifndef DISABLE_PARCHECK
int iParQueueInterval = 0;
#endif
while (!IsStopped())
{
if (g_pOptions->GetNzbDir() && g_pOptions->GetNzbDirInterval() > 0 &&
iNZBDirInterval == g_pOptions->GetNzbDirInterval() * 1000)
{
// check nzbdir every g_pOptions->GetNzbDirInterval() seconds
CheckIncomingNZBs();
iNZBDirInterval = 0;
}
iNZBDirInterval += 200;
#ifndef DISABLE_PARCHECK
if (iParQueueInterval == 1000 && g_pOptions->GetParCheck())
{
// check par-queue every 1 second
CheckParQueue();
iParQueueInterval = 0;
}
iParQueueInterval += 200;
#endif
usleep(200 * 1000);
}
debug("Exiting PrePostProcessor-loop");
}
void PrePostProcessor::Stop()
{
Thread::Stop();
#ifndef DISABLE_PARCHECK
m_mutexParChecker.Lock();
if (m_ParChecker.IsRunning())
{
m_ParChecker.Stop();
int iMSecWait = 5000;
while (m_ParChecker.IsRunning() && iMSecWait > 0)
{
usleep(50 * 1000);
iMSecWait -= 50;
}
if (m_ParChecker.IsRunning())
{
warn("Terminating par-check for %s", m_ParChecker.GetInfoName());
m_ParChecker.Kill();
}
}
m_mutexParChecker.Unlock();
#endif
}
void PrePostProcessor::QueueCoordinatorUpdate(Subject * Caller, void * Aspect)
{
if (IsStopped())
{
return;
}
QueueCoordinator::Aspect* pAspect = (QueueCoordinator::Aspect*)Aspect;
if (pAspect->eAction == QueueCoordinator::eaNZBFileAdded &&
g_pOptions->GetLoadPars() != Options::plAll)
{
PausePars(pAspect->pDownloadQueue, pAspect->szNZBFilename);
}
else if ((pAspect->eAction == QueueCoordinator::eaFileCompleted ||
pAspect->eAction == QueueCoordinator::eaFileDeleted))
{
if (
#ifndef DISABLE_PARCHECK
!AddPar(pAspect->pFileInfo, pAspect->eAction == QueueCoordinator::eaFileDeleted) &&
#endif
WasLastInCollection(pAspect->pDownloadQueue, pAspect->pFileInfo, true))
{
char szNZBNiceName[1024];
pAspect->pFileInfo->GetNiceNZBName(szNZBNiceName, 1024);
if (pAspect->eAction == QueueCoordinator::eaFileCompleted)
{
info("Collection %s completely downloaded", szNZBNiceName);
}
else if (WasLastInCollection(pAspect->pDownloadQueue, pAspect->pFileInfo, false))
{
info("Collection %s deleted from queue", szNZBNiceName);
}
#ifndef DISABLE_PARCHECK
if (g_pOptions->GetParCheck() &&
pAspect->eAction == QueueCoordinator::eaFileCompleted)
{
CheckPars(pAspect->pDownloadQueue, pAspect->pFileInfo);
}
else
#endif
{
ExecPostScript(pAspect->pFileInfo->GetDestDir(), pAspect->pFileInfo->GetNZBFilename(), "", PARSTATUS_NOT_CHECKED);
}
}
}
}
void PrePostProcessor::PausePars(DownloadQueue* pDownloadQueue, const char* szNZBFilename)
{
debug("PrePostProcessor: Pausing pars");
for (DownloadQueue::iterator it = pDownloadQueue->begin(); it != pDownloadQueue->end(); it++)
{
FileInfo* pFileInfo = *it;
if (!strcmp(pFileInfo->GetNZBFilename(), szNZBFilename))
{
g_pQueueCoordinator->GetQueueEditor()->LockedEditEntry(pDownloadQueue, pFileInfo->GetID(), false,
(g_pOptions->GetLoadPars() == Options::plOne ||
(g_pOptions->GetLoadPars() == Options::plNone && g_pOptions->GetParCheck()))
? QueueEditor::eaGroupPauseExtraPars : QueueEditor::eaGroupPauseAllPars,
0);
break;
}
}
}
/**
* Check if there are files in directory for incoming nzb-files
* and add them to download queue
*/
void PrePostProcessor::CheckIncomingNZBs()
{
DirBrowser dir(g_pOptions->GetNzbDir());
while (const char* filename = dir.Next())
{
int len = strlen(filename);
if (len > 4 && !strcasecmp(filename + len - 4, ".nzb"))
{
// file found, checking modification-time
struct stat buffer;
char fullfilename[1024];
snprintf(fullfilename, 1024, "%s%c%s", g_pOptions->GetNzbDir(), (int)PATH_SEPARATOR, filename);
fullfilename[1024-1] = '\0';
if (!stat(fullfilename, &buffer) &&
time(NULL) - buffer.st_mtime > g_pOptions->GetNzbDirFileAge() &&
time(NULL) - buffer.st_ctime > g_pOptions->GetNzbDirFileAge())
{
// the file is at least g_pOptions->GetNzbDirFileAge() seconds old, we can process it
info("Collection %s found", filename);
char bakname[1024];
if (g_pQueueCoordinator->AddFileToQueue(fullfilename))
{
info("Collection %s added to queue", filename);
snprintf(bakname, 1024, "%s.queued", fullfilename);
bakname[1024-1] = '\0';
}
else
{
error("Could not add collection %s to queue", filename);
snprintf(bakname, 1024, "%s.error", fullfilename);
bakname[1024-1] = '\0';
}
char bakname2[1024];
strcpy(bakname2, bakname);
int i = 2;
while (!stat(bakname2, &buffer))
{
snprintf(bakname2, 1024, "%s%i", bakname, i++);
bakname2[1024-1] = '\0';
}
rename(fullfilename, bakname2);
}
}
}
}
/**
* Check if the completed file was last (unpaused, if bIgnorePaused is "true") file in nzb-collection
*/
bool PrePostProcessor::WasLastInCollection(DownloadQueue* pDownloadQueue, FileInfo * pFileInfo, bool bIgnorePaused)
{
debug("File %s completed or deleted", pFileInfo->GetFilename());
for (DownloadQueue::iterator it = pDownloadQueue->begin(); it != pDownloadQueue->end(); it++)
{
FileInfo* pFileInfo2 = *it;
if (pFileInfo2 != pFileInfo && (!bIgnorePaused || !pFileInfo2->GetPaused()) &&
!strcmp(pFileInfo2->GetNZBFilename(), pFileInfo->GetNZBFilename()))
{
return false;
}
}
return true;
}
PrePostProcessor::ParQueue* PrePostProcessor::LockParQueue()
{
m_mutexParChecker.Lock();
return &m_ParQueue;
}
void PrePostProcessor::UnlockParQueue()
{
m_mutexParChecker.Unlock();
}
#ifndef DISABLE_PARCHECK
void PrePostProcessor::CheckPars(DownloadQueue * pDownloadQueue, FileInfo * pFileInfo)
{
char szNZBNiceName[1024];
pFileInfo->GetNiceNZBName(szNZBNiceName, 1024);
m_mutexParChecker.Lock();
FileList fileList;
if (FindMainPars(pFileInfo->GetDestDir(), &fileList))
{
for (FileList::iterator it = fileList.begin(); it != fileList.end(); it++)
{
char* szParFilename = *it;
debug("Found par: %s", szParFilename);
char szFullFilename[1024];
snprintf(szFullFilename, 1024, "%s%c%s", pFileInfo->GetDestDir(), (int)PATH_SEPARATOR, szParFilename);
szFullFilename[1024-1] = '\0';
char szInfoName[1024];
int iBaseLen = 0;
ParChecker::ParseParFilename(szParFilename, &iBaseLen, NULL);
int maxlen = iBaseLen < 1024 ? iBaseLen : 1024 - 1;
strncpy(szInfoName, szParFilename, maxlen);
szInfoName[maxlen] = '\0';
char szParInfoName[1024];
snprintf(szParInfoName, 1024, "%s%c%s", szNZBNiceName, (int)PATH_SEPARATOR, szInfoName);
szParInfoName[1024-1] = '\0';
info("Queueing %s%c%s for par-check", szNZBNiceName, (int)PATH_SEPARATOR, szInfoName);
ParJob* pParJob = new ParJob(pFileInfo->GetNZBFilename(), szFullFilename, szParInfoName);
m_ParQueue.push_back(pParJob);
m_bHasMoreJobs = true;
free(szParFilename);
}
}
m_mutexParChecker.Unlock();
}
bool PrePostProcessor::FindMainPars(const char * szPath, FileList * pFileList)
{
pFileList->clear();
DirBrowser dir(szPath);
while (const char* filename = dir.Next())
{
int iBaseLen = 0;
if (ParChecker::ParseParFilename(filename, &iBaseLen, NULL))
{
// check if the base file already added to list
bool exists = false;
for (FileList::iterator it = pFileList->begin(); it != pFileList->end(); it++)
{
const char* filename2 = *it;
exists = SameParCollection(filename, filename2);
if (exists)
{
break;
}
}
if (!exists)
{
pFileList->push_back(strdup(filename));
}
}
}
return !pFileList->empty();
}
bool PrePostProcessor::AddPar(FileInfo * pFileInfo, bool bDeleted)
{
m_mutexParChecker.Lock();
bool bSameCollection = m_ParChecker.IsRunning() &&
!strcmp(pFileInfo->GetNZBFilename(), m_ParChecker.GetNZBFilename()) &&
SameParCollection(pFileInfo->GetFilename(), BaseFileName(m_ParChecker.GetParFilename()));
if (bSameCollection)
{
if (!bDeleted)
{
char szFullFilename[1024];
snprintf(szFullFilename, 1024, "%s%c%s", pFileInfo->GetDestDir(), (int)PATH_SEPARATOR, pFileInfo->GetFilename());
szFullFilename[1024-1] = '\0';
m_ParChecker.AddParFile(szFullFilename);
}
else
{
m_ParChecker.QueueChanged();
}
}
m_mutexParChecker.Unlock();
return bSameCollection;
}
bool PrePostProcessor::SameParCollection(const char* szFilename1, const char* szFilename2)
{
int iBaseLen1 = 0, iBaseLen2 = 0;
return ParChecker::ParseParFilename(szFilename1, &iBaseLen1, NULL) &&
ParChecker::ParseParFilename(szFilename2, &iBaseLen2, NULL) &&
iBaseLen1 == iBaseLen2 &&
!strncasecmp(szFilename1, szFilename2, iBaseLen1);
}
void PrePostProcessor::CheckParQueue()
{
m_mutexParChecker.Lock();
if (!m_ParChecker.IsRunning() && !m_ParQueue.empty())
{
ParJob* pParJob = m_ParQueue.front();
info("Checking pars for %s", pParJob->GetInfoName());
m_ParChecker.SetNZBFilename(pParJob->GetNZBFilename());
m_ParChecker.SetParFilename(pParJob->GetParFilename());
m_ParChecker.SetInfoName(pParJob->GetInfoName());
m_ParChecker.Start();
}
m_mutexParChecker.Unlock();
}
void PrePostProcessor::ParCheckerUpdate(Subject * Caller, void * Aspect)
{
if (m_ParChecker.GetStatus() == ParChecker::psFinished ||
m_ParChecker.GetStatus() == ParChecker::psFailed)
{
char szPath[1024];
strncpy(szPath, m_ParChecker.GetParFilename(), 1024);
szPath[1024-1] = '\0';
if (char* p = strrchr(szPath, PATH_SEPARATOR)) *p = '\0';
if (g_pOptions->GetCreateBrokenLog())
{
char szBrokenLogName[1024];
snprintf(szBrokenLogName, 1024, "%s%c_brokenlog.txt", szPath, (int)PATH_SEPARATOR);
szBrokenLogName[1024-1] = '\0';
bool bExists = false;
if (m_ParChecker.GetRepairNotNeeded())
{
struct stat buffer;
bExists = !stat(szBrokenLogName, &buffer);
}
if (!m_ParChecker.GetRepairNotNeeded() || bExists)
{
FILE* file = fopen(szBrokenLogName, "a");
if (m_ParChecker.GetStatus() == ParChecker::psFailed)
{
fprintf(file, "Repair failed for %s: %s\n", m_ParChecker.GetInfoName(), m_ParChecker.GetErrMsg() ? m_ParChecker.GetErrMsg() : "");
}
else if (m_ParChecker.GetRepairNotNeeded())
{
fprintf(file, "Repair not needed for %s\n", m_ParChecker.GetInfoName());
}
else
{
if (g_pOptions->GetParRepair())
{
fprintf(file, "Successfully repaired %s\n", m_ParChecker.GetInfoName());
}
else
{
fprintf(file, "Repair possible for %s\n", m_ParChecker.GetInfoName());
}
}
fclose(file);
}
}
int iParStatus = 0;
if (m_ParChecker.GetStatus() == ParChecker::psFailed)
{
iParStatus = PARSTATUS_FAILED;
}
else if (g_pOptions->GetParRepair() || m_ParChecker.GetRepairNotNeeded())
{
iParStatus = PARSTATUS_REPAIRED;
}
else
{
iParStatus = PARSTATUS_REPAIR_POSSIBLE;
}
ExecPostScript(szPath, m_ParChecker.GetNZBFilename(), m_ParChecker.GetParFilename(), iParStatus);
m_mutexParChecker.Lock();
ParJob* pParJob = m_ParQueue.front();
m_ParQueue.pop_front();
delete pParJob;
m_bHasMoreJobs = !m_ParQueue.empty();
m_mutexParChecker.Unlock();
}
}
#endif
void PrePostProcessor::ExecPostScript(const char * szPath, const char * szNZBFilename, const char * szParFilename, int iParStatus)
{
const char* szScript = g_pOptions->GetPostProcess();
if (!szScript || strlen(szScript) == 0)
{
return;
}
info("Executing post-process for %s (%s)", szPath, BaseFileName(szNZBFilename));
struct stat buffer;
bool bExists = !stat(szScript, &buffer);
if (!bExists)
{
error("Could not start post-process: could not find file %s", szScript);
return;
}
bool bCollectionCompleted = true;
DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue();
for (DownloadQueue::iterator it = pDownloadQueue->begin(); it != pDownloadQueue->end(); it++)
{
FileInfo* pFileInfo2 = *it;
if (!pFileInfo2->GetPaused() &&
!strcmp(pFileInfo2->GetNZBFilename(), szNZBFilename))
{
bCollectionCompleted = false;
break;
}
}
g_pQueueCoordinator->UnlockQueue();
#ifndef DISABLE_PARCHECK
if (bCollectionCompleted)
{
m_mutexParChecker.Lock();
for (ParQueue::iterator it = m_ParQueue.begin(); it != m_ParQueue.end(); it++)
{
ParJob* pParJob = *it;
if (!strcmp(pParJob->GetNZBFilename(), szNZBFilename))
{
bCollectionCompleted = false;
break;
}
}
m_mutexParChecker.Unlock();
}
#endif
char szParStatus[10];
snprintf(szParStatus, 10, "%i", iParStatus);
szParStatus[10-1] = '\0';
char szCollectionCompleted[10];
snprintf(szCollectionCompleted, 10, "%i", (int)bCollectionCompleted);
szCollectionCompleted[10-1] = '\0';
#ifdef WIN32
char szCmdLine[2048];
snprintf(szCmdLine, 2048, "%s \"%s\" \"%s\" \"%s\" %s %s", szScript, szPath, szNZBFilename, szParFilename, szParStatus, szCollectionCompleted);
szCmdLine[2048-1] = '\0';
UINT ErrCode = WinExec(szCmdLine, SW_HIDE);
if (ErrCode < 32)
{
char szErrMsg[255];
szErrMsg[255-1] = '\0';
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM || FORMAT_MESSAGE_IGNORE_INSERTS || FORMAT_MESSAGE_ARGUMENT_ARRAY,
NULL, ErrCode, 0, szErrMsg, 255, NULL);
error("Could not start post-process: %s", szErrMsg);
}
#else
if (fork())
{
// continue the first instance
return;
}
// here goes the second instance
int h;
for (h = getdtablesize(); h >= 0;--h) close(h); /* close all descriptors */
h = open("/dev/null", O_RDWR); dup(h); dup(h); /* handle standart I/O */
execlp(szScript, szScript, szPath, szNZBFilename, szParFilename, szParStatus, szCollectionCompleted, NULL);
error("Could not start post-process: %s", strerror(errno));
exit(-1);
#endif
}

114
PrePostProcessor.h Normal file
View File

@@ -0,0 +1,114 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2007 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$
*
*/
#ifndef PREPOSTPROCESSOR_H
#define PREPOSTPROCESSOR_H
#include <deque>
#include "Thread.h"
#include "Observer.h"
#include "DownloadInfo.h"
#ifndef DISABLE_PARCHECK
#include "ParChecker.h"
#endif
class PrePostProcessor : public Thread
{
public:
class ParJob
{
private:
char* m_szNZBFilename;
char* m_szParFilename;
char* m_szInfoName;
public:
ParJob(const char* szNZBFilename, const char* szParFilename, const char* szInfoName);
~ParJob();
const char* GetNZBFilename() { return m_szNZBFilename; }
const char* GetParFilename() { return m_szParFilename; }
const char* GetInfoName() { return m_szInfoName; }
};
typedef std::deque<ParJob*> ParQueue;
private:
typedef std::deque<char*> FileList;
class QueueCoordinatorObserver: public Observer
{
public:
PrePostProcessor* owner;
virtual void Update(Subject* Caller, void* Aspect) { owner->QueueCoordinatorUpdate(Caller, Aspect); }
};
#ifndef DISABLE_PARCHECK
class ParCheckerObserver: public Observer
{
public:
PrePostProcessor* owner;
virtual void Update(Subject* Caller, void* Aspect) { owner->ParCheckerUpdate(Caller, Aspect); }
};
#endif
private:
QueueCoordinatorObserver m_QueueCoordinatorObserver;
bool m_bHasMoreJobs;
void PausePars(DownloadQueue* pDownloadQueue, const char* szNZBFilename);
void CheckIncomingNZBs();
bool WasLastInCollection(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo, bool bIgnorePaused);
void ExecPostScript(const char* szPath, const char* szNZBFilename, const char * szParFilename, int iParStatus);
Mutex m_mutexParChecker;
ParQueue m_ParQueue;
#ifndef DISABLE_PARCHECK
ParChecker m_ParChecker;
ParCheckerObserver m_ParCheckerObserver;
void ParCheckerUpdate(Subject* Caller, void* Aspect);
void CheckParQueue();
void CheckPars(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo);
bool AddPar(FileInfo* pFileInfo, bool bDeleted);
bool SameParCollection(const char* szFilename1, const char* szFilename2);
bool FindMainPars(const char* szPath, FileList* pFileList);
#endif
public:
PrePostProcessor();
virtual ~PrePostProcessor();
virtual void Run();
virtual void Stop();
void QueueCoordinatorUpdate(Subject* Caller, void* Aspect);
bool HasMoreJobs() { return m_bHasMoreJobs; }
ParQueue* LockParQueue();
void UnlockParQueue();
};
#endif

781
QueueCoordinator.cpp Normal file
View File

@@ -0,0 +1,781 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2005 Bo Cordes Petersen <placebodk@users.sourceforge.net>
* Copyright (C) 2007 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 <sys/stat.h>
#ifndef WIN32
#include <unistd.h>
#include <sys/time.h>
#endif
#include "nzbget.h"
#include "QueueCoordinator.h"
#include "Options.h"
#include "ServerPool.h"
#include "ArticleDownloader.h"
#include "DiskState.h"
#include "Log.h"
#include "Util.h"
#include "Decoder.h"
extern Options* g_pOptions;
extern ServerPool* g_pServerPool;
extern DiskState* g_pDiskState;
QueueCoordinator::QueueCoordinator()
{
debug("Creating QueueCoordinator");
m_bHasMoreJobs = true;
m_DownloadQueue.clear();
m_ActiveDownloads.clear();
for (int i = 0; i < SPEEDMETER_SECONDS; i++)
{
m_iSpeedBytes[i] = 0;
}
m_iSpeedBytesIndex = 0;
m_iAllBytes = 0;
m_tStartServer = 0;
m_tStartDownload = 0;
m_tPausedFrom = 0;
m_bStandBy = true;
YDecoder::Init();
}
QueueCoordinator::~QueueCoordinator()
{
debug("Destroying QueueCoordinator");
// Cleanup
debug("Deleting DownloadQueue");
for (DownloadQueue::iterator it = m_DownloadQueue.begin(); it != m_DownloadQueue.end(); it++)
{
delete *it;
}
m_DownloadQueue.clear();
debug("Deleting ArticleDownloaders");
for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++)
{
delete *it;
}
m_ActiveDownloads.clear();
YDecoder::Final();
debug("QueueCoordinator destroyed");
}
void QueueCoordinator::Run()
{
debug("Entering QueueCoordinator-loop");
m_mutexDownloadQueue.Lock();
if (g_pOptions->GetServerMode() && g_pOptions->GetSaveQueue() && g_pDiskState->Exists())
{
if (g_pOptions->GetReloadQueue())
{
g_pDiskState->Load(&m_DownloadQueue);
}
else
{
g_pDiskState->Discard();
}
}
g_pDiskState->CleanupTempDir(&m_DownloadQueue);
m_mutexDownloadQueue.Unlock();
m_tStartServer = time(NULL);
bool bWasStandBy = true;
bool bArticeDownloadsRunning = false;
int iResetCounter = 0;
while (!IsStopped())
{
if (!g_pOptions->GetPause())
{
NNTPConnection* pConnection = g_pServerPool->GetConnection(0, false);
if (pConnection)
{
// start download for next article
FileInfo* pFileInfo;
ArticleInfo* pArticleInfo;
m_mutexDownloadQueue.Lock();
bool bHasMoreArticles = GetNextArticle(pFileInfo, pArticleInfo);
bArticeDownloadsRunning = !m_ActiveDownloads.empty();
m_bHasMoreJobs = bHasMoreArticles || bArticeDownloadsRunning;
if (bHasMoreArticles && !IsStopped() && Thread::GetThreadCount() < g_pOptions->GetThreadLimit())
{
StartArticleDownload(pFileInfo, pArticleInfo, pConnection);
bArticeDownloadsRunning = true;
}
else
{
g_pServerPool->FreeConnection(pConnection, false);
}
m_mutexDownloadQueue.Unlock();
}
}
else
{
m_mutexDownloadQueue.Lock();
bArticeDownloadsRunning = !m_ActiveDownloads.empty();
m_mutexDownloadQueue.Unlock();
}
bool bStandBy = !bArticeDownloadsRunning;
if (bStandBy ^ bWasStandBy)
{
EnterLeaveStandBy(bStandBy);
bWasStandBy = bStandBy;
}
// sleep longer in StandBy
int iSleepInterval = bStandBy ? 100 : 5;
usleep(iSleepInterval * 1000);
AddSpeedReading(0);
iResetCounter+= iSleepInterval;
if (iResetCounter >= 1000)
{
// this code should not be called too often, once per second is OK
g_pServerPool->CloseUnusedConnections();
ResetHangingDownloads();
iResetCounter = 0;
}
}
// waiting for downloads
debug("QueueCoordinator: waiting for Downloads to complete");
bool completed = false;
while (!completed)
{
m_mutexDownloadQueue.Lock();
completed = m_ActiveDownloads.size() == 0;
m_mutexDownloadQueue.Unlock();
usleep(100 * 1000);
ResetHangingDownloads();
}
debug("QueueCoordinator: Downloads are completed");
debug("Exiting QueueCoordinator-loop");
}
void QueueCoordinator::AddNZBFileToQueue(NZBFile* pNZBFile, bool bAddFirst)
{
debug("Adding NZBFile to queue");
m_mutexDownloadQueue.Lock();
DownloadQueue tmpDownloadQueue;
tmpDownloadQueue.clear();
DownloadQueue DupeList;
DupeList.clear();
for (NZBFile::FileInfos::iterator it = pNZBFile->GetFileInfos()->begin(); it != pNZBFile->GetFileInfos()->end(); it++)
{
FileInfo* pFileInfo = *it;
if (g_pOptions->GetDupeCheck())
{
bool dupe = false;
if (IsDupe(pFileInfo))
{
warn("File \"%s\" seems to be duplicate, skipping", pFileInfo->GetFilename());
dupe = true;
}
for (NZBFile::FileInfos::iterator it2 = pNZBFile->GetFileInfos()->begin(); it2 != pNZBFile->GetFileInfos()->end(); it2++)
{
FileInfo* pFileInfo2 = *it2;
if (!strcmp(pFileInfo->GetFilename(), pFileInfo2->GetFilename()) &&
(pFileInfo->GetSize() < pFileInfo2->GetSize()))
{
warn("File \"%s\" appears twice in nzb-request, adding only the biggest file", pFileInfo->GetFilename());
dupe = true;
break;
}
}
if (dupe)
{
DupeList.push_back(pFileInfo);
continue;
}
}
if (bAddFirst)
{
tmpDownloadQueue.push_front(pFileInfo);
}
else
{
tmpDownloadQueue.push_back(pFileInfo);
}
}
for (DownloadQueue::iterator it = tmpDownloadQueue.begin(); it != tmpDownloadQueue.end(); it++)
{
if (bAddFirst)
{
m_DownloadQueue.push_front(*it);
}
else
{
m_DownloadQueue.push_back(*it);
}
}
for (DownloadQueue::iterator it = DupeList.begin(); it != DupeList.end(); it++)
{
FileInfo* pFileInfo = *it;
g_pDiskState->DiscardFile(NULL, pFileInfo);
delete pFileInfo;
}
pNZBFile->DetachFileInfos();
Aspect aspect = { eaNZBFileAdded, NULL, &m_DownloadQueue, pNZBFile->GetFileName() };
Notify(&aspect);
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
{
g_pDiskState->Save(&m_DownloadQueue);
}
m_mutexDownloadQueue.Unlock();
}
bool QueueCoordinator::AddFileToQueue(const char* szFileName)
{
// Parse the buffer and make it into a NZBFile
NZBFile* pNZBFile = NZBFile::CreateFromFile(szFileName);
// Did file parse correctly?
if (!pNZBFile)
{
return false;
}
// Add NZBFile to Queue
AddNZBFileToQueue(pNZBFile, false);
delete pNZBFile;
return true;
}
/*
* NOTE: see note to "AddSpeedReading"
*/
float QueueCoordinator::CalcCurrentDownloadSpeed()
{
int iTotal = 0;
for (int i = 0; i < SPEEDMETER_SECONDS; i++)
{
iTotal += m_iSpeedBytes[i];
}
float fSpeed = iTotal / 1024.0 / SPEEDMETER_SECONDS;
return fSpeed;
}
/*
* NOTE: we should use mutex by access to m_iSpeedBytes and m_iSpeedBytesIndex,
* but this would results in a big performance loss (the function
* "AddSpeedReading" is called extremly often), so we better agree with calculation
* errors possible because of simultaneuos access from several threads.
* The used algorithm is able to recover after few seconds.
* In any case the calculation errors can not result in fatal system
* errors (segmentation faults).
*/
void QueueCoordinator::AddSpeedReading(int iBytes)
{
int iIndex = time(NULL);
if (iIndex - m_iSpeedBytesIndex > SPEEDMETER_SECONDS)
{
m_iSpeedBytesIndex = iIndex - SPEEDMETER_SECONDS - 1;
}
for (int i = m_iSpeedBytesIndex + 1; i < iIndex; i++)
{
m_iSpeedBytes[i % SPEEDMETER_SECONDS] = 0;
}
if (iIndex > m_iSpeedBytesIndex)
{
m_iSpeedBytesIndex = iIndex;
m_iSpeedBytes[iIndex % SPEEDMETER_SECONDS] = iBytes;
}
else
{
m_iSpeedBytes[m_iSpeedBytesIndex % SPEEDMETER_SECONDS] += iBytes;
}
m_iAllBytes += iBytes;
}
long long QueueCoordinator::CalcRemainingSize()
{
long long lRemainingSize = 0;
m_mutexDownloadQueue.Lock();
for (DownloadQueue::iterator it = m_DownloadQueue.begin(); it != m_DownloadQueue.end(); it++)
{
FileInfo* pFileInfo = *it;
if (!pFileInfo->GetPaused() && !pFileInfo->GetDeleted())
{
lRemainingSize += pFileInfo->GetRemainingSize();
}
}
m_mutexDownloadQueue.Unlock();
return lRemainingSize;
}
/*
* NOTE: DownloadQueue must be locked prior to call of this function
* Returns True if Entry was deleted from Queue or False if it was scheduled for Deletion.
* NOTE: "False" does not mean unsuccess; the entry is (or will be) deleted in any case.
*/
bool QueueCoordinator::DeleteQueueEntry(FileInfo* pFileInfo)
{
pFileInfo->SetDeleted(true);
bool hasDownloads = false;
for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++)
{
ArticleDownloader* pArticleDownloader = *it;
if (pArticleDownloader->GetFileInfo() == pFileInfo)
{
hasDownloads = true;
pArticleDownloader->Stop();
}
}
if (!hasDownloads)
{
Aspect aspect = { eaFileDeleted, pFileInfo, &m_DownloadQueue, NULL };
Notify(&aspect);
DeleteFileInfo(pFileInfo, false);
}
return hasDownloads;
}
void QueueCoordinator::Stop()
{
Thread::Stop();
debug("Stopping ArticleDownloads");
m_mutexDownloadQueue.Lock();
for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++)
{
(*it)->Stop();
}
m_mutexDownloadQueue.Unlock();
debug("ArticleDownloads are notified");
}
bool QueueCoordinator::GetNextArticle(FileInfo* &pFileInfo, ArticleInfo* &pArticleInfo)
{
//debug("QueueCoordinator::GetNextArticle()");
for (DownloadQueue::iterator it = m_DownloadQueue.begin(); it != m_DownloadQueue.end(); it++)
{
pFileInfo = *it;
if (!pFileInfo->GetPaused() && !pFileInfo->GetDeleted())
{
if (pFileInfo->GetArticles()->empty() && g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
{
g_pDiskState->LoadArticles(pFileInfo);
}
for (FileInfo::Articles::iterator at = pFileInfo->GetArticles()->begin(); at != pFileInfo->GetArticles()->end(); at++)
{
pArticleInfo = *at;
if (pArticleInfo->GetStatus() == 0)
{
return true;
}
}
}
}
return false;
}
void QueueCoordinator::StartArticleDownload(FileInfo* pFileInfo, ArticleInfo* pArticleInfo, NNTPConnection* pConnection)
{
debug("Starting new ArticleDownloader");
ArticleDownloader* pArticleDownloader = new ArticleDownloader();
pArticleDownloader->SetAutoDestroy(true);
pArticleDownloader->Attach(this);
pArticleDownloader->SetFileInfo(pFileInfo);
pArticleDownloader->SetArticleInfo(pArticleInfo);
pArticleDownloader->SetConnection(pConnection);
BuildArticleFilename(pArticleDownloader, pFileInfo, pArticleInfo);
pArticleInfo->SetStatus(ArticleInfo::aiRunning);
m_ActiveDownloads.push_back(pArticleDownloader);
pArticleDownloader->Start();
}
void QueueCoordinator::BuildArticleFilename(ArticleDownloader* pArticleDownloader, FileInfo* pFileInfo, ArticleInfo* pArticleInfo)
{
char name[1024];
snprintf(name, 1024, "%s%i.%03i", g_pOptions->GetTempDir(), pFileInfo->GetID(), pArticleInfo->GetPartNumber());
name[1024-1] = '\0';
pArticleInfo->SetResultFilename(name);
char tmpname[1024];
snprintf(tmpname, 1024, "%s.tmp", name);
tmpname[1024-1] = '\0';
pArticleDownloader->SetTempFilename(tmpname);
char szNZBNiceName[1024];
pFileInfo->GetNiceNZBName(szNZBNiceName, 1024);
snprintf(name, 1024, "%s%c%s [%i/%i]", szNZBNiceName, (int)PATH_SEPARATOR, pFileInfo->GetFilename(), pArticleInfo->GetPartNumber(), pFileInfo->GetArticles()->size());
name[1024-1] = '\0';
pArticleDownloader->SetInfoName(name);
if (g_pOptions->GetDirectWrite())
{
snprintf(name, 1024, "%s%i.out", g_pOptions->GetTempDir(), pFileInfo->GetID());
name[1024-1] = '\0';
pArticleDownloader->SetOutputFilename(name);
}
}
DownloadQueue* QueueCoordinator::LockQueue()
{
m_mutexDownloadQueue.Lock();
return &m_DownloadQueue;
}
void QueueCoordinator::UnlockQueue()
{
m_mutexDownloadQueue.Unlock();
}
void QueueCoordinator::Update(Subject* Caller, void* Aspect)
{
debug("Notification from ArticleDownloader received");
ArticleDownloader* pArticleDownloader = (ArticleDownloader*) Caller;
if ((pArticleDownloader->GetStatus() == ArticleDownloader::adFinished) ||
(pArticleDownloader->GetStatus() == ArticleDownloader::adFailed))
{
ArticleCompleted(pArticleDownloader);
}
}
void QueueCoordinator::ArticleCompleted(ArticleDownloader* pArticleDownloader)
{
debug("Article downloaded");
FileInfo* pFileInfo = pArticleDownloader->GetFileInfo();
ArticleInfo* pArticleInfo = pArticleDownloader->GetArticleInfo();
m_mutexDownloadQueue.Lock();
if (pArticleDownloader->GetStatus() == ArticleDownloader::adFinished)
{
pArticleInfo->SetStatus(ArticleInfo::aiFinished);
}
else if (pArticleDownloader->GetStatus() == ArticleDownloader::adFailed)
{
pArticleInfo->SetStatus(ArticleInfo::aiFailed);
}
pFileInfo->SetRemainingSize(pFileInfo->GetRemainingSize() - pArticleInfo->GetSize());
pFileInfo->SetCompleted(pFileInfo->GetCompleted() + 1);
bool fileCompleted = (int)pFileInfo->GetArticles()->size() == pFileInfo->GetCompleted();
if (!pFileInfo->GetFilenameConfirmed() &&
pArticleDownloader->GetStatus() == ArticleDownloader::adFinished &&
pArticleDownloader->GetArticleFilename())
{
pFileInfo->SetFilename(pArticleDownloader->GetArticleFilename());
pFileInfo->SetFilenameConfirmed(true);
if (g_pOptions->GetDupeCheck() && pFileInfo->IsDupe(pFileInfo->GetFilename()))
{
warn("File \"%s\" seems to be duplicate, cancelling download and deleting file from queue", pFileInfo->GetFilename());
fileCompleted = false;
DeleteQueueEntry(pFileInfo);
}
}
bool deleteFileObj = false;
if (pFileInfo->GetDeleted())
{
int cnt = 0;
for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++)
{
if ((*it)->GetFileInfo() == pFileInfo)
{
cnt++;
}
}
if (cnt == 1)
{
// this was the last Download for a file deleted from queue
deleteFileObj = true;
}
}
if (fileCompleted && !IsStopped() && !pFileInfo->GetDeleted())
{
// all jobs done
m_mutexDownloadQueue.Unlock();
pArticleDownloader->CompleteFileParts();
m_mutexDownloadQueue.Lock();
deleteFileObj = true;
}
// delete Download from Queue
for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++)
{
ArticleDownloader* pa = *it;
if (pa == pArticleDownloader)
{
m_ActiveDownloads.erase(it);
break;
}
}
if (deleteFileObj)
{
// delete File from Queue
pFileInfo->SetDeleted(true);
Aspect aspect = { fileCompleted ? eaFileCompleted : eaFileDeleted, pFileInfo, &m_DownloadQueue, NULL };
Notify(&aspect);
DeleteFileInfo(pFileInfo, fileCompleted);
}
m_mutexDownloadQueue.Unlock();
}
void QueueCoordinator::DeleteFileInfo(FileInfo* pFileInfo, bool bCompleted)
{
for (DownloadQueue::iterator it = m_DownloadQueue.begin(); it != m_DownloadQueue.end(); it++)
{
FileInfo* pa = *it;
if (pa == pFileInfo)
{
m_DownloadQueue.erase(it);
break;
}
}
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
{
g_pDiskState->DiscardFile(&m_DownloadQueue, pFileInfo);
}
if (!bCompleted)
{
// deleting temporary files
if (!g_pOptions->GetDirectWrite() || g_pOptions->GetContinuePartial())
{
for (FileInfo::Articles::iterator it = pFileInfo->GetArticles()->begin(); it != pFileInfo->GetArticles()->end(); it++)
{
ArticleInfo* pa = *it;
if (pa->GetResultFilename())
{
remove(pa->GetResultFilename());
}
}
}
if (g_pOptions->GetDirectWrite())
{
char name[1024];
snprintf(name, 1024, "%s%i.out", g_pOptions->GetTempDir(), pFileInfo->GetID());
name[1024-1] = '\0';
remove(name);
}
}
delete pFileInfo;
}
bool QueueCoordinator::IsDupe(FileInfo* pFileInfo)
{
debug("Checking if the file is already queued");
// checking on disk
if (pFileInfo->IsDupe(pFileInfo->GetFilename()))
{
return true;
}
// checking in queue
for (DownloadQueue::iterator it = m_DownloadQueue.begin(); it != m_DownloadQueue.end(); it++)
{
FileInfo* pQueueEntry = *it;
if (!strcmp(pFileInfo->GetDestDir(), pQueueEntry->GetDestDir()) &&
!strcmp(pFileInfo->GetFilename(), pQueueEntry->GetFilename()) &&
pFileInfo != pQueueEntry)
{
return true;
}
}
return false;
}
void QueueCoordinator::LogDebugInfo()
{
debug("--------------------------------------------");
debug("Dumping debug info to log");
debug("--------------------------------------------");
debug(" QueueCoordinator");
debug(" ----------------");
m_mutexDownloadQueue.Lock();
debug(" Active Downloads: %i", m_ActiveDownloads.size());
for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++)
{
ArticleDownloader* pArticleDownloader = *it;
pArticleDownloader->LogDebugInfo();
}
m_mutexDownloadQueue.Unlock();
debug("");
g_pServerPool->LogDebugInfo();
}
void QueueCoordinator::ResetHangingDownloads()
{
const int TimeOut = g_pOptions->GetTerminateTimeout();
if (TimeOut == 0)
{
return;
}
m_mutexDownloadQueue.Lock();
time_t tm = ::time(NULL);
for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end();)
{
ArticleDownloader* pArticleDownloader = *it;
if (tm - pArticleDownloader->GetLastUpdateTime() > TimeOut &&
pArticleDownloader->GetStatus() == ArticleDownloader::adRunning)
{
ArticleInfo* pArticleInfo = pArticleDownloader->GetArticleInfo();
debug("Terminating hanging download %s", pArticleDownloader->GetInfoName());
if (pArticleDownloader->Terminate())
{
error("Terminated hanging download %s", pArticleDownloader->GetInfoName());
pArticleInfo->SetStatus(ArticleInfo::aiUndefined);
}
else
{
error("Could not terminate hanging download %s", BaseFileName(pArticleInfo->GetResultFilename()));
}
m_ActiveDownloads.erase(it);
// it's not safe to destroy pArticleDownloader, because the state of object is unknown
delete pArticleDownloader;
it = m_ActiveDownloads.begin();
continue;
}
it++;
}
m_mutexDownloadQueue.Unlock();
}
void QueueCoordinator::EnterLeaveStandBy(bool bEnter)
{
m_mutexStat.Lock();
m_bStandBy = bEnter;
if (bEnter)
{
m_tPausedFrom = time(NULL);
}
else
{
if (m_tStartDownload == 0)
{
m_tStartDownload = time(NULL);
}
else
{
m_tStartDownload += time(NULL) - m_tPausedFrom;
}
m_tPausedFrom = 0;
}
m_mutexStat.Unlock();
}
void QueueCoordinator::CalcStat(int* iUpTimeSec, int* iDnTimeSec, long long* iAllBytes, bool* bStandBy)
{
m_mutexStat.Lock();
if (m_tStartServer > 0)
{
*iUpTimeSec = time(NULL) - m_tStartServer;
}
else
{
*iUpTimeSec = 0;
}
*bStandBy = m_bStandBy;
if (m_bStandBy)
{
*iDnTimeSec = m_tPausedFrom - m_tStartDownload;
}
else
{
*iDnTimeSec = time(NULL) - m_tStartDownload;
}
*iAllBytes = m_iAllBytes;
m_mutexStat.Unlock();
}

115
QueueCoordinator.h Normal file
View File

@@ -0,0 +1,115 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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$
*
*/
#ifndef QUEUECOORDINATOR_H
#define QUEUECOORDINATOR_H
#include <deque>
#include <list>
#include <time.h>
#ifdef WIN32
#include <sys/timeb.h>
#endif
#include "Thread.h"
#include "NZBFile.h"
#include "ArticleDownloader.h"
#include "DownloadInfo.h"
#include "Observer.h"
#include "QueueEditor.h"
#include "NNTPConnection.h"
class QueueCoordinator : public Thread, public Observer, public Subject, public DownloadSpeedMeter
{
public:
typedef std::list<ArticleDownloader*> ActiveDownloads;
typedef enum EAspectAction
{
eaNZBFileAdded,
eaFileCompleted,
eaFileDeleted
};
typedef struct Aspect
{
EAspectAction eAction;
FileInfo* pFileInfo;
DownloadQueue* pDownloadQueue;
const char* szNZBFilename;
};
private:
DownloadQueue m_DownloadQueue;
ActiveDownloads m_ActiveDownloads;
QueueEditor m_QueueEditor;
Mutex m_mutexDownloadQueue;
bool m_bHasMoreJobs;
// statistics
static const int SPEEDMETER_SECONDS = 5;
int m_iSpeedBytes[SPEEDMETER_SECONDS];
int m_iSpeedBytesIndex;
long long m_iAllBytes;
time_t m_tStartServer;
time_t m_tStartDownload;
time_t m_tPausedFrom;
void EnterLeaveStandBy(bool bEnter);
bool m_bStandBy;
Mutex m_mutexStat;
bool GetNextArticle(FileInfo* &pFileInfo, ArticleInfo* &pArticleInfo);
void StartArticleDownload(FileInfo* pFileInfo, ArticleInfo* pArticleInfo, NNTPConnection* pConnection);
void BuildArticleFilename(ArticleDownloader* pArticleDownloader, FileInfo* pFileInfo, ArticleInfo* pArticleInfo);
bool IsDupe(FileInfo* pFileInfo);
void ArticleCompleted(ArticleDownloader* pArticleDownloader);
void DeleteFileInfo(FileInfo* pFileInfo, bool bCompleted);
void ResetHangingDownloads();
public:
QueueCoordinator();
virtual ~QueueCoordinator();
virtual void Run();
virtual void Stop();
void Update(Subject* Caller, void* Aspect);
// statistics
long long CalcRemainingSize();
virtual float CalcCurrentDownloadSpeed();
virtual void AddSpeedReading(int iBytes);
void CalcStat(int* iUpTimeSec, int* iDnTimeSec, long long* iAllBytes, bool* bStandBy);
// Editing the queue
DownloadQueue* LockQueue();
void UnlockQueue() ;
void AddNZBFileToQueue(NZBFile* pNZBQueue, bool bAddFirst);
bool AddFileToQueue(const char* szFileName);
bool HasMoreJobs() { return m_bHasMoreJobs; }
bool DeleteQueueEntry(FileInfo* pFileInfo);
QueueEditor* GetQueueEditor() { return &m_QueueEditor; }
void LogDebugInfo();
};
#endif

655
QueueEditor.cpp Normal file
View File

@@ -0,0 +1,655 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2007 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>
#ifndef WIN32
#include <unistd.h>
#include <sys/time.h>
#endif
#include "nzbget.h"
#include "DownloadInfo.h"
#include "QueueEditor.h"
#include "QueueCoordinator.h"
#include "DiskState.h"
#include "Options.h"
#include "Log.h"
#include "Util.h"
extern QueueCoordinator* g_pQueueCoordinator;
extern Options* g_pOptions;
extern DiskState* g_pDiskState;
const int MAX_ID = 100000000;
QueueEditor::EditItem::EditItem(FileInfo* pFileInfo, int iOffset)
{
m_pFileInfo = pFileInfo;
m_iOffset = iOffset;
}
QueueEditor::QueueEditor()
{
debug("Creating QueueEditor");
}
QueueEditor::~QueueEditor()
{
debug("Destroying QueueEditor");
}
FileInfo* QueueEditor::FindFileInfo(DownloadQueue* pDownloadQueue, int iID)
{
for (DownloadQueue::iterator it = pDownloadQueue->begin(); it != pDownloadQueue->end(); it++)
{
FileInfo* pFileInfo = *it;
if (pFileInfo->GetID() == iID)
{
return pFileInfo;
}
}
return NULL;
}
int QueueEditor::FindFileInfoEntry(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo)
{
int iEntry = 0;
for (DownloadQueue::iterator it = pDownloadQueue->begin(); it != pDownloadQueue->end(); it++)
{
FileInfo* pFileInfo2 = *it;
if (pFileInfo2 == pFileInfo)
{
return iEntry;
}
iEntry ++;
}
return -1;
}
/*
* Set the pause flag of the specific entry in the queue
* returns true if successful, false if operation is not possible
*/
void QueueEditor::PauseUnpauseEntry(FileInfo* pFileInfo, bool bPause)
{
pFileInfo->SetPaused(bPause);
}
/*
* Removes entry with index iEntry
* returns true if successful, false if operation is not possible
*/
void QueueEditor::DeleteEntry(FileInfo* pFileInfo)
{
info("Deleting file %s from download queue", pFileInfo->GetFilename());
g_pQueueCoordinator->DeleteQueueEntry(pFileInfo);
}
/*
* Moves entry identified with iID in the queue
* returns true if successful, false if operation is not possible
*/
void QueueEditor::MoveEntry(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo, int iOffset)
{
int iEntry = FindFileInfoEntry(pDownloadQueue, pFileInfo);
if (iEntry > -1)
{
int iNewEntry = iEntry + iOffset;
if (iNewEntry < 0)
{
iNewEntry = 0;
}
if ((unsigned int)iNewEntry > pDownloadQueue->size() - 1)
{
iNewEntry = (int)pDownloadQueue->size() - 1;
}
if (iNewEntry >= 0 && (unsigned int)iNewEntry <= pDownloadQueue->size() - 1)
{
FileInfo* fi = (*pDownloadQueue)[iEntry];
pDownloadQueue->erase(pDownloadQueue->begin() + iEntry);
pDownloadQueue->insert(pDownloadQueue->begin() + iNewEntry, fi);
}
}
}
bool QueueEditor::EditEntry(int ID, bool bSmartOrder, EEditAction eAction, int iOffset)
{
IDList cIDList;
cIDList.clear();
cIDList.push_back(ID);
return EditList(&cIDList, bSmartOrder, eAction, iOffset);
}
bool QueueEditor::LockedEditEntry(DownloadQueue* pDownloadQueue, int ID, bool bSmartOrder, EEditAction eAction, int iOffset)
{
IDList cIDList;
cIDList.clear();
cIDList.push_back(ID);
return InternEditList(pDownloadQueue, &cIDList, bSmartOrder, eAction, iOffset);
}
bool QueueEditor::EditList(IDList* pIDList, bool bSmartOrder, EEditAction eAction, int iOffset)
{
DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue();
bool bOK = InternEditList(pDownloadQueue, pIDList, bSmartOrder, eAction, iOffset);
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
{
g_pDiskState->Save(pDownloadQueue);
}
g_pQueueCoordinator->UnlockQueue();
return bOK;
}
bool QueueEditor::InternEditList(DownloadQueue* pDownloadQueue, IDList* pIDList, bool bSmartOrder, EEditAction eAction, int iOffset)
{
if (eAction == eaGroupMoveOffset)
{
AlignAffectedGroups(pDownloadQueue, pIDList, bSmartOrder, iOffset);
}
ItemList cItemList;
PrepareList(pDownloadQueue, &cItemList, pIDList, bSmartOrder, eAction, iOffset);
if (eAction == eaFilePauseAllPars || eAction == eaFilePauseExtraPars)
{
PauseParsInGroups(&cItemList, eAction == eaFilePauseExtraPars);
}
else
{
for (ItemList::iterator it = cItemList.begin(); it != cItemList.end(); it++)
{
EditItem* pItem = *it;
switch (eAction)
{
case eaFilePause:
PauseUnpauseEntry(pItem->m_pFileInfo, true);
break;
case eaFileResume:
PauseUnpauseEntry(pItem->m_pFileInfo, false);
break;
case eaFileMoveOffset:
case eaFileMoveTop:
case eaFileMoveBottom:
MoveEntry(pDownloadQueue, pItem->m_pFileInfo, pItem->m_iOffset);
break;
case eaFileDelete:
DeleteEntry(pItem->m_pFileInfo);
break;
case eaFilePauseAllPars:
case eaFilePauseExtraPars:
// remove compiler warning "enumeration not handled in switch"
break;
case eaGroupPause:
case eaGroupResume:
case eaGroupDelete:
case eaGroupMoveTop:
case eaGroupMoveBottom:
case eaGroupMoveOffset:
case eaGroupPauseAllPars:
case eaGroupPauseExtraPars:
EditGroup(pDownloadQueue, pItem->m_pFileInfo, eAction, iOffset);
break;
}
delete pItem;
}
}
return cItemList.size() > 0;
}
void QueueEditor::PrepareList(DownloadQueue* pDownloadQueue, ItemList* pItemList, IDList* pIDList, bool bSmartOrder,
EEditAction EEditAction, int iOffset)
{
if (EEditAction == eaFileMoveTop)
{
iOffset = -MAX_ID;
}
else if (EEditAction == eaFileMoveBottom)
{
iOffset = MAX_ID;
}
pItemList->reserve(pIDList->size());
if (bSmartOrder && iOffset != 0 &&
(EEditAction == eaFileMoveOffset || EEditAction == eaFileMoveTop || EEditAction == eaFileMoveBottom))
{
//add IDs to list in order they currently have in download queue
int iLastDestPos = -1;
int iStart, iEnd, iStep;
if (iOffset < 0)
{
iStart = 0;
iEnd = pDownloadQueue->size();
iStep = 1;
}
else
{
iStart = pDownloadQueue->size() - 1;
iEnd = -1;
iStep = -1;
}
for (int iIndex = iStart; iIndex != iEnd; iIndex += iStep)
{
FileInfo* pFileInfo = (*pDownloadQueue)[iIndex];
int iID = pFileInfo->GetID();
for (IDList::iterator it = pIDList->begin(); it != pIDList->end(); it++)
{
if (iID == *it)
{
int iWorkOffset = iOffset;
int iDestPos = iIndex + iWorkOffset;
if (iLastDestPos == -1)
{
if (iDestPos < 0)
{
iWorkOffset = -iIndex;
}
else if (iDestPos > int(pDownloadQueue->size()) - 1)
{
iWorkOffset = int(pDownloadQueue->size()) - 1 - iIndex;
}
}
else
{
if (iWorkOffset < 0 && iDestPos <= iLastDestPos)
{
iWorkOffset = iLastDestPos - iIndex + 1;
}
else if (iWorkOffset > 0 && iDestPos >= iLastDestPos)
{
iWorkOffset = iLastDestPos - iIndex - 1;
}
}
iLastDestPos = iIndex + iWorkOffset;
pItemList->push_back(new EditItem(pFileInfo, iWorkOffset));
break;
}
}
}
}
else
{
// check ID range
int iMaxID = 0;
int iMinID = MAX_ID;
for (DownloadQueue::iterator it = pDownloadQueue->begin(); it != pDownloadQueue->end(); it++)
{
FileInfo* pFileInfo = *it;
int ID = pFileInfo->GetID();
if (ID > iMaxID)
{
iMaxID = ID;
}
if (ID < iMinID)
{
iMinID = ID;
}
}
//add IDs to list in order they were transmitted in command
for (IDList::iterator it = pIDList->begin(); it != pIDList->end(); it++)
{
int iID = *it;
if (iMinID <= iID && iID <= iMaxID)
{
FileInfo* pFileInfo = FindFileInfo(pDownloadQueue, iID);
if (pFileInfo)
{
pItemList->push_back(new EditItem(pFileInfo, iOffset));
}
}
}
}
}
bool QueueEditor::EditGroup(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo, EEditAction eAction, int iOffset)
{
IDList cIDList;
cIDList.clear();
// collecting files belonging to group
for (DownloadQueue::iterator it = pDownloadQueue->begin(); it != pDownloadQueue->end(); it++)
{
FileInfo* pFileInfo2 = *it;
if (!strcmp(pFileInfo2->GetNZBFilename(), pFileInfo->GetNZBFilename()))
{
cIDList.push_back(pFileInfo2->GetID());
}
}
if (eAction == eaGroupMoveOffset)
{
// calculating offset in terms of files
FileList cGroupList;
BuildGroupList(pDownloadQueue, &cGroupList);
unsigned int iNum = 0;
for (FileList::iterator it = cGroupList.begin(); it != cGroupList.end(); it++, iNum++)
{
FileInfo* pGroupInfo = *it;
if (!strcmp(pGroupInfo->GetNZBFilename(), pFileInfo->GetNZBFilename()))
{
break;
}
}
int iFileOffset = 0;
if (iOffset > 0)
{
if (iNum + iOffset >= cGroupList.size() - 1)
{
eAction = eaGroupMoveBottom;
}
else
{
for (unsigned int i = iNum + 2; i < cGroupList.size() && iOffset > 0; i++, iOffset--)
{
iFileOffset += FindFileInfoEntry(pDownloadQueue, cGroupList[i]) - FindFileInfoEntry(pDownloadQueue, cGroupList[i-1]);
}
}
}
else
{
if (iNum + iOffset <= 0)
{
eAction = eaGroupMoveTop;
}
else
{
for (unsigned int i = iNum; i > 0 && iOffset < 0; i--, iOffset++)
{
iFileOffset -= FindFileInfoEntry(pDownloadQueue, cGroupList[i]) - FindFileInfoEntry(pDownloadQueue, cGroupList[i-1]);
}
}
}
iOffset = iFileOffset;
}
EEditAction GroupToFileMap[] = { (EEditAction)0, eaFileMoveOffset, eaFileMoveTop, eaFileMoveBottom, eaFilePause, eaFileResume, eaFileDelete, eaFilePauseAllPars, eaFilePauseExtraPars,
eaFileMoveOffset, eaFileMoveTop, eaFileMoveBottom, eaFilePause, eaFileResume, eaFileDelete, eaFilePauseAllPars, eaFilePauseExtraPars };
return InternEditList(pDownloadQueue, &cIDList, true, GroupToFileMap[eAction], iOffset);
}
void QueueEditor::BuildGroupList(DownloadQueue* pDownloadQueue, FileList* pGroupList)
{
pGroupList->clear();
for (DownloadQueue::iterator it = pDownloadQueue->begin(); it != pDownloadQueue->end(); it++)
{
FileInfo* pFileInfo = *it;
FileInfo* pGroupInfo = NULL;
for (FileList::iterator itg = pGroupList->begin(); itg != pGroupList->end(); itg++)
{
FileInfo* pGroupInfo1 = *itg;
if (!strcmp(pGroupInfo1->GetNZBFilename(), pFileInfo->GetNZBFilename()))
{
pGroupInfo = pGroupInfo1;
break;
}
}
if (!pGroupInfo)
{
pGroupList->push_back(pFileInfo);
}
}
}
bool QueueEditor::ItemExists(FileList* pFileList, FileInfo* pFileInfo)
{
for (FileList::iterator it = pFileList->begin(); it != pFileList->end(); it++)
{
if (*it == pFileInfo)
{
return true;
}
}
return false;
}
void QueueEditor::AlignAffectedGroups(DownloadQueue* pDownloadQueue, IDList* pIDList, bool bSmartOrder, int iOffset)
{
// Build list of all groups; List contains first file of each group
FileList cGroupList;
BuildGroupList(pDownloadQueue, &cGroupList);
// Find affected groups. It includes groups being moved and groups directly
// above or under of these groups (those order is also changed)
FileList cAffectedGroupList;
cAffectedGroupList.clear();
ItemList cItemList;
PrepareList(pDownloadQueue, &cItemList, pIDList, bSmartOrder, eaFileMoveOffset, iOffset);
for (ItemList::iterator it = cItemList.begin(); it != cItemList.end(); it++)
{
EditItem* pItem = *it;
unsigned int iNum = 0;
for (FileList::iterator it = cGroupList.begin(); it != cGroupList.end(); it++, iNum++)
{
FileInfo* pFileInfo = *it;
if (!strcmp(pItem->m_pFileInfo->GetNZBFilename(), pFileInfo->GetNZBFilename()))
{
if (!ItemExists(&cAffectedGroupList, pFileInfo))
{
cAffectedGroupList.push_back(pFileInfo);
}
if (iOffset < 0)
{
for (int i = iNum - 1; i >= -iOffset-1; i--)
{
if (!ItemExists(&cAffectedGroupList, cGroupList[i]))
{
cAffectedGroupList.push_back(cGroupList[i]);
}
}
}
if (iOffset > 0)
{
for (unsigned int i = iNum + 1; i <= cGroupList.size() - iOffset; i++)
{
if (!ItemExists(&cAffectedGroupList, cGroupList[i]))
{
cAffectedGroupList.push_back(cGroupList[i]);
}
}
if (iNum + 1 < cGroupList.size())
{
cAffectedGroupList.push_back(cGroupList[iNum + 1]);
}
}
break;
}
}
delete pItem;
}
cGroupList.clear();
// Aligning groups
for (FileList::iterator it = cAffectedGroupList.begin(); it != cAffectedGroupList.end(); it++)
{
FileInfo* pFileInfo = *it;
AlignGroup(pDownloadQueue, pFileInfo);
}
}
void QueueEditor::AlignGroup(DownloadQueue* pDownloadQueue, FileInfo* pFirstFileInfo)
{
FileInfo* pLastFileInfo = NULL;
unsigned int iLastNum = 0;
unsigned int iNum = 0;
while (iNum < pDownloadQueue->size())
{
FileInfo* pFileInfo = (*pDownloadQueue)[iNum];
if (!strcmp(pFirstFileInfo->GetNZBFilename(), pFileInfo->GetNZBFilename()))
{
if (pLastFileInfo && iNum - iLastNum > 1)
{
pDownloadQueue->erase(pDownloadQueue->begin() + iNum);
pDownloadQueue->insert(pDownloadQueue->begin() + iLastNum + 1, pFileInfo);
iLastNum++;
}
else
{
iLastNum = iNum;
}
pLastFileInfo = pFileInfo;
}
iNum++;
}
}
void QueueEditor::PauseParsInGroups(ItemList* pItemList, bool bExtraParsOnly)
{
while (true)
{
FileList GroupFileList;
GroupFileList.clear();
FileInfo* pFirstFileInfo = NULL;
for (ItemList::iterator it = pItemList->begin(); it != pItemList->end(); )
{
EditItem* pItem = *it;
if (!pFirstFileInfo ||
!strcmp(pFirstFileInfo->GetNZBFilename(), pItem->m_pFileInfo->GetNZBFilename()))
{
GroupFileList.push_back(pItem->m_pFileInfo);
if (!pFirstFileInfo)
{
pFirstFileInfo = pItem->m_pFileInfo;
}
delete pItem;
pItemList->erase(it);
it = pItemList->begin();
continue;
}
it++;
}
if (!GroupFileList.empty())
{
PausePars(&GroupFileList, bExtraParsOnly);
}
else
{
break;
}
}
}
/**
* If the parameter "bExtraParsOnly" is set to "false", then we pause all par2-files.
* If the parameter "bExtraParsOnly" is set to "true", we use the following strategy:
* At first we find all par-files, which do not have "vol" in their names, then we pause
* all vols and do not affect all just-pars.
* In a case, if there are no just-pars, but only vols, we find the smallest vol-file
* and do not affect it, but pause all other pars.
*/
void QueueEditor::PausePars(FileList* pFileList, bool bExtraParsOnly)
{
debug("QueueEditor: Pausing pars");
FileList Pars, Vols;
Pars.clear();
Vols.clear();
for (FileList::iterator it = pFileList->begin(); it != pFileList->end(); it++)
{
FileInfo* pFileInfo = *it;
char szLoFileName[1024];
strncpy(szLoFileName, pFileInfo->GetFilename(), 1024);
szLoFileName[1024-1] = '\0';
for (char* p = szLoFileName; *p; p++) *p = tolower(*p); // convert string to lowercase
if (strstr(szLoFileName, ".par2"))
{
if (!bExtraParsOnly)
{
pFileInfo->SetPaused(true);
}
else
{
if (strstr(szLoFileName, ".vol"))
{
Vols.push_back(pFileInfo);
}
else
{
Pars.push_back(pFileInfo);
}
}
}
}
if (bExtraParsOnly)
{
if (!Pars.empty())
{
for (FileList::iterator it = Vols.begin(); it != Vols.end(); it++)
{
FileInfo* pFileInfo = *it;
pFileInfo->SetPaused(true);
}
}
else
{
// pausing all Vol-files except the smallest one
FileInfo* pSmallest = NULL;
for (FileList::iterator it = Vols.begin(); it != Vols.end(); it++)
{
FileInfo* pFileInfo = *it;
if (!pSmallest)
{
pSmallest = pFileInfo;
}
else if (pSmallest->GetSize() > pFileInfo->GetSize())
{
pSmallest->SetPaused(true);
pSmallest = pFileInfo;
}
else
{
pFileInfo->SetPaused(true);
}
}
}
}
}

98
QueueEditor.h Normal file
View File

@@ -0,0 +1,98 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2007 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$
*
*/
#ifndef QUEUEEDITOR_H
#define QUEUEEDITOR_H
#include <vector>
#include "DownloadInfo.h"
class QueueEditor
{
public:
typedef std::vector<int> IDList;
enum EEditAction
{
eaFileMoveOffset = 1, // move to m_iOffset relative to the current position in queue
eaFileMoveTop,
eaFileMoveBottom,
eaFilePause,
eaFileResume,
eaFileDelete,
eaFilePauseAllPars,
eaFilePauseExtraPars,
eaGroupMoveOffset, // move to m_iOffset relative to the current position in queue
eaGroupMoveTop,
eaGroupMoveBottom,
eaGroupPause,
eaGroupResume,
eaGroupDelete,
eaGroupPauseAllPars,
eaGroupPauseExtraPars
};
private:
class EditItem
{
public:
int m_iOffset;
FileInfo* m_pFileInfo;
EditItem(FileInfo* pFileInfo, int iOffset);
};
typedef std::vector<EditItem*> ItemList;
typedef std::vector<FileInfo*> FileList;
private:
FileInfo* FindFileInfo(DownloadQueue* pDownloadQueue, int iID);
int FindFileInfoEntry(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo);
bool InternEditList(DownloadQueue* pDownloadQueue, IDList* pIDList, bool bSmartOrder, EEditAction eAction, int iOffset);
void PrepareList(DownloadQueue* pDownloadQueue, ItemList* pItemList, IDList* pIDList, bool bSmartOrder, EEditAction eAction, int iOffset);
bool EditGroup(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo, EEditAction eAction, int iOffset);
void BuildGroupList(DownloadQueue* pDownloadQueue, FileList* pGroupList);
void AlignAffectedGroups(DownloadQueue* pDownloadQueue, IDList* pIDList, bool bSmartOrder, int iOffset);
bool ItemExists(FileList* pFileList, FileInfo* pFileInfo);
void AlignGroup(DownloadQueue* pDownloadQueue, FileInfo* pFirstFileInfo);
void PauseParsInGroups(ItemList* pItemList, bool bExtraParsOnly);
void PausePars(FileList* pFileList, bool bExtraParsOnly);
void PauseUnpauseEntry(FileInfo* pFileInfo, bool bPause);
void DeleteEntry(FileInfo* pFileInfo);
void MoveEntry(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo, int iOffset);
public:
QueueEditor();
~QueueEditor();
bool EditEntry(int ID, bool bSmartOrder, EEditAction eAction, int iOffset);
bool EditList(IDList* pIDList, bool bSmartOrder, EEditAction eAction, int iOffset);
bool LockedEditEntry(DownloadQueue* pDownloadQueue, int ID, bool bSmartOrder, EEditAction eAction, int iOffset);
};
#endif

376
README
View File

@@ -2,10 +2,6 @@
NZBGet ReadMe
=====================================
This is a short documentation. For more information please
visit NZBGet home page at
http://nzbget.net
Contents
--------
1. About NZBGet
@@ -33,9 +29,6 @@ In server/client mode NZBGet runs as server in background.
Then you use client to send requests to server. The sample requests
are: download nzb-file, list files in queue, etc.
There is also a built-in web-interface. The server has RPC-support
and can be controlled from third party applications too.
Standalone-tool, server and client are all contained in only one
executable file "nzbget". The mode in which the program works
depends on command-line parameters passed to the program.
@@ -44,16 +37,29 @@ depends on command-line parameters passed to the program.
2. Supported OS
=====================================
NZBGet is written in C++ and works on Windows, OS X, Linux and
most POSIX-conform OS'es.
NZBGet is written in C++ and was initialy developed on Linux.
It was ported to Windows later and tested for compatibility with
several POSIX-OS'es.
The current version (0.3.1) should run at least on:
- Linux Debian 4.0 on x86;
- Linux BusyBox with uClibc on MIPSEL;
- PC-BSD 1.4 (based on FreeBSD 6.2) on x86;
- Windows XP SP2 on x86.
The previous version (0.3.0) was also tested on:
- Linux Debian 3.1 on x86;
- Solaris 10 on x86;
- Linux Debian 3.1 on SPARC (QEmu).
Clients and servers running on different OS'es may communicate with
each other. For example, you can use NZBGet as client on Windows to
control your NZBGet-server running on Linux.
The download-section of NZBGet web-site provides binary files
for Windows, OS X and Linux. For most POSIX-systems you need to compile
the program yourself.
for Windows. The binary packages for many routers and NAS devices are
also available in OPTWARE repository (http://www.nslu2-linux.org),
but for most POSIX-systems you need to compile the program yourself.
If you have downloaded binaries you can just jump to section
"Configuration".
@@ -62,8 +68,8 @@ If you have downloaded binaries you can just jump to section
3. Prerequisites on POSIX
=====================================
NZBGet is developed on a linux-system, but it runs on other
POSIX platforms.
NZBGet is developed on a linux-system, but it should run on other
POSIX platforms (see the list of tested platforms above).
NZBGet absolutely needs the following libraries:
@@ -76,174 +82,178 @@ And the following libraries are optional:
- libcurses (usually part of commercial systems)
or (better)
- libncurses (http://invisible-island.net/ncurses)
- for encrypted connections (TLS/SSL):
- OpenSSL (http://www.openssl.org)
or
- GnuTLS (http://www.gnu.org/software/gnutls)
- for par-check and -repair (enabled by default):
- libpar2 (http://parchive.sourceforge.net)
- libsigc++ (http://libsigc.sourceforge.net)
- for gzip support in web-server and web-client (enabled by default):
- zlib (http://www.zlib.net)
- for support of encoding-formats other than yEnc (disabled by default):
- libuu (http://www.fpx.de/fp/Software/UUDeview)
All these libraries are included in modern POSIX distributions and
All these libraries are included in modern Linux distributions and
should be available as installable packages. Please note that you also
need the developer packages for these libraries too, they package names
have often suffix "dev" or "devel". On other systems you may need to
download the libraries at the given URLs and compile them (see hints below).
=====================================
4. Installation on POSIX
=====================================
Installation from the source distribution archive (nzbget-VERSION.tar.gz):
Well, the usual stuff:
- untar the nzbget-source via
tar -zxf nzbget-VERSION.tar.gz
- change into nzbget-directory via
cd nzbget-VERSION
- configure it via
./configure
(maybe you have to tell configure, where to find some libraries.
./configure --help is your friend!
also see "Configure-options" later)
- in a case you don't have root access or want to install the program
in your home directory use the configure parameter --prefix, e. g.:
./configure --prefix ~/usr
./configure --help is your friend! ;-)
also see "Configure-options" later.)
- compile it via
make
- to install system wide become root via:
(you may get some warnings concerning 'mktemp', simply ignore them!)
- become root via
su
- install it via:
- install it via
make install
- install configuration files into <prefix>/etc via:
make install-conf
(you can skip this step if you intend to store configuration
files in a non-standard location)
Configure-options
-----------------
You may run configure with additional arguments:
--enable-uulib - to make with uulib-library, in a case you want it
(see later section "Optional package: uulib"). This option is not
enabled by default.
--disable-curses - to make without curses-support. Use this option
if you can not use curses/ncurses.
--disable-parcheck - to make without parcheck-support. Use this option
if you have troubles when compiling par2-module.
--with-tlslib=(OpenSSL, GnuTLS) - to select which TLS/SSL library
should be used for encrypted server connections.
--disable-tls - to make without TLS/SSL support. Use this option if
you can not neither OpenSSL nor GnuTLS.
--disable-gzip - to make without gzip support. Use this option
if you can not use zlib.
if you can not use libpar2 or libsigc++.
--enable-debug - to build in debug-mode, if you want to see and log
debug-messages.
Optional package: uulib
-----------------------
uulib is not required to compile and run nzbget, because nzbget includes
internal decoder for yEnc-format. However, uulib supports many other formats,
you may possibly want to have support for. In this case you can build the
program with uulib-support enabled.
NOTE: enabling uulib does not disable internal decoder. The program built with
uulib-support can use both decoders (internal and uulib), depending on option
"decoder" in program's configuration file.
To build with uulib use option "--enable-uulib" while running configure:
./configure --enable-uulib
The uulib must be installed on your system. On most linux distributions
the package uulib-dev is available. So you only need to install this package
and run configure with parameter "--enable-uulib".
If you do not have this package you can compile uulib yourself:
- download source code of uudeview from
http://www.fpx.de/fp/Software/UUDeview;
- build uudeview as usually:
/.confugure
make
- start nzbget's configure-script with following parameters:
./configure --enable-uulib \
--with-uulib-includes=<path to uudeview>/uulib \
--with-uulib-libraries=<path to uudeview>/uulib
for example:
./configure --enable-uulib \
--with-uulib-includes=/home/user/uudeview-0.5.20/uulib \
--with-uulib-libraries=/home/user/uudeview-0.5.20/uulib
- now you can compile nzbget.
NOTE: after nzbget is compiled, the code of uulib-library is built into
nzbget's executable. You do not need to have uulib on target system
to run nzbget.
Optional package: par-check
---------------------------
NZBGet can check and repair downloaded files for you. For this purpose
it uses library par2.
it uses library par2 (libpar2), which needs sigc++ on its part.
For your convenience the source code of libpar2 is integrated into
NZBGets source tree and is compiled automatically when you make NZBGet.
To build with par-check use option "--enable-parcheck" while running
configure:
./configure --enable-parcheck
In a case errors occur during this process the inclusion of par2-module
can be disabled using configure option "--disable-parcheck":
The libpar2 and libsigc++ (version 2 or later) must be installed on your
system. On most linux distributions these libraries are available as packages.
So you only need to install theme and run configure with parameter
"--enable-parcheck".
If you do not have these package you can compile them yourself. Please
refer to section "Optional package: uulib" for an example on how to
compile additional library. Following configure-parameters may be usefull:
./configure --disable-parcheck
--with-libpar2-includes
--with-libpar2-libraries
--with-libsigc-includes
--with-libsigc-libraries
The library libsigc++ must be installed first, since libpar2 requires it.
Optional package: curses
-------------------------
For curses-outputmode you need ncurses or curses on your system.
If you do not have one of them you can download and compile ncurses yourself.
Following configure-parameters may be useful:
Please refer to section "Optional package: uulib" for an example on how to
compile additional library. Following configure-parameters may be usefull:
--with-libcurses-includes=/path/to/curses/includes
--with-libcurses-libraries=/path/to/curses/libraries
--with-libcurses-includes
--with-libcurses-libraries
If you are not able to use curses or ncurses or do not want them you can
make the program without support for curses using option "--disable-curses":
./configure --disable-curses
Optional package: TLS
-------------------------
To enable encrypted server connections (TLS/SSL) you need to build the program
with TLS/SSL support. NZBGet can use two libraries: OpenSSL or GnuTLS.
Configure-script checks which library is installed and use it. If both are
available it gives the precedence to OpenSSL. You may override that with
the option --with-tlslib=(OpenSSL, GnuTLS). For example to build with GnuTLS:
./configure --with-tlslib= GnuTLS
Following configure-parameters may be useful:
--with-libtls-includess=/path/to/gnutls/includes
--with-libtls-libraries=/path/to/gnutls/libraries
--with-openssl-includess=/path/to/openssl/includes
--with-openssl-libraries=/path/to/openssl/libraries
If none of these libraries is available you can make the program without
TLS/SSL support using option "--disable-tls":
./configure --disable-tls
=====================================
5. Compiling on Windows
=====================================
NZBGet is developed using MS Visual Studio 2015 (Community Edition). The project
file is provided.
NZBGet is developed using MS Visual C++ 2005. The project file and solution
are provided. If you use MS Visual C++ 2005 Express you need to download
and install Platform SDK.
To compile the program with TLS/SSL support you need either OpenSSL or GnuTLS:
- OpenSSL (http://www.openssl.org)
or
- GnuTLS (http://www.gnu.org/software/gnutls)
To compile the program with par-check-support you also need the following
libraries:
Also required are:
- Regex (http://gnuwin32.sourceforge.net/packages/regex.htm)
- Zlib (http://gnuwin32.sourceforge.net/packages/zlib.htm)
- libsigc++ (http://libsigc.sourceforge.net)
- libpar2 (http://parchive.sourceforge.net)
Download these libaries, then use patch-files provided with NZBGet to create
preconfigured project files and solutions for each library.
Look at http://gnuwin32.sourceforge.net/packages/patch.htm for info on how
to use patch-files, if you do not familiar with this technique.
After libsigc++ and libpar2 are compiled in static libraries (.lib)
and include- and libraries-paths are configured in MS Visual C++ 2005 you
should be able to compile NZBGet.
=====================================
6. Configuration
=====================================
NZBGet needs a configuration file.
NZBGet needs a configuration-file to work properly.
An example configuration file is provided in "nzbget.conf", which
is installed into "<prefix>/share/nzbget" (where <prefix> depends on
system configuration and configure options - typically "/usr/local",
"/usr" or "/opt"). The installer adjusts the file according to your
system paths. If you have performed the installation step
"make install-conf" this file is already copied to "<prefix>/etc" and
NZBGet finds it automatically. If you install the program manually
from a binary archive you have to copy the file from "<prefix>/share/nzbget"
to one of the locations listed below.
Open the file in a text editor and modify it accodring to your needs.
You need to set at least the option "MAINDIR" and one news server in
configuration file. The file has comments on how to use each option.
You need to set at least the option "MAINDIR" and one newsserver in
configuration file. Have a look at the example in nzbget.conf.example,
it has comments on how to use each option.
The program looks for configuration file in following standard
locations (in this order):
On POSIX systems:
<EXE-DIR>/nzbget.conf
~/.nzbget
/etc/nzbget.conf
/usr/etc/nzbget.conf
@@ -267,12 +277,6 @@ options via command-line.
NZBGet can be used in either standalone mode which downloads a single file
or as a server which is able to queue up numerous download requests.
TIP for Windows users: NZBGet is controlled via various command line
parameters. For easier using there is a simple shell script included
in "nzbget-shell.bat". Start this script from Windows Explorer and you will
be running a command shell with PATH adjusted to find NZBGet executable.
Then you can type all commands without full path to nzbget.exe.
Standalone mode:
----------------
@@ -300,20 +304,15 @@ To stop server use:
nzbget -Q
TIP for POSIX users: with included script "nzbgetd" you can use standard
commands to control daemon:
nzbgetd start
nzbgetd stop
etc.
When NZBGet is started in console server mode it displays a message that
it is ready to receive download requests. In daemon mode it doesn't print any
messages to console since it runs in background.
Depending on which frontend has been selected in the nzbget.conf file
(option "outputmode") the server should display a message that
it is ready to receive download requests (this applies only to console
mode, not to daemon mode).
When the server is running it is possible to queue up downloads. This can be
done either in terminal with "nzbget -A <nzb-file>" or by uploading
a nzb-file into server's monitor-directory (<MAINDIR>/nzb by default).
a nzb-file into server's monitor-directory (<MAINDIR>/nzb by default, the
directory must exist on server's start; otherwise it will not be monitored).
To check the status of server start client and connect it to server:
@@ -336,18 +335,9 @@ It prints something like:
[1] nzbname\filename1.rar (50.00 MB)
[2] nzbname\filename1.r01 (50.00 MB)
[3] another-nzb\filename3.r01 (100.00 MB)
[4] another-nzb\filename3.r02 (100.00 MB)
This is the list of individual files listed within nzb-file. To print
the list of nzb-files (without content) add G-modifier to the list command:
[1] nzbname (4.56 GB)
[2] another-nzb (4.20 GB)
The numbers in square braces are ID's of files or groups in queue.
They can be used in edit-command. For example to move file with
ID 2 to the top of queue:
The numbers in square braces are ID's of files in queue. They can be used
in edit-command. For example to move file with ID 2 to the top of queue:
nzbget -E T 2
@@ -360,8 +350,8 @@ or to delete files from queue:
nzbget -E D 3 10-15 20-21 16
The edit-command has also a group-mode which affects all files from the
same nzb-file. You need to pass an ID of the group. For example to delete
the whole group 1:
same nzb-request. You need to pass one ID of any file in the group. For
example to delete all files from the first nzb-request:
nzbget -E G D 1
@@ -380,7 +370,7 @@ Running client & server on seperate machines:
Since nzbget communicates via TCP/IP it's possible to have a server running on
one computer and adding downloads via a client on another computer.
Do this by setting the "ControlIP" option in the nzbget.conf file to point to the
Do this by setting the "serverip" option in the nzbget.conf file to point to the
IP of the server (default is localhost which means client and server runs on
same computer)
@@ -396,112 +386,36 @@ If you need to control server from WAN it is better to connect to server's
terminal via SSH (POSIX) or remote desktop (Windows) and then run
nzbget-client-commands in this terminal.
Post processing scripts
-----------------------
After the download of nzb-file is completed nzbget can call post-processing
scripts, defined in configuration file.
Example post-processing scripts are provided in directory "scripts".
To use the scripts copy them into your local directory and set options
<ScriptDir>, <PostScript> and <ScriptOrder>.
For information on writing your own post-processing scripts please
visit NZBGet web site.
Web-interface
-------------
NZBGet has a built-in web-server providing the access to the program
functions via web-interface.
To activate web-interface set the option "WebDir" to the path with
web-interface files. If you install using "make install-conf" as
described above the option is set automatically. If you install using
binary files you should check if the option is set correctly.
To access web-interface from your web-browser use the server address
and port defined in NZBGet configuration file in options "ControlIP" and
"ControlPort". For example:
http://localhost:6789/
For login credentials type username and the password defined by
options "ControlUsername" (default "nzbget") and "ControlPassword"
(default "tegbzn6789").
In a case your browser forget credentials, to prevent typing them each
time, there is a workaround - use URL in the form:
http://localhost:6789/username:password/
Please note, that in this case the password is saved in a bookmark or in
browser history in plain text and is easy to find by persons having
access to your computer.
=====================================
8. Authors
=====================================
NZBGet is developed and maintained by Andrey Prygunkov
(hugbug@users.sourceforge.net).
The original project was initially created by Sven Henkel
(sidddy@users.sourceforge.net) in 2004 and later developed by
Bo Cordes Petersen (placebodk@users.sourceforge.net) until 2005.
In 2007 the abandoned project was overtaken by Andrey Prygunkov.
Since then the program has been completely rewritten.
NZBGet distribution archive includes additional components
written by other authors:
Par2:
Peter Brian Clements <peterbclements@users.sourceforge.net>
Par2 library API:
Francois Lesueur <flesueur@users.sourceforge.net>
Catch:
Two Blue Cubes Ltd <https://github.com/philsquared/Catch>
jQuery:
John Resig <http://jquery.com>
The Dojo Foundation <http://sizzlejs.com>
Bootstrap:
Twitter, Inc <http://twitter.github.com/bootstrap>
Raphaël:
Dmitry Baranovskiy <http://raphaeljs.com>
Sencha Labs <http://sencha.com>
Elycharts:
Void Labs s.n.c. <http://void.it>
iconSweets:
Yummygum <http://yummygum.com>
NZBGet was initialiy written by Sven Henkel (sidddy@users.sourceforge.net).
Up to version 0.2.3 it was been developed and maintained by Bo Cordes Petersen
(placebodk@users.sourceforge.net). Beginning at version 0.3.0 the program is
being developed by Andrei Prygounkov (hugbug@users.sourceforge.net).
=====================================
9. Copyright
=====================================
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.
NZBGet is distributed under GNU General Pubic License Version 2.
The complete content of license is provided in file COPYING.
Additional exemption: compiling, linking, and/or using OpenSSL is allowed.
Binary distribution for Windows contains code from the following libraries:
- libpar2 (http://parchive.sourceforge.net)
- libsigc++ (http://libsigc.sourceforge.net)
libpar2 is distributed under GPL and libsigc++ under LGPL.
=====================================
10. Contact
=====================================
If you encounter any problem, feel free to use the forum
If you encounter any problem, feel free to use tracker/forums on
nzbget.net/forum
sourceforge.net/projects/nzbget
or contact me at

View File

@@ -1,19 +0,0 @@
# NZBGet #
[![License](https://img.shields.io/badge/license-GPL-blue.svg)](http://www.gnu.org/licenses/)
[![Build Status](https://img.shields.io/travis/nzbget/nzbget/develop.svg)](https://travis-ci.org/nzbget/nzbget)
[![Code Quality: Cpp](https://img.shields.io/lgtm/grade/cpp/g/nzbget/nzbget.svg?label=code%20quality:%20c%2b%2b)](https://lgtm.com/projects/g/nzbget/nzbget/context:cpp)
[![Code Quality: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/nzbget/nzbget.svg?label=code%20quality:%20js)](https://lgtm.com/projects/g/nzbget/nzbget/context:javascript)
[![Total Alerts](https://img.shields.io/lgtm/alerts/g/nzbget/nzbget.svg)](https://lgtm.com/projects/g/nzbget/nzbget/alerts)
[![Total downloads](https://img.shields.io/github/downloads/nzbget/nzbget/total.svg)](https://github.com/nzbget/nzbget/releases)
[![Downloads (latest release)](https://img.shields.io/github/downloads/nzbget/nzbget/latest/total.svg?label=latest%20release)](https://github.com/nzbget/nzbget/releases/latest)
NZBGet is a binary downloader, which downloads files from Usenet
based on information given in nzb-files.
NZBGet is written in C++ and is known for its performance and efficiency.
NZBGet can run on almost any device - classic PC, NAS, media player, SAT-receiver, WLAN-router, etc.
The download area provides precompiled binaries for Windows, macOS, Linux (compatible with
many CPUs and platform variants), FreeBSD and Android. For other platforms
the program can be compiled from sources.

582
RemoteClient.cpp Normal file
View File

@@ -0,0 +1,582 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2005 Bo Cordes Petersen <placebodk@sourceforge.net>
* Copyright (C) 2007 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>
#ifdef WIN32
#include <windows.h>
#else
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#endif
#include <stdarg.h>
#include "nzbget.h"
#include "RemoteClient.h"
#include "DownloadInfo.h"
#include "Options.h"
#include "Log.h"
#include "Util.h"
extern Options* g_pOptions;
RemoteClient::RemoteClient()
{
m_pConnection = NULL;
m_pNetAddress = NULL;
m_bVerbose = true;
/*
printf("sizeof(SNZBRequestBase)=%i\n", sizeof(SNZBRequestBase));
printf("sizeof(SNZBDownloadRequest)=%i\n", sizeof(SNZBDownloadRequest));
printf("sizeof(SNZBListRequest)=%i\n", sizeof(SNZBListRequest));
printf("sizeof(SNZBListResponse)=%i\n", sizeof(SNZBListResponse));
printf("sizeof(SNZBListResponseEntry)=%i\n", sizeof(SNZBListResponseEntry));
printf("sizeof(SNZBLogRequest)=%i\n", sizeof(SNZBLogRequest));
printf("sizeof(SNZBLogResponse)=%i\n", sizeof(SNZBLogResponse));
printf("sizeof(SNZBLogResponseEntry)=%i\n", sizeof(SNZBLogResponseEntry));
printf("sizeof(SNZBPauseUnpauseRequest)=%i\n", sizeof(SNZBPauseUnpauseRequest));
printf("sizeof(SNZBSetDownloadRateRequest)=%i\n", sizeof(SNZBSetDownloadRateRequest));
printf("sizeof(SNZBEditQueueRequest)=%i\n", sizeof(SNZBEditQueueRequest));
printf("sizeof(SNZBDumpDebugRequest)=%i\n", sizeof(SNZBDumpDebugRequest));
*/
}
RemoteClient::~RemoteClient()
{
if (m_pConnection)
{
delete m_pConnection;
}
if (m_pNetAddress)
{
delete m_pNetAddress;
}
}
void RemoteClient::printf(char * msg,...)
{
if (m_bVerbose)
{
va_list ap;
va_start(ap, msg);
::vprintf(msg, ap);
va_end(ap);
}
}
void RemoteClient::perror(char * msg)
{
if (m_bVerbose)
{
::perror(msg);
}
}
bool RemoteClient::InitConnection()
{
// Create a connection to the server
m_pNetAddress = new NetAddress(g_pOptions->GetServerIP(), g_pOptions->GetServerPort());
m_pConnection = new Connection(m_pNetAddress);
bool OK = m_pConnection->Connect() >= 0;
if (!OK)
{
printf("Unable to send request to nzbserver at %s (port %i)\n", g_pOptions->GetServerIP(), g_pOptions->GetServerPort());
}
return OK;
}
void RemoteClient::InitMessageBase(SNZBRequestBase* pMessageBase, int iRequest, int iSize)
{
pMessageBase->m_iSignature = htonl(NZBMESSAGE_SIGNATURE);
pMessageBase->m_iType = htonl(iRequest);
pMessageBase->m_iStructSize = htonl(iSize);
strncpy(pMessageBase->m_szPassword, g_pOptions->GetServerPassword(), NZBREQUESTPASSWORDSIZE - 1);
pMessageBase->m_szPassword[NZBREQUESTPASSWORDSIZE - 1] = '\0';
}
bool RemoteClient::ReceiveBoolResponse()
{
printf("request sent\n");
// all bool-responses have the same format of structure, we use SNZBDownloadResponse here
SNZBDownloadResponse BoolResponse;
memset(&BoolResponse, 0, sizeof(BoolResponse));
int iResponseLen = m_pConnection->Recv((char*)&BoolResponse, sizeof(BoolResponse));
if (iResponseLen != sizeof(BoolResponse) ||
(int)ntohl(BoolResponse.m_MessageBase.m_iSignature) != (int)NZBMESSAGE_SIGNATURE ||
ntohl(BoolResponse.m_MessageBase.m_iStructSize) != sizeof(BoolResponse))
{
printf("invaid response received: either not nzbget-server or wrong server version\n");
return false;
}
int iTextLen = ntohl(BoolResponse.m_iTrailingDataLength);
char* buf = (char*)malloc(iTextLen);
iResponseLen = m_pConnection->Recv(buf, iTextLen);
if (iResponseLen != iTextLen)
{
printf("invaid response received: either not nzbget-server or wrong server version\n");
return false;
}
printf("server returned: %s\n", buf);
free(buf);
return ntohl(BoolResponse.m_bSuccess);
}
/*
* Sends a message to the running nzbget process.
*/
bool RemoteClient::RequestServerDownload(const char* szName, bool bAddFirst)
{
// Read the file into the buffer
char* szBuffer = NULL;
int iLength = 0;
if (!LoadFileIntoBuffer(szName, &szBuffer, &iLength))
{
printf("Could not load file %s\n", szName);
return false;
}
bool OK = InitConnection();
if (OK)
{
SNZBDownloadRequest DownloadRequest;
InitMessageBase(&DownloadRequest.m_MessageBase, eRemoteRequestDownload, sizeof(DownloadRequest));
DownloadRequest.m_bAddFirst = htonl(bAddFirst);
DownloadRequest.m_iTrailingDataLength = htonl(iLength);
strncpy(DownloadRequest.m_szFilename, szName, NZBREQUESTFILENAMESIZE - 1);
DownloadRequest.m_szFilename[NZBREQUESTFILENAMESIZE-1] = '\0';
if (m_pConnection->Send((char*)(&DownloadRequest), sizeof(DownloadRequest)) < 0)
{
perror("m_pConnection->Send");
OK = false;
}
else
{
m_pConnection->Send(szBuffer, iLength);
OK = ReceiveBoolResponse();
m_pConnection->Disconnect();
}
}
// Cleanup
if (szBuffer)
{
free(szBuffer);
}
return OK;
}
bool RemoteClient::RequestServerList()
{
if (!InitConnection()) return false;
SNZBListRequest ListRequest;
InitMessageBase(&ListRequest.m_MessageBase, eRemoteRequestList, sizeof(ListRequest));
ListRequest.m_bFileList = htonl(true);
ListRequest.m_bServerState = htonl(true);
if (m_pConnection->Send((char*)(&ListRequest), sizeof(ListRequest)) < 0)
{
perror("m_pConnection->Send");
return false;
}
// Now listen for the returned list
SNZBListResponse ListResponse;
int iResponseLen = m_pConnection->Recv((char*) &ListResponse, sizeof(ListResponse));
if (iResponseLen != sizeof(ListResponse) ||
(int)ntohl(ListResponse.m_MessageBase.m_iSignature) != (int)NZBMESSAGE_SIGNATURE ||
ntohl(ListResponse.m_MessageBase.m_iStructSize) != sizeof(ListResponse))
{
printf("invaid response received: either not nzbget-server or wrong server version\n");
return false;
}
char* pBuf = NULL;
if (ntohl(ListResponse.m_iTrailingDataLength) > 0)
{
pBuf = (char*)malloc(ntohl(ListResponse.m_iTrailingDataLength));
if (!m_pConnection->RecvAll(pBuf, ntohl(ListResponse.m_iTrailingDataLength)))
{
free(pBuf);
return false;
}
}
m_pConnection->Disconnect();
if (ntohl(ListResponse.m_iTrailingDataLength) == 0)
{
printf("Server has no files queued for download\n");
}
else
{
printf("Queue List\n");
printf("-----------------------------------\n");
long long lRemaining = 0;
long long lPaused = 0;
char* pBufPtr = (char*)pBuf;
for (unsigned int i = 0; i < ntohl(ListResponse.m_iNrTrailingEntries); i++)
{
SNZBListResponseEntry* pListAnswer = (SNZBListResponseEntry*) pBufPtr;
long long lFileSize = JoinInt64(ntohl(pListAnswer->m_iFileSizeHi), ntohl(pListAnswer->m_iFileSizeLo));
long long lRemainingSize = JoinInt64(ntohl(pListAnswer->m_iRemainingSizeHi), ntohl(pListAnswer->m_iRemainingSizeLo));
char szCompleted[100];
szCompleted[0] = '\0';
if (lRemainingSize < lFileSize)
{
sprintf(szCompleted, ", %i%s", (int)(100 - lRemainingSize * 100.0 / lFileSize), "%");
}
char szStatus[100];
if (ntohl(pListAnswer->m_bPaused))
{
sprintf(szStatus, " (paused)");
lPaused += lRemainingSize;
}
else
{
szStatus[0] = '\0';
lRemaining += lRemainingSize;
}
char* szNZBFilename = pBufPtr + sizeof(SNZBListResponseEntry);
char* szFilename = pBufPtr + sizeof(SNZBListResponseEntry) + ntohl(pListAnswer->m_iNZBFilenameLen) + ntohl(pListAnswer->m_iSubjectLen);
char szNZBNiceName[1024];
FileInfo::MakeNiceNZBName(szNZBFilename, szNZBNiceName, 1024);
printf("[%i] %s%c%s (%.2f MB%s)%s\n", ntohl(pListAnswer->m_iID), szNZBNiceName, (int)PATH_SEPARATOR, szFilename, lFileSize / 1024.0 / 1024.0, szCompleted, szStatus);
pBufPtr += sizeof(SNZBListResponseEntry) + ntohl(pListAnswer->m_iNZBFilenameLen) +
ntohl(pListAnswer->m_iSubjectLen) + ntohl(pListAnswer->m_iFilenameLen) + ntohl(pListAnswer->m_iDestDirLen);
}
printf("-----------------------------------\n");
printf("Files: %i\n", ntohl(ListResponse.m_iNrTrailingEntries));
if (lPaused > 0)
{
printf("Remaining size: %.2f MB (+%.2f MB paused)\n", lRemaining / 1024.0 / 1024.0, lPaused / 1024.0 / 1024.0);
}
else
{
printf("Remaining size: %.2f MB\n", lRemaining / 1024.0 / 1024.0);
}
printf("Current download rate: %.1f KB/s\n", (float)(ntohl(ListResponse.m_iDownloadRate) / 1024.0));
free(pBuf);
}
long long iAllBytes = JoinInt64(ntohl(ListResponse.m_iDownloadedBytesHi), ntohl(ListResponse.m_iDownloadedBytesLo));
float fAverageSpeed = ntohl(ListResponse.m_iDownloadTimeSec) > 0 ? iAllBytes / ntohl(ListResponse.m_iDownloadTimeSec) : 0;
printf("Session download rate: %.1f KB/s\n", (float)(fAverageSpeed / 1024.0));
if (ntohl(ListResponse.m_iDownloadLimit) > 0)
{
printf("Speed limit: %.1f KB/s\n", (float)(ntohl(ListResponse.m_iDownloadLimit) / 1024.0));
}
int sec = ntohl(ListResponse.m_iUpTimeSec);
int h = sec / 3600;
int m = (sec % 3600) / 60;
int s = sec % 60;
printf("Up time: %.2d:%.2d:%.2d\n", h, m, s);
sec = ntohl(ListResponse.m_iDownloadTimeSec);
h = sec / 3600;
m = (sec % 3600) / 60;
s = sec % 60;
printf("Download time: %.2d:%.2d:%.2d\n", h, m, s);
printf("Downloaded: %.2f MB\n", iAllBytes / 1024.0 / 1024.0);
printf("Threads running: %i\n", ntohl(ListResponse.m_iThreadCount));
if (ntohl(ListResponse.m_iParJobCount) > 0)
{
printf("Par-jobs: %i\n", (int)ntohl(ListResponse.m_iParJobCount));
}
if (ntohl(ListResponse.m_bServerStandBy))
{
printf("Server state: Stand-By\n");
}
else
{
printf("Server state: %s\n", ntohl(ListResponse.m_bServerPaused) ? "Paused" : "Downloading");
}
return true;
}
bool RemoteClient::RequestServerLog(int iLines)
{
if (!InitConnection()) return false;
SNZBLogRequest LogRequest;
InitMessageBase(&LogRequest.m_MessageBase, eRemoteRequestLog, sizeof(LogRequest));
LogRequest.m_iLines = htonl(iLines);
LogRequest.m_iIDFrom = 0;
if (m_pConnection->Send((char*)(&LogRequest), sizeof(LogRequest)) < 0)
{
perror("m_pConnection->Send");
return false;
}
// Now listen for the returned log
SNZBLogResponse LogResponse;
int iResponseLen = m_pConnection->Recv((char*) &LogResponse, sizeof(LogResponse));
if (iResponseLen != sizeof(LogResponse) ||
(int)ntohl(LogResponse.m_MessageBase.m_iSignature) != (int)NZBMESSAGE_SIGNATURE ||
ntohl(LogResponse.m_MessageBase.m_iStructSize) != sizeof(LogResponse))
{
printf("invaid response received: either not nzbget-server or wrong server version\n");
return false;
}
char* pBuf = NULL;
if (ntohl(LogResponse.m_iTrailingDataLength) > 0)
{
pBuf = (char*)malloc(ntohl(LogResponse.m_iTrailingDataLength));
if (!m_pConnection->RecvAll(pBuf, ntohl(LogResponse.m_iTrailingDataLength)))
{
free(pBuf);
return false;
}
}
m_pConnection->Disconnect();
if (LogResponse.m_iTrailingDataLength == 0)
{
printf("Log is empty\n");
}
else
{
printf("Log (last %i entries)\n", ntohl(LogResponse.m_iNrTrailingEntries));
printf("-----------------------------------\n");
char* pBufPtr = (char*)pBuf;
for (unsigned int i = 0; i < ntohl(LogResponse.m_iNrTrailingEntries); i++)
{
SNZBLogResponseEntry* pLogAnswer = (SNZBLogResponseEntry*) pBufPtr;
char* szText = pBufPtr + sizeof(SNZBLogResponseEntry);
switch (ntohl(pLogAnswer->m_iKind))
{
case Message::mkDebug:
printf("[DEBUG] %s\n", szText);
break;
case Message::mkError:
printf("[ERROR] %s\n", szText);
break;
case Message::mkWarning:
printf("[WARNING] %s\n", szText);
break;
case Message::mkInfo:
printf("[INFO] %s\n", szText);
break;
}
pBufPtr += sizeof(SNZBLogResponseEntry) + ntohl(pLogAnswer->m_iTextLen);
}
printf("-----------------------------------\n");
free(pBuf);
}
return true;
}
bool RemoteClient::RequestServerPauseUnpause(bool bPause)
{
if (!InitConnection()) return false;
SNZBPauseUnpauseRequest PauseUnpauseRequest;
InitMessageBase(&PauseUnpauseRequest.m_MessageBase, eRemoteRequestPauseUnpause, sizeof(PauseUnpauseRequest));
PauseUnpauseRequest.m_bPause = htonl(bPause);
if (m_pConnection->Send((char*)(&PauseUnpauseRequest), sizeof(PauseUnpauseRequest)) < 0)
{
perror("m_pConnection->Send");
m_pConnection->Disconnect();
return false;
}
bool OK = ReceiveBoolResponse();
m_pConnection->Disconnect();
return OK;
}
bool RemoteClient::RequestServerSetDownloadRate(float fRate)
{
if (!InitConnection()) return false;
SNZBSetDownloadRateRequest SetDownloadRateRequest;
InitMessageBase(&SetDownloadRateRequest.m_MessageBase, eRemoteRequestSetDownloadRate, sizeof(SetDownloadRateRequest));
SetDownloadRateRequest.m_iDownloadRate = htonl((unsigned int)(fRate * 1024));
if (m_pConnection->Send((char*)(&SetDownloadRateRequest), sizeof(SetDownloadRateRequest)) < 0)
{
perror("m_pConnection->Send");
m_pConnection->Disconnect();
return false;
}
bool OK = ReceiveBoolResponse();
m_pConnection->Disconnect();
return OK;
}
bool RemoteClient::RequestServerDumpDebug()
{
if (!InitConnection()) return false;
SNZBDumpDebugRequest DumpDebugInfo;
InitMessageBase(&DumpDebugInfo.m_MessageBase, eRemoteRequestDumpDebug, sizeof(DumpDebugInfo));
if (m_pConnection->Send((char*)(&DumpDebugInfo), sizeof(DumpDebugInfo)) < 0)
{
perror("m_pConnection->Send");
m_pConnection->Disconnect();
return false;
}
bool OK = ReceiveBoolResponse();
m_pConnection->Disconnect();
return OK;
}
bool RemoteClient::RequestServerEditQueue(int iAction, int iOffset, int* pIDList, int iIDCount, bool bSmartOrder)
{
if (iIDCount <= 0 || pIDList == NULL)
{
printf("File(s) not specified\n");
return false;
}
if (!InitConnection()) return false;
int iLength = sizeof(int32_t) * iIDCount;
SNZBEditQueueRequest EditQueueRequest;
InitMessageBase(&EditQueueRequest.m_MessageBase, eRemoteRequestEditQueue, sizeof(EditQueueRequest));
EditQueueRequest.m_iAction = htonl(iAction);
EditQueueRequest.m_iOffset = htonl((int)iOffset);
EditQueueRequest.m_bSmartOrder = htonl(bSmartOrder);
EditQueueRequest.m_iNrTrailingEntries = htonl(iIDCount);
EditQueueRequest.m_iTrailingDataLength = htonl(iLength);
int32_t* pIDs = (int32_t*)malloc(iLength);
for (int i = 0; i < iIDCount; i++)
{
pIDs[i] = htonl(pIDList[i]);
}
bool OK = false;
if (m_pConnection->Send((char*)(&EditQueueRequest), sizeof(EditQueueRequest)) < 0)
{
perror("m_pConnection->Send");
}
else
{
m_pConnection->Send((char*)pIDs, iLength);
OK = ReceiveBoolResponse();
m_pConnection->Disconnect();
}
free(pIDs);
m_pConnection->Disconnect();
return OK;
}
bool RemoteClient::RequestServerShutdown()
{
if (!InitConnection()) return false;
SNZBShutdownRequest ShutdownRequest;
InitMessageBase(&ShutdownRequest.m_MessageBase, eRemoteRequestShutdown, sizeof(ShutdownRequest));
bool OK = m_pConnection->Send((char*)(&ShutdownRequest), sizeof(ShutdownRequest)) >= 0;
if (OK)
{
OK = ReceiveBoolResponse();
}
else
{
perror("m_pConnection->Send");
}
m_pConnection->Disconnect();
return OK;
}
bool RemoteClient::RequestServerVersion()
{
if (!InitConnection()) return false;
SNZBVersionRequest VersionRequest;
InitMessageBase(&VersionRequest.m_MessageBase, eRemoteRequestVersion, sizeof(VersionRequest));
bool OK = m_pConnection->Send((char*)(&VersionRequest), sizeof(VersionRequest)) >= 0;
if (OK)
{
OK = ReceiveBoolResponse();
}
else
{
perror("m_pConnection->Send");
}
m_pConnection->Disconnect();
return OK;
}

62
RemoteClient.h Normal file
View File

@@ -0,0 +1,62 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2005 Bo Cordes Petersen <placebodk@users.sourceforge.net>
* Copyright (C) 2007 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$
*
*/
#ifndef REMOTECLIENT_H
#define REMOTECLIENT_H
#include "Options.h"
#include "MessageBase.h"
#include "Connection.h"
class RemoteClient
{
private:
Connection* m_pConnection;
NetAddress* m_pNetAddress;
bool m_bVerbose;
bool InitConnection();
void InitMessageBase(SNZBRequestBase* pMessageBase, int iRequest, int iSize);
bool ReceiveBoolResponse();
void printf(char* msg, ...);
void perror(char* msg);
public:
RemoteClient();
~RemoteClient();
void SetVerbose(bool bVerbose) { m_bVerbose = bVerbose; };
bool RequestServerDownload(const char* szName, bool bAddFirst);
bool RequestServerList();
bool RequestServerPauseUnpause(bool bPause);
bool RequestServerSetDownloadRate(float fRate);
bool RequestServerDumpDebug();
bool RequestServerEditQueue(int iAction, int iOffset, int* pIDList, int iIDCount, bool bSmartOrder);
bool RequestServerLog(int iLines);
bool RequestServerShutdown();
bool RequestServerVersion();
};
#endif

705
RemoteServer.cpp Normal file
View File

@@ -0,0 +1,705 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2005 Bo Cordes Petersen <placebodk@sourceforge.net>
* Copyright (C) 2007 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>
#ifndef WIN32
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#endif
#include "nzbget.h"
#include "RemoteServer.h"
#include "Log.h"
#include "Options.h"
#include "QueueCoordinator.h"
#include "QueueEditor.h"
#include "PrePostProcessor.h"
#include "Util.h"
extern Options* g_pOptions;
extern QueueCoordinator* g_pQueueCoordinator;
extern PrePostProcessor* g_pPrePostProcessor;
extern void ExitProc();
const char* g_szMessageRequestNames[] =
{ "N/A", "Download", "Pause/Unpause", "List",
"Set download rate", "Dump debug", "Edit queue", "Log", "Quit"
};
const unsigned int g_iMessageRequestSizes[] =
{ 0,
sizeof(SNZBDownloadRequest),
sizeof(SNZBPauseUnpauseRequest),
sizeof(SNZBListRequest),
sizeof(SNZBSetDownloadRateRequest),
sizeof(SNZBDumpDebugRequest),
sizeof(SNZBEditQueueRequest),
sizeof(SNZBLogRequest),
sizeof(SNZBRequestBase)
};
//*****************************************************************
// RemoteServer
RemoteServer::RemoteServer()
{
debug("Creating RemoteServer");
m_pNetAddress = new NetAddress(g_pOptions->GetServerIP(), g_pOptions->GetServerPort());
m_pConnection = NULL;
}
RemoteServer::~RemoteServer()
{
debug("Destroying RemoteServer");
if (m_pConnection)
{
delete m_pConnection;
}
delete m_pNetAddress;
}
void RemoteServer::Run()
{
debug("Entering RemoteServer-loop");
while (!IsStopped())
{
bool bBind = true;
if (!m_pConnection)
{
m_pConnection = new Connection(m_pNetAddress);
m_pConnection->SetTimeout(g_pOptions->GetConnectionTimeout());
m_pConnection->SetSuppressErrors(false);
bBind = m_pConnection->Bind() == 0;
}
// Accept connections and store the "new" socket value
SOCKET iSocket = INVALID_SOCKET;
if (bBind)
{
iSocket = m_pConnection->Accept();
}
if (!bBind || iSocket == INVALID_SOCKET)
{
// Remote server could not bind or accept connection, waiting 1/2 sec and try again
if (IsStopped())
{
break;
}
usleep(500 * 1000);
delete m_pConnection;
m_pConnection = NULL;
continue;
}
RequestProcessor* commandThread = new RequestProcessor();
commandThread->SetAutoDestroy(true);
commandThread->SetSocket(iSocket);
commandThread->Start();
}
if (m_pConnection)
{
m_pConnection->Disconnect();
}
debug("Exiting RemoteServer-loop");
}
void RemoteServer::Stop()
{
Thread::Stop();
if (m_pConnection)
{
m_pConnection->SetSuppressErrors(true);
m_pConnection->Cancel();
#ifdef WIN32
m_pConnection->Disconnect();
#endif
}
}
//*****************************************************************
// RequestProcessor
void RequestProcessor::Run()
{
int iBytesReceived = 0;
// Read the first package which needs to be a request
iBytesReceived = recv(m_iSocket, (char*) & m_MessageBase, sizeof(m_MessageBase), 0);
if (iBytesReceived < 0)
{
return;
}
// Make sure this is a nzbget request from a client
if ((int)ntohl(m_MessageBase.m_iSignature) != (int)NZBMESSAGE_SIGNATURE)
{
warn("Non-nzbget request received on port %i", g_pOptions->GetServerPort());
if (m_iSocket > -1)
{
closesocket(m_iSocket);
}
return;
}
if (strcmp(m_MessageBase.m_szPassword, g_pOptions->GetServerPassword()))
{
warn("nzbget request received on port %i, but password invalid", g_pOptions->GetServerPort());
if (m_iSocket > -1)
{
closesocket(m_iSocket);
}
return;
}
// Info - connection received
struct sockaddr_in PeerName;
int iPeerNameLength = sizeof(PeerName);
if (getpeername(m_iSocket, (struct sockaddr*)&PeerName, (socklen_t*) &iPeerNameLength) >= 0)
{
#ifdef WIN32
char* ip = inet_ntoa(PeerName.sin_addr);
#else
char ip[20];
inet_ntop(AF_INET, &PeerName.sin_addr, ip, sizeof(ip));
#endif
debug("%s request received from %s", g_szMessageRequestNames[ntohl(m_MessageBase.m_iType)], ip);
}
Dispatch();
// Close the socket
closesocket(m_iSocket);
}
void RequestProcessor::Dispatch()
{
if (ntohl(m_MessageBase.m_iType) >= (int)eRemoteRequestDownload &&
ntohl(m_MessageBase.m_iType) <= (int)eRemoteRequestShutdown &&
g_iMessageRequestSizes[ntohl(m_MessageBase.m_iType)] != ntohl(m_MessageBase.m_iStructSize))
{
error("Invalid size of request: needed %i Bytes, but received %i Bytes",
g_iMessageRequestSizes[ntohl(m_MessageBase.m_iType)], ntohl(m_MessageBase.m_iStructSize));
return;
}
MessageCommand* command = NULL;
switch (ntohl(m_MessageBase.m_iType))
{
case eRemoteRequestDownload:
{
command = new DownloadCommand();
break;
}
case eRemoteRequestList:
{
command = new ListCommand();
break;
}
case eRemoteRequestLog:
{
command = new LogCommand();
break;
}
case eRemoteRequestPauseUnpause:
{
command = new PauseUnpauseCommand();
break;
}
case eRemoteRequestEditQueue:
{
command = new EditQueueCommand();
break;
}
case eRemoteRequestSetDownloadRate:
{
command = new SetDownloadRateCommand();
break;
}
case eRemoteRequestDumpDebug:
{
command = new DumpDebugCommand();
break;
}
case eRemoteRequestShutdown:
{
command = new ShutdownCommand();
break;
}
case eRemoteRequestVersion:
{
command = new VersionCommand();
break;
}
default:
error("Received unsupported request %i", ntohl(m_MessageBase.m_iType));
break;
}
if (command)
{
command->SetSocket(m_iSocket);
command->SetMessageBase(&m_MessageBase);
command->Execute();
delete command;
}
}
//*****************************************************************
// Commands
void MessageCommand::SendBoolResponse(bool bSuccess, const char* szText)
{
// all bool-responses have the same format of structure, we use SNZBDownloadResponse here
SNZBDownloadResponse BoolResponse;
memset(&BoolResponse, 0, sizeof(BoolResponse));
BoolResponse.m_MessageBase.m_iSignature = htonl(NZBMESSAGE_SIGNATURE);
BoolResponse.m_MessageBase.m_iStructSize = htonl(sizeof(BoolResponse));
BoolResponse.m_bSuccess = htonl(bSuccess);
int iTextLen = strlen(szText) + 1;
BoolResponse.m_iTrailingDataLength = htonl(iTextLen);
// Send the request answer
send(m_iSocket, (char*) &BoolResponse, sizeof(BoolResponse), 0);
send(m_iSocket, (char*)szText, iTextLen, 0);
}
bool MessageCommand::ReceiveRequest(void* pBuffer, int iSize)
{
memcpy(pBuffer, m_pMessageBase, sizeof(SNZBRequestBase));
iSize -= sizeof(SNZBRequestBase);
if (iSize > 0)
{
int iBytesReceived = recv(m_iSocket, ((char*)pBuffer) + sizeof(SNZBRequestBase), iSize, 0);
if (iBytesReceived != iSize)
{
error("invalid request");
return false;
}
}
return true;
}
void PauseUnpauseCommand::Execute()
{
SNZBPauseUnpauseRequest PauseUnpauseRequest;
if (!ReceiveRequest(&PauseUnpauseRequest, sizeof(PauseUnpauseRequest)))
{
return;
}
g_pOptions->SetPause(ntohl(PauseUnpauseRequest.m_bPause));
SendBoolResponse(true, "Pause-/Unpause-Command completed successfully");
}
void SetDownloadRateCommand::Execute()
{
SNZBSetDownloadRateRequest SetDownloadRequest;
if (!ReceiveRequest(&SetDownloadRequest, sizeof(SetDownloadRequest)))
{
return;
}
g_pOptions->SetDownloadRate(ntohl(SetDownloadRequest.m_iDownloadRate) / 1024.0);
SendBoolResponse(true, "Rate-Command completed successfully");
}
void DumpDebugCommand::Execute()
{
SNZBDumpDebugRequest DumpDebugRequest;
if (!ReceiveRequest(&DumpDebugRequest, sizeof(DumpDebugRequest)))
{
return;
}
g_pQueueCoordinator->LogDebugInfo();
SendBoolResponse(true, "Debug-Command completed successfully");
}
void ShutdownCommand::Execute()
{
SNZBShutdownRequest ShutdownRequest;
if (!ReceiveRequest(&ShutdownRequest, sizeof(ShutdownRequest)))
{
return;
}
SendBoolResponse(true, "Stopping server");
ExitProc();
}
void VersionCommand::Execute()
{
SNZBVersionRequest VersionRequest;
if (!ReceiveRequest(&VersionRequest, sizeof(VersionRequest)))
{
return;
}
SendBoolResponse(true, VERSION);
}
void DownloadCommand::Execute()
{
SNZBDownloadRequest DownloadRequest;
if (!ReceiveRequest(&DownloadRequest, sizeof(DownloadRequest)))
{
return;
}
char* pRecvBuffer = (char*)malloc(ntohl(DownloadRequest.m_iTrailingDataLength) + 1);
char* pBufPtr = pRecvBuffer;
// Read from the socket until nothing remains
int iResult = 0;
int NeedBytes = ntohl(DownloadRequest.m_iTrailingDataLength);
while (NeedBytes > 0)
{
iResult = recv(m_iSocket, pBufPtr, NeedBytes, 0);
// Did the recv succeed?
if (iResult <= 0)
{
error("invalid request");
break;
}
pBufPtr += iResult;
NeedBytes -= iResult;
}
if (NeedBytes == 0)
{
NZBFile* pNZBFile = NZBFile::CreateFromBuffer(DownloadRequest.m_szFilename, pRecvBuffer, ntohl(DownloadRequest.m_iTrailingDataLength));
if (pNZBFile)
{
info("Request: Queue collection %s", DownloadRequest.m_szFilename);
g_pQueueCoordinator->AddNZBFileToQueue(pNZBFile, ntohl(DownloadRequest.m_bAddFirst));
delete pNZBFile;
char tmp[1024];
snprintf(tmp, 1024, "Collection %s added to queue", BaseFileName(DownloadRequest.m_szFilename));
tmp[1024-1] = '\0';
SendBoolResponse(true, tmp);
}
else
{
char tmp[1024];
snprintf(tmp, 1024, "Download Request failed for %s", BaseFileName(DownloadRequest.m_szFilename));
tmp[1024-1] = '\0';
SendBoolResponse(false, tmp);
}
}
free(pRecvBuffer);
}
void ListCommand::Execute()
{
SNZBListRequest ListRequest;
if (!ReceiveRequest(&ListRequest, sizeof(ListRequest)))
{
return;
}
SNZBListResponse ListResponse;
memset(&ListResponse, 0, sizeof(ListResponse));
ListResponse.m_MessageBase.m_iSignature = htonl(NZBMESSAGE_SIGNATURE);
ListResponse.m_MessageBase.m_iStructSize = htonl(sizeof(ListResponse));
ListResponse.m_iEntrySize = htonl(sizeof(SNZBListResponseEntry));
char* buf = NULL;
int bufsize = 0;
if (ntohl(ListRequest.m_bFileList))
{
// Make a data structure and copy all the elements of the list into it
DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue();
int NrEntries = pDownloadQueue->size();
// calculate required buffer size
bufsize = NrEntries * sizeof(SNZBListResponseEntry);
for (DownloadQueue::iterator it = pDownloadQueue->begin(); it != pDownloadQueue->end(); it++)
{
FileInfo* pFileInfo = *it;
bufsize += strlen(pFileInfo->GetNZBFilename()) + 1;
bufsize += strlen(pFileInfo->GetSubject()) + 1;
bufsize += strlen(pFileInfo->GetFilename()) + 1;
bufsize += strlen(pFileInfo->GetDestDir()) + 1;
}
buf = (char*) malloc(bufsize);
char* bufptr = buf;
for (DownloadQueue::iterator it = pDownloadQueue->begin(); it != pDownloadQueue->end(); it++)
{
unsigned int iSizeHi, iSizeLo;
FileInfo* pFileInfo = *it;
SNZBListResponseEntry* pListAnswer = (SNZBListResponseEntry*) bufptr;
pListAnswer->m_iID = htonl(pFileInfo->GetID());
SplitInt64(pFileInfo->GetSize(), &iSizeHi, &iSizeLo);
pListAnswer->m_iFileSizeLo = htonl(iSizeLo);
pListAnswer->m_iFileSizeHi = htonl(iSizeHi);
SplitInt64(pFileInfo->GetRemainingSize(), &iSizeHi, &iSizeLo);
pListAnswer->m_iRemainingSizeLo = htonl(iSizeLo);
pListAnswer->m_iRemainingSizeHi = htonl(iSizeHi);
pListAnswer->m_bFilenameConfirmed = htonl(pFileInfo->GetFilenameConfirmed());
pListAnswer->m_bPaused = htonl(pFileInfo->GetPaused());
pListAnswer->m_iNZBFilenameLen = htonl(strlen(pFileInfo->GetNZBFilename()) + 1);
pListAnswer->m_iSubjectLen = htonl(strlen(pFileInfo->GetSubject()) + 1);
pListAnswer->m_iFilenameLen = htonl(strlen(pFileInfo->GetFilename()) + 1);
pListAnswer->m_iDestDirLen = htonl(strlen(pFileInfo->GetDestDir()) + 1);
bufptr += sizeof(SNZBListResponseEntry);
strcpy(bufptr, pFileInfo->GetNZBFilename());
bufptr += ntohl(pListAnswer->m_iNZBFilenameLen);
strcpy(bufptr, pFileInfo->GetSubject());
bufptr += ntohl(pListAnswer->m_iSubjectLen);
strcpy(bufptr, pFileInfo->GetFilename());
bufptr += ntohl(pListAnswer->m_iFilenameLen);
strcpy(bufptr, pFileInfo->GetDestDir());
bufptr += ntohl(pListAnswer->m_iDestDirLen);
}
g_pQueueCoordinator->UnlockQueue();
ListResponse.m_iNrTrailingEntries = htonl(NrEntries);
ListResponse.m_iTrailingDataLength = htonl(bufsize);
}
if (htonl(ListRequest.m_bServerState))
{
unsigned int iSizeHi, iSizeLo;
ListResponse.m_iDownloadRate = htonl((int)(g_pQueueCoordinator->CalcCurrentDownloadSpeed() * 1024));
SplitInt64(g_pQueueCoordinator->CalcRemainingSize(), &iSizeHi, &iSizeLo);
ListResponse.m_iRemainingSizeHi = htonl(iSizeHi);
ListResponse.m_iRemainingSizeLo = htonl(iSizeLo);
ListResponse.m_iDownloadLimit = htonl((int)(g_pOptions->GetDownloadRate() * 1024));
ListResponse.m_bServerPaused = htonl(g_pOptions->GetPause());
ListResponse.m_iThreadCount = htonl(Thread::GetThreadCount() - 1); // not counting itself
PrePostProcessor::ParQueue* pParQueue = g_pPrePostProcessor->LockParQueue();
ListResponse.m_iParJobCount = htonl(pParQueue->size());
g_pPrePostProcessor->UnlockParQueue();
int iUpTimeSec, iDnTimeSec;
long long iAllBytes;
bool bStandBy;
g_pQueueCoordinator->CalcStat(&iUpTimeSec, &iDnTimeSec, &iAllBytes, &bStandBy);
ListResponse.m_iUpTimeSec = htonl(iUpTimeSec);
ListResponse.m_iDownloadTimeSec = htonl(iDnTimeSec);
ListResponse.m_bServerStandBy = htonl(bStandBy);
SplitInt64(iAllBytes, &iSizeHi, &iSizeLo);
ListResponse.m_iDownloadedBytesHi = htonl(iSizeHi);
ListResponse.m_iDownloadedBytesLo = htonl(iSizeLo);
}
// Send the request answer
send(m_iSocket, (char*) &ListResponse, sizeof(ListResponse), 0);
// Send the data
if (bufsize > 0)
{
send(m_iSocket, buf, bufsize, 0);
}
if (buf)
{
free(buf);
}
}
void LogCommand::Execute()
{
SNZBLogRequest LogRequest;
if (!ReceiveRequest(&LogRequest, sizeof(LogRequest)))
{
return;
}
Log::Messages* pMessages = g_pLog->LockMessages();
int iNrEntries = ntohl(LogRequest.m_iLines);
unsigned int iIDFrom = ntohl(LogRequest.m_iIDFrom);
int iStart = pMessages->size();
if (iNrEntries > 0)
{
if (iNrEntries > (int)pMessages->size())
{
iNrEntries = pMessages->size();
}
iStart = pMessages->size() - iNrEntries;
}
if (iIDFrom > 0 && !pMessages->empty())
{
iStart = iIDFrom - pMessages->front()->GetID();
if (iStart < 0)
{
iStart = 0;
}
iNrEntries = pMessages->size() - iStart;
if (iNrEntries < 0)
{
iNrEntries = 0;
}
}
// calculate required buffer size
int bufsize = iNrEntries * sizeof(SNZBLogResponseEntry);
for (unsigned int i = (unsigned int)iStart; i < pMessages->size(); i++)
{
Message* pMessage = (*pMessages)[i];
bufsize += strlen(pMessage->GetText()) + 1;
}
char* buf = (char*) malloc(bufsize);
char* bufptr = buf;
for (unsigned int i = (unsigned int)iStart; i < pMessages->size(); i++)
{
Message* pMessage = (*pMessages)[i];
SNZBLogResponseEntry* pLogAnswer = (SNZBLogResponseEntry*) bufptr;
pLogAnswer->m_iID = htonl(pMessage->GetID());
pLogAnswer->m_iKind = htonl(pMessage->GetKind());
pLogAnswer->m_tTime = htonl(pMessage->GetTime());
pLogAnswer->m_iTextLen = htonl(strlen(pMessage->GetText()) + 1);
bufptr += sizeof(SNZBLogResponseEntry);
strcpy(bufptr, pMessage->GetText());
bufptr += ntohl(pLogAnswer->m_iTextLen);
}
g_pLog->UnlockMessages();
SNZBLogResponse LogResponse;
LogResponse.m_MessageBase.m_iSignature = htonl(NZBMESSAGE_SIGNATURE);
LogResponse.m_MessageBase.m_iStructSize = htonl(sizeof(LogResponse));
LogResponse.m_iEntrySize = htonl(sizeof(SNZBLogResponseEntry));
LogResponse.m_iNrTrailingEntries = htonl(iNrEntries);
LogResponse.m_iTrailingDataLength = htonl(bufsize);
// Send the request answer
send(m_iSocket, (char*) &LogResponse, sizeof(LogResponse), 0);
// Send the data
if (bufsize > 0)
{
send(m_iSocket, buf, bufsize, 0);
}
free(buf);
}
void EditQueueCommand::Execute()
{
SNZBEditQueueRequest EditQueueRequest;
if (!ReceiveRequest(&EditQueueRequest, sizeof(EditQueueRequest)))
{
return;
}
int iNrEntries = ntohl(EditQueueRequest.m_iNrTrailingEntries);
int iAction = ntohl(EditQueueRequest.m_iAction);
int iOffset = ntohl(EditQueueRequest.m_iOffset);
bool bSmartOrder = ntohl(EditQueueRequest.m_bSmartOrder);
unsigned int iBufLength = ntohl(EditQueueRequest.m_iTrailingDataLength);
if (iNrEntries * sizeof(int32_t) != iBufLength)
{
error("Invalid struct size");
return;
}
if (iNrEntries <= 0)
{
SendBoolResponse(false, "Edit-Command failed: no IDs specified");
return;
}
int32_t* pIDs = (int32_t*)malloc(iBufLength);
// Read from the socket until nothing remains
char* pBufPtr = (char*)pIDs;
int NeedBytes = iBufLength;
int iResult = 0;
while (NeedBytes > 0)
{
iResult = recv(m_iSocket, pBufPtr, NeedBytes, 0);
// Did the recv succeed?
if (iResult <= 0)
{
error("invalid request");
break;
}
pBufPtr += iResult;
NeedBytes -= iResult;
}
QueueEditor::IDList cIDList;
cIDList.reserve(iNrEntries);
for (int i = 0; i < iNrEntries; i++)
{
cIDList.push_back(ntohl(pIDs[i]));
}
bool bOK = g_pQueueCoordinator->GetQueueEditor()->EditList(&cIDList, bSmartOrder, (QueueEditor::EEditAction)iAction, iOffset);
free(pIDs);
if (bOK)
{
SendBoolResponse(true, "Edit-Command completed successfully");
}
else
{
SendBoolResponse(false, "Edit-Command failed");
}
}

133
RemoteServer.h Normal file
View File

@@ -0,0 +1,133 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2005 Bo Cordes Petersen <placebodk@users.sourceforge.net>
* Copyright (C) 2007 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$
*
*/
#ifndef REMOTESERVER_H
#define REMOTESERVER_H
#include <list>
#include "Thread.h"
#include "NetAddress.h"
#include "Connection.h"
#include "MessageBase.h"
class RemoteServer : public Thread
{
private:
NetAddress* m_pNetAddress;
Connection* m_pConnection;
public:
RemoteServer();
~RemoteServer();
virtual void Run();
virtual void Stop();
};
class RequestProcessor : public Thread
{
private:
SOCKET m_iSocket;
SNZBRequestBase m_MessageBase;
void Dispatch();
public:
virtual void Run();
void SetSocket(SOCKET iSocket) { m_iSocket = iSocket; };
};
class MessageCommand
{
protected:
SOCKET m_iSocket;
SNZBRequestBase* m_pMessageBase;
bool ReceiveRequest(void* pBuffer, int iSize);
void SendBoolResponse(bool bSuccess, const char* szText);
public:
virtual ~MessageCommand() {};
virtual void Execute() = 0;
void SetSocket(SOCKET iSocket) { m_iSocket = iSocket; };
void SetMessageBase(SNZBRequestBase* pMessageBase) { m_pMessageBase = pMessageBase; };
};
class DownloadCommand: public MessageCommand
{
public:
virtual void Execute();
};
class ListCommand: public MessageCommand
{
public:
virtual void Execute();
};
class LogCommand: public MessageCommand
{
public:
virtual void Execute();
};
class PauseUnpauseCommand: public MessageCommand
{
public:
virtual void Execute();
};
class EditQueueCommand: public MessageCommand
{
public:
virtual void Execute();
};
class SetDownloadRateCommand: public MessageCommand
{
public:
virtual void Execute();
};
class DumpDebugCommand: public MessageCommand
{
public:
virtual void Execute();
};
class ShutdownCommand: public MessageCommand
{
public:
virtual void Execute();
};
class VersionCommand: public MessageCommand
{
public:
virtual void Execute();
};
#endif

251
ServerPool.cpp Normal file
View File

@@ -0,0 +1,251 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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$
*
*/
/*
*
* m_Semaphore Patch by Florian Penzkofer <f.penzkofer@sent.com>
* The queue of mutexes that was used did not work for every
* implementation of POSIX threads. Now a m_Semaphore is used.
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#ifndef WIN32
#include <unistd.h>
#include <errno.h>
#endif
#include "nzbget.h"
#include "ServerPool.h"
#include "Log.h"
static const int CONNECTION_HOLD_SECODNS = 5;
ServerPool::PooledConnection::PooledConnection(NewsServer* server) : NNTPConnection(server)
{
m_bInUse = false;
m_tFreeTime = 0;
}
ServerPool::ServerPool()
{
debug("Creating ServerPool");
m_iMaxLevel = 0;
m_iTimeout = 60;
m_Servers.clear();
m_Connections.clear();
m_Semaphores.clear();
}
ServerPool::~ ServerPool()
{
debug("Destroying ServerPool");
for (Semaphores::iterator it = m_Semaphores.begin(); it != m_Semaphores.end(); it++)
{
delete *it;
}
m_Semaphores.clear();
for (Servers::iterator it = m_Servers.begin(); it != m_Servers.end(); it++)
{
delete *it;
}
m_Servers.clear();
for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++)
{
delete *it;
}
m_Connections.clear();
}
void ServerPool::AddServer(NewsServer* pNewsServer)
{
debug("Adding server to ServerPool");
m_Servers.push_back(pNewsServer);
}
void ServerPool::InitConnections()
{
debug("Initializing connections in ServerPool");
m_iMaxLevel = 0;
for (Servers::iterator it = m_Servers.begin(); it != m_Servers.end(); it++)
{
NewsServer* pNewsServer = *it;
if (m_iMaxLevel < pNewsServer->GetLevel())
{
m_iMaxLevel = pNewsServer->GetLevel();
}
for (int i = 0; i < pNewsServer->GetMaxConnections(); i++)
{
PooledConnection* pConnection = new PooledConnection(pNewsServer);
pConnection->SetTimeout(m_iTimeout);
m_Connections.push_back(pConnection);
}
}
for (int iLevel = 0; iLevel <= m_iMaxLevel; iLevel++)
{
int iMaxConnectionsForLevel = 0;
for (Servers::iterator it = m_Servers.begin(); it != m_Servers.end(); it++)
{
NewsServer* pNewsServer = *it;
if (iLevel == pNewsServer->GetLevel())
{
iMaxConnectionsForLevel += pNewsServer->GetMaxConnections();
}
}
Semaphore* sem = new Semaphore(iMaxConnectionsForLevel);
m_Semaphores.push_back(sem);
}
}
NNTPConnection* ServerPool::GetConnection(int iLevel, bool bWait)
{
bool bWaitVal = false;
if (bWait)
{
debug("Getting connection (wait)");
bWaitVal = m_Semaphores[iLevel]->Wait();
}
else
{
bWaitVal = m_Semaphores[iLevel]->TryWait();
}
if (!bWaitVal)
{
// signal received or wait timeout
return NULL;
}
m_mutexConnections.Lock();
PooledConnection* pConnection = NULL;
for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++)
{
PooledConnection* pConnection1 = *it;
if (!pConnection1->GetInUse() && pConnection1->GetNewsServer()->GetLevel() == iLevel)
{
// free connection found, take it!
pConnection = pConnection1;
pConnection->SetInUse(true);
break;
}
}
m_mutexConnections.Unlock();
if (!pConnection)
{
error("ServerPool: serious error, no free connection found, but there should be one.");
}
return pConnection;
}
void ServerPool::FreeConnection(NNTPConnection* pConnection, bool bUsed)
{
if (bUsed)
{
debug("Freeing used connection");
}
m_mutexConnections.Lock();
((PooledConnection*)pConnection)->SetInUse(false);
if (bUsed)
{
((PooledConnection*)pConnection)->SetFreeTimeNow();
}
m_Semaphores[pConnection->GetNewsServer()->GetLevel()]->Post();
m_mutexConnections.Unlock();
}
void ServerPool::CloseUnusedConnections()
{
m_mutexConnections.Lock();
time_t curtime = ::time(NULL);
for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++)
{
PooledConnection* pConnection = *it;
if (!pConnection->GetInUse() && pConnection->GetStatus() == Connection::csConnected)
{
int tdiff = curtime - pConnection->GetFreeTime();
if (tdiff > CONNECTION_HOLD_SECODNS)
{
debug("Closing unused connection to %s", pConnection->GetNewsServer()->GetHost());
pConnection->Disconnect();
}
}
}
m_mutexConnections.Unlock();
}
void ServerPool::LogDebugInfo()
{
debug(" ServerPool");
debug(" ----------------");
debug(" Max-Level: %i", m_iMaxLevel);
m_mutexConnections.Lock();
debug(" Connections: %i", m_Connections.size());
for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++)
{
debug(" Connection: Level=%i, InUse:%i", (*it)->GetNewsServer()->GetLevel(), (int)(*it)->GetInUse());
}
/*
debug(" Semaphores: %i", m_Semaphores.size());
for (int iLevel = 0; iLevel <= m_iMaxLevel; iLevel++)
{
sem_t* sem = m_Semaphores[iLevel];
int iSemValue;
sem_getvalue(sem, &iSemValue);
debug(" Semaphore: level=%i, value=%i", iLevel, iSemValue);
}
*/
m_mutexConnections.Unlock();
}

79
ServerPool.h Normal file
View File

@@ -0,0 +1,79 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2005 Florian Penzkofer <f.penzkofer@sent.com>
* Copyright (C) 2007 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$
*
*/
#ifndef SERVERPOOL_H
#define SERVERPOOL_H
#include <vector>
#include <time.h>
#include "Thread.h"
#include "NewsServer.h"
#include "NNTPConnection.h"
class ServerPool
{
private:
class PooledConnection : public NNTPConnection
{
private:
bool m_bInUse;
time_t m_tFreeTime;
public:
PooledConnection(NewsServer* server);
bool GetInUse() { return m_bInUse; }
void SetInUse(bool bInUse) { m_bInUse = bInUse; }
time_t GetFreeTime() { return m_tFreeTime; }
void SetFreeTimeNow() { m_tFreeTime = ::time(NULL); }
};
typedef std::vector<NewsServer*> Servers;
typedef std::vector<Semaphore*> Semaphores;
typedef std::vector<PooledConnection*> Connections;
Servers m_Servers;
Connections m_Connections;
Semaphores m_Semaphores;
int m_iMaxLevel;
Mutex m_mutexConnections;
int m_iTimeout;
public:
ServerPool();
~ServerPool();
void SetTimeout(int iTimeout) { m_iTimeout = iTimeout; }
void AddServer(NewsServer *s);
void InitConnections();
int GetMaxLevel() { return m_iMaxLevel; }
NNTPConnection* GetConnection(int iLevel, bool bWait);
void FreeConnection(NNTPConnection* pConnection, bool bUsed);
void CloseUnusedConnections();
void LogDebugInfo();
};
#endif

302
Thread.cpp Normal file
View File

@@ -0,0 +1,302 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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 <stdio.h>
#ifdef WIN32
#include <process.h>
#endif
#include "Log.h"
#include "Thread.h"
int Thread::m_iThreadCount = 1; // take the main program thread into account
Mutex Thread::m_mutexThread;
Mutex::Mutex()
{
#ifdef WIN32
InitializeCriticalSection(&m_mutexObj);
#else
pthread_mutex_init(&m_mutexObj, NULL);
#endif
}
Mutex::~ Mutex()
{
#ifdef WIN32
DeleteCriticalSection(&m_mutexObj);
#else
pthread_mutex_destroy(&m_mutexObj);
#endif
}
void Mutex::Lock()
{
#ifdef WIN32
EnterCriticalSection(&m_mutexObj);
#ifdef DEBUG
// CriticalSections on Windows can be locked many times from the same thread,
// but we do not want this and must treat such situations as errors and detect them.
if (m_mutexObj.RecursionCount > 1)
{
error("Internal program error: inconsistent thread-lock detected");
}
#endif
#else
pthread_mutex_lock(&m_mutexObj);
#endif
}
void Mutex::Unlock()
{
#ifdef WIN32
LeaveCriticalSection(&m_mutexObj);
#else
pthread_mutex_unlock(&m_mutexObj);
#endif
}
Semaphore::Semaphore()
{
#ifdef WIN32
m_semObj = CreateSemaphore(NULL, 0, 1, NULL);
#else
sem_init(&m_semObj, 0, 0);
#endif
}
Semaphore::Semaphore(int iValue)
{
#ifdef WIN32
m_semObj = CreateSemaphore(NULL, iValue, iValue, NULL);
#else
sem_init(&m_semObj, 0, iValue);
#endif
}
Semaphore::~ Semaphore()
{
#ifdef WIN32
CloseHandle(m_semObj);
#else
sem_destroy(&m_semObj);
#endif
}
void Semaphore::Post()
{
#ifdef WIN32
ReleaseSemaphore(m_semObj, 1, NULL);
#else
sem_post(&m_semObj);
#endif
}
bool Semaphore::Wait()
{
#ifdef WIN32
return WaitForSingleObject(m_semObj, INFINITE) == WAIT_OBJECT_0;
#else
return sem_wait(&m_semObj) == 0;
#endif
}
bool Semaphore::TryWait()
{
#ifdef WIN32
return WaitForSingleObject(m_semObj, 0) == WAIT_OBJECT_0;
#else
return sem_trywait(&m_semObj) == 0;
#endif
}
bool Semaphore::TimedWait(int iMSec)
{
#ifdef WIN32
return WaitForSingleObject(m_semObj, iMSec) == WAIT_OBJECT_0;
#else
struct timespec alarm;
alarm.tv_sec = ::time(NULL) + iMSec / 1000;
alarm.tv_nsec = (iMSec % 1000) * 1000;
return sem_timedwait(&m_semObj, &alarm) == 0;
#endif
}
bool Semaphore::IsLocked()
{
#ifdef WIN32
bool bCanLock = WaitForSingleObject(m_semObj, 0) == WAIT_OBJECT_0;
if (bCanLock)
{
ReleaseSemaphore(m_semObj, 1, NULL);
}
return !bCanLock;
#else
int iSemValue;
sem_getvalue(&m_semObj, &iSemValue);
return iSemValue <= 0;
#endif
}
void Thread::Init()
{
debug("Initializing global thread data");
}
void Thread::Final()
{
debug("Finalizing global thread data");
}
Thread::Thread()
{
debug("Creating Thread");
m_Thread = 0;
m_bRunning = false;
m_bStopped = false;
m_bAutoDestroy = false;
}
Thread::~Thread()
{
debug("Destroying Thread");
}
void Thread::Start()
{
debug("Starting Thread");
m_bRunning = true;
// NOTE: we must garantee, that in a time we setting m_bRunning
// to value returned from pthread_create, the thread-object still exists.
// This is not obviously!
// pthread_create could wait long enough before returning result
// back to allow the started thread to complete it job
// and terminate.
// We lock mutex m_mutexThread on calling pthread_create; the started thread
// then also try to lock the mutex (see thread_handler) and therefore
// must wait until we unlock it
m_mutexThread.Lock();
#ifdef WIN32
m_Thread = (HANDLE)_beginthread(Thread::thread_handler, 0, (void *)this);
m_bRunning = m_Thread != NULL;
#else
pthread_attr_t m_Attr;
pthread_attr_init(&m_Attr);
pthread_attr_setdetachstate(&m_Attr, PTHREAD_CREATE_DETACHED);
pthread_attr_setinheritsched(&m_Attr , PTHREAD_INHERIT_SCHED);
m_bRunning = !pthread_create(&m_Thread, &m_Attr, Thread::thread_handler, (void *) this);
pthread_attr_destroy(&m_Attr);
#endif
m_mutexThread.Unlock();
}
void Thread::Stop()
{
debug("Stopping Thread");
m_bStopped = true;
}
bool Thread::Kill()
{
debug("Killing Thread");
m_mutexThread.Lock();
#ifdef WIN32
bool terminated = TerminateThread(m_Thread, 0) != 0;
#else
bool terminated = pthread_cancel(m_Thread) == 0;
#endif
if (terminated)
{
m_iThreadCount--;
}
m_mutexThread.Unlock();
return terminated;
}
#ifdef WIN32
void __cdecl Thread::thread_handler(void* pObject)
#else
void* Thread::thread_handler(void* pObject)
#endif
{
m_mutexThread.Lock();
m_iThreadCount++;
m_mutexThread.Unlock();
debug("Entering Thread-func");
Thread* pThread = (Thread*)pObject;
pThread->Run();
debug("Thread-func exited");
pThread->m_bRunning = false;
if (pThread->m_bAutoDestroy)
{
debug("Autodestroying Thread-object");
delete pThread;
}
m_mutexThread.Lock();
m_iThreadCount--;
m_mutexThread.Unlock();
#ifndef WIN32
return NULL;
#endif
}
int Thread::GetThreadCount()
{
m_mutexThread.Lock();
int iThreadCount = m_iThreadCount;
m_mutexThread.Unlock();
return iThreadCount;
}

115
Thread.h Normal file
View File

@@ -0,0 +1,115 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 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$
*
*/
#ifndef THREAD_H
#define THREAD_H
#ifndef WIN32
#include <pthread.h>
#include <semaphore.h>
#endif
class Mutex
{
private:
#ifdef WIN32
CRITICAL_SECTION m_mutexObj;
#else
pthread_mutex_t m_mutexObj;
#endif
public:
Mutex();
~Mutex();
void Lock();
void Unlock();
};
class Semaphore
{
private:
#ifdef WIN32
HANDLE m_semObj;
#else
sem_t m_semObj;
#endif
public:
Semaphore();
Semaphore(int iValue);
~Semaphore();
void Post();
bool Wait();
bool TryWait();
bool TimedWait(int iMSec);
bool IsLocked();
};
class Thread
{
private:
static Mutex m_mutexThread;
static int m_iThreadCount;
#ifdef WIN32
HANDLE m_Thread;
#else
pthread_t m_Thread;
#endif
bool m_bRunning;
bool m_bStopped;
bool m_bAutoDestroy;
#ifdef WIN32
static void __cdecl thread_handler(void* pObject);
#else
static void *thread_handler(void* pObject);
#endif
public:
Thread();
virtual ~Thread();
virtual void Start();
virtual void Stop();
bool Kill();
bool IsStopped() { return m_bStopped; };
bool IsRunning() const { return m_bRunning; }
void SetRunning(bool bOnOff) { m_bRunning = bOnOff; }
bool GetAutoDestroy() { return m_bAutoDestroy; }
void SetAutoDestroy(bool bAutoDestroy) { m_bAutoDestroy = bAutoDestroy; }
static int GetThreadCount();
static void Init();
static void Final();
protected:
virtual void Run() {}; // Virtual function - override in derivatives
};
#endif

416
Util.cpp Normal file
View File

@@ -0,0 +1,416 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2007 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 <sys/stat.h>
#include <stdio.h>
#ifdef WIN32
#include <direct.h>
#else
#include <unistd.h>
#endif
#include "nzbget.h"
#include "Util.h"
char* BaseFileName(const char* filename)
{
char* p = (char*)strrchr(filename, PATH_SEPARATOR);
char* p1 = (char*)strrchr(filename, ALT_PATH_SEPARATOR);
if (p1)
{
if ((p && p < p1) || !p)
{
p = p1;
}
}
if (p)
{
return p + 1;
}
else
{
return (char*)filename;
}
}
#ifdef WIN32
// getopt for WIN32:
// from http://www.codeproject.com/cpp/xgetopt.asp
// Original Author: Hans Dietrich (hdietrich2@hotmail.com)
// Released to public domain from author (thanks)
// Slightly modified by Andrei Prygounkov
char *optarg; // global argument pointer
int optind = 0; // global argv index
int getopt(int argc, char *argv[], char *optstring)
{
static char *next = NULL;
if (optind == 0)
next = NULL;
optarg = NULL;
if (next == NULL || *next == '\0')
{
if (optind == 0)
optind++;
if (optind >= argc || argv[optind][0] != '-' || argv[optind][1] == '\0')
{
optarg = NULL;
if (optind < argc)
optarg = argv[optind];
return -1;
}
if (strcmp(argv[optind], "--") == 0)
{
optind++;
optarg = NULL;
if (optind < argc)
optarg = argv[optind];
return -1;
}
next = argv[optind];
next++; // skip past -
optind++;
}
char c = *next++;
char *cp = strchr(optstring, c);
if (cp == NULL || c == ':')
{
fprintf(stderr, "Invalid option %c", c);
return '?';
}
cp++;
if (*cp == ':')
{
if (*next != '\0')
{
optarg = next;
next = NULL;
}
else if (optind < argc)
{
optarg = argv[optind];
optind++;
}
else
{
fprintf(stderr, "Option %c needs an argument", c);
return '?';
}
}
return c;
}
DirBrowser::DirBrowser(const char* szPath)
{
char szMask[MAX_PATH + 1];
int len = strlen(szPath);
if (szPath[len] == '\\' || szPath[len] == '/')
{
snprintf(szMask, MAX_PATH + 1, "%s*.*", szPath);
}
else
{
snprintf(szMask, MAX_PATH + 1, "%s%c*.*", szPath, (int)PATH_SEPARATOR);
}
szMask[MAX_PATH] = '\0';
m_hFile = _findfirst(szMask, &m_FindData);
m_bFirst = true;
}
DirBrowser::~DirBrowser()
{
if (m_hFile != -1L)
{
_findclose(m_hFile);
}
}
const char* DirBrowser::Next()
{
bool bOK = false;
if (m_bFirst)
{
bOK = m_hFile != -1L;
m_bFirst = false;
}
else
{
bOK = _findnext(m_hFile, &m_FindData) == 0;
}
if (bOK)
{
return m_FindData.name;
}
return NULL;
}
#else
DirBrowser::DirBrowser(const char* szPath)
{
m_pDir = opendir(szPath);
}
DirBrowser::~DirBrowser()
{
if (m_pDir)
{
closedir(m_pDir);
}
}
const char* DirBrowser::Next()
{
if (m_pDir)
{
m_pFindData = readdir(m_pDir);
if (m_pFindData)
{
return m_pFindData->d_name;
}
}
return NULL;
}
#endif
void NormalizePathSeparators(char* szPath)
{
for (char* p = szPath; *p; p++)
{
if (*p == ALT_PATH_SEPARATOR)
{
*p = PATH_SEPARATOR;
}
}
}
bool ForceDirectories(const char* szPath)
{
char* szNormPath = strdup(szPath);
NormalizePathSeparators(szNormPath);
int iLen = strlen(szNormPath);
if ((iLen > 0) && szNormPath[iLen-1] == PATH_SEPARATOR
#ifdef WIN32
&& iLen > 3
#endif
)
{
szNormPath[iLen-1] = '\0';
}
struct stat buffer;
bool bOK = !stat(szNormPath, &buffer) && S_ISDIR(buffer.st_mode);
if (!bOK
#ifdef WIN32
&& strlen(szNormPath) > 2
#endif
)
{
char* szParentPath = strdup(szNormPath);
bOK = true;
char* p = (char*)strrchr(szParentPath, PATH_SEPARATOR);
if (p)
{
#ifdef WIN32
if (p - szParentPath == 2 && szParentPath[1] == ':' && strlen(szParentPath) > 2)
{
szParentPath[3] = '\0';
}
else
#endif
{
*p = '\0';
}
if (strlen(szParentPath) != strlen(szPath))
{
bOK = ForceDirectories(szParentPath);
}
}
if (bOK)
{
mkdir(szNormPath, S_DIRMODE);
bOK = !stat(szNormPath, &buffer) && S_ISDIR(buffer.st_mode);
}
free(szParentPath);
}
free(szNormPath);
return bOK;
}
bool LoadFileIntoBuffer(const char* szFileName, char** pBuffer, int* pBufferLength)
{
FILE* pFile = fopen(szFileName, "r");
if (!pFile)
{
return false;
}
// obtain file size.
fseek(pFile , 0 , SEEK_END);
int iSize = ftell(pFile);
rewind(pFile);
// allocate memory to contain the whole file.
*pBuffer = (char*) malloc(iSize + 1);
if (!*pBuffer)
{
return false;
}
// copy the file into the buffer.
fread(*pBuffer, 1, iSize, pFile);
fclose(pFile);
(*pBuffer)[iSize] = 0;
*pBufferLength = iSize + 1;
return true;
}
bool SetFileSize(const char* szFilename, int iSize)
{
bool bOK = false;
#ifdef WIN32
FILE* pFile = fopen(szFilename, "a");
if (pFile)
{
bOK = _chsize_s(pFile->_file, iSize) == 0;
fclose(pFile);
}
#else
// create file
FILE* pFile = fopen(szFilename, "a");
if (pFile)
{
fclose(pFile);
}
// there no reliable function to expand file on POSIX, so we must try different approaches,
// starting with the fastest one and hoping it will work
// 1) set file size using function "truncate" (it is fast, if works)
truncate(szFilename, iSize);
// check if it worked
pFile = fopen(szFilename, "a");
if (pFile)
{
fseek(pFile, 0, SEEK_END);
bOK = ftell(pFile) == iSize;
if (!bOK)
{
// 2) truncate did not work, expanding the file by writing in it (it is slow)
fclose(pFile);
truncate(szFilename, 0);
pFile = fopen(szFilename, "a");
char c = '0';
fwrite(&c, 1, iSize, pFile);
bOK = ftell(pFile) == iSize;
}
fclose(pFile);
}
#endif
return bOK;
}
//replace bad chars in filename
void MakeValidFilename(char* szFilename, char cReplaceChar)
{
char* p = szFilename;
while (*p)
{
if (strchr("\\/:*?\"><'\n\r\t", *p))
{
*p = cReplaceChar;
}
p++;
}
// remove trailing points. they are not allowed in directory names on windows,
// but we remove them on posix also, in a case the directory is accessed from windows via samba.
for (int iLen = strlen(szFilename); iLen > 0 && szFilename[iLen - 1] == '.'; iLen--)
{
szFilename[iLen - 1] = '\0';
}
}
long long JoinInt64(unsigned int Hi, unsigned int Lo)
{
return (((long long)Hi) << 32) + Lo;
}
void SplitInt64(long long Int64, unsigned int* Hi, unsigned int* Lo)
{
*Hi = (unsigned int)(Int64 >> 32);
*Lo = (unsigned int)Int64;
}
float EqualTime(_timeval* t1, _timeval* t2)
{
#ifdef WIN32
return t1->time == t2->time && t1->millitm == t2->millitm;
#else
return t1->tv_sec == t2->tv_sec && t1->tv_usec == t2->tv_usec;
#endif
}
bool EmptyTime(_timeval* t)
{
#ifdef WIN32
return t->time == 0 && t->millitm == 0;
#else
return t->tv_sec == 0 && t->tv_usec == 0;
#endif
}
float DiffTime(_timeval* t1, _timeval* t2)
{
#ifdef WIN32
return ((t1->time - t2->time) + (t1->millitm - t2->millitm) / 1000.0);
#else
return (float)((t1->tv_sec - t2->tv_sec) + (t1->tv_usec - t2->tv_usec) / 1000000.0);
#endif
}

75
Util.h Normal file
View File

@@ -0,0 +1,75 @@
/*
* This file if part of nzbget
*
* Copyright (C) 2007 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$
*
*/
#ifndef UTIL_H
#define UTIL_H
#ifdef WIN32
#include <stdio.h>
#include <io.h>
#include <sys/timeb.h>
#else
#include <dirent.h>
#endif
#ifdef WIN32
extern int optind, opterr;
extern char *optarg;
int getopt(int argc, char *argv[], char *optstring);
#endif
class DirBrowser
{
private:
#ifdef WIN32
struct _finddata_t m_FindData;
intptr_t m_hFile;
bool m_bFirst;
#else
DIR* m_pDir;
struct dirent* m_pFindData;
#endif
public:
DirBrowser(const char* szPath);
~DirBrowser();
const char* Next();
};
char* BaseFileName(const char* filename);
void NormalizePathSeparators(char* szPath);
bool ForceDirectories(const char* szPath);
bool LoadFileIntoBuffer(const char* szFileName, char** pBuffer, int* pBufferLength);
bool SetFileSize(const char* szFilename, int iSize);
void MakeValidFilename(char* szFilename, char cReplaceChar);
long long JoinInt64(unsigned int Hi, unsigned int Lo);
void SplitInt64(long long Int64, unsigned int* Hi, unsigned int* Lo);
float EqualTime(_timeval* t1, _timeval* t2);
bool EmptyTime(_timeval* t);
float DiffTime(_timeval* t1, _timeval* t2);
#endif

1142
aclocal.m4 vendored
View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

@@ -6,28 +6,16 @@
/* Define to 1 to not use curses */
#undef DISABLE_CURSES
/* Define to 1 to disable gzip-support */
#undef DISABLE_GZIP
/* Define to 1 to not use libxml2, only for development purposes */
#undef DISABLE_LIBXML2
/* Define to 1 to disable par-verification and repair */
/* Define to 1 to disable smart par-verification and restoration */
#undef DISABLE_PARCHECK
/* Define to 1 to not use TLS/SSL */
#undef DISABLE_TLS
/* Define to 1 to enable unit and integration tests */
#undef ENABLE_TESTS
/* Define to 1 to include support for uulib */
#undef ENABLE_UULIB
/* Define to the name of macro which returns the name of function being
compiled */
#undef FUNCTION_MACRO_NAME
/* Define to 1 to create stacktrace on segmentation faults */
#undef HAVE_BACKTRACE
/* Define to 1 if ctime_r takes 2 arguments */
#undef HAVE_CTIME_R_2
@@ -37,54 +25,18 @@
/* Define to 1 if you have the <curses.h> header file. */
#undef HAVE_CURSES_H
/* define if the compiler supports basic C++14 syntax */
#undef HAVE_CXX14
/* Define to 1 if you have the <endian.h> header file. */
#undef HAVE_ENDIAN_H
/* Define to 1 if fdatasync is supported */
#undef HAVE_FDATASYNC
/* Define to 1 if fseeko (and presumably ftello) exists and is declared. */
#undef HAVE_FSEEKO
/* Define to 1 if F_FULLFSYNC is supported */
#undef HAVE_FULLFSYNC
/* Define to 1 if getaddrinfo is supported */
#undef HAVE_GETADDRINFO
/* Define to 1 if gethostbyname_r is supported */
#undef HAVE_GETHOSTBYNAME_R
/* Define to 1 if gethostbyname_r takes 3 arguments */
#undef HAVE_GETHOSTBYNAME_R_3
/* Define to 1 if gethostbyname_r takes 5 arguments */
#undef HAVE_GETHOSTBYNAME_R_5
/* Define to 1 if gethostbyname_r takes 6 arguments */
#undef HAVE_GETHOSTBYNAME_R_6
/* Define to 1 if you have the `getopt' function. */
#undef HAVE_GETOPT
/* Define to 1 if you have the <getopt.h> header file. */
#undef HAVE_GETOPT_H
/* Define to 1 if getopt_long is supported */
#undef HAVE_GETOPT_LONG
/* Define to 1 if you have the <inttypes.h> header file. */
#undef HAVE_INTTYPES_H
/* Define to 1 to use GnuTLS library for TLS/SSL-support. */
#undef HAVE_LIBGNUTLS
/* Define to 1 if lockf is supported */
#undef HAVE_LOCKF
/* Define to 1 if you have the <memory.h> header file. */
#undef HAVE_MEMORY_H
@@ -94,20 +46,8 @@
/* Define to 1 if you have the <ncurses/ncurses.h> header file. */
#undef HAVE_NCURSES_NCURSES_H
/* Define to 1 to use Nettle library for decryption. */
#undef HAVE_NETTLE
/* Define to 1 to use OpenSSL library for TLS/SSL-support and decryption. */
#undef HAVE_OPENSSL
/* Define to 1 if pthread_cancel is supported */
#undef HAVE_PTHREAD_CANCEL
/* Define to 1 if you have the <regex.h> header file. */
#undef HAVE_REGEX_H
/* Define to 1 if _SC_NPROCESSORS_ONLN is present in unistd.h */
#undef HAVE_SC_NPROCESSORS_ONLN
/* Define to 1 to use pragma pack directive in MessageBase.h */
#undef HAVE_PRAGMA_PACK
/* Define to 1 if you have the <stdint.h> header file. */
#undef HAVE_STDINT_H
@@ -115,18 +55,12 @@
/* Define to 1 if you have the <stdlib.h> header file. */
#undef HAVE_STDLIB_H
/* Define to 1 if you have the `stricmp' function. */
#undef HAVE_STRICMP
/* Define to 1 if you have the <strings.h> header file. */
#undef HAVE_STRINGS_H
/* Define to 1 if you have the <string.h> header file. */
#undef HAVE_STRING_H
/* Define to 1 if you have the <sys/prctl.h> header file. */
#undef HAVE_SYS_PRCTL_H
/* Define to 1 if you have the <sys/stat.h> header file. */
#undef HAVE_SYS_STAT_H
@@ -139,12 +73,6 @@
/* Define to 1 if variadic macros are supported */
#undef HAVE_VARIADIC_MACROS
/* Define to 1 if OpenSSL supports function "X509_check_host". */
#undef HAVE_X509_CHECK_HOST
/* Define to 1 to exclude debug-code */
#undef NDEBUG
/* Name of package */
#undef PACKAGE
@@ -160,37 +88,11 @@
/* Define to the one symbol short name of this package. */
#undef PACKAGE_TARNAME
/* Define to the home page for this package. */
#undef PACKAGE_URL
/* Define to the version of this package. */
#undef PACKAGE_VERSION
/* Define to 1 to install an empty signal handler for SIGCHLD */
#undef SIGCHLD_HANDLER
/* Determine what socket length (socklen_t) data type is */
#undef SOCKLEN_T
/* Define to 1 if you have the ANSI C header files. */
#undef STDC_HEADERS
/* Version number of package */
#undef VERSION
/* Enable large inode numbers on Mac OS X 10.5. */
#ifndef _DARWIN_USE_64_BIT_INODE
# define _DARWIN_USE_64_BIT_INODE 1
#endif
/* Number of bits in a file offset, on hosts where this is settable. */
#undef _FILE_OFFSET_BITS
/* Define to 1 to make fseeko visible on some hosts (e.g. glibc 2.2). */
#undef _LARGEFILE_SOURCE
/* Define for large files, on AIX-style hosts. */
#undef _LARGE_FILES
/* Define to `unsigned int' if <sys/types.h> does not define. */
#undef size_t

View File

@@ -1,40 +1,44 @@
#! /bin/sh
# Configuration validation subroutine script.
# Copyright 1992-2014 Free Software Foundation, Inc.
# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999,
# 2000, 2001, 2002, 2003, 2004, 2005, 2006 Free Software Foundation,
# Inc.
timestamp='2014-12-03'
timestamp='2006-09-20'
# This file 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 3 of the License, or
# This file is (in principle) common to ALL GNU software.
# The presence of a machine in this file suggests that SOME GNU software
# can handle that machine. It does not imply ALL GNU software can.
#
# This file 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.
# 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/>.
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA
# 02110-1301, USA.
#
# As a special exception to the GNU General Public License, if you
# distribute this file as part of a program that contains a
# configuration script generated by Autoconf, you may include it under
# the same distribution terms that you use for the rest of that
# program. This Exception is an additional permission under section 7
# of the GNU General Public License, version 3 ("GPLv3").
# the same distribution terms that you use for the rest of that program.
# Please send patches to <config-patches@gnu.org>.
# Please send patches to <config-patches@gnu.org>. Submit a context
# diff and a properly formatted ChangeLog entry.
#
# Configuration subroutine to validate and canonicalize a configuration type.
# Supply the specified configuration type as an argument.
# If it is invalid, we print an error message on stderr and exit with code 1.
# Otherwise, we print the canonical config type on stdout and succeed.
# You can get the latest version of this script from:
# http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD
# This file is supposed to be the same for all GNU packages
# and recognize all the CPU types, system types and aliases
# that are meaningful with *any* GNU software.
@@ -68,7 +72,8 @@ Report bugs and patches to <config-patches@gnu.org>."
version="\
GNU config.sub ($timestamp)
Copyright 1992-2014 Free Software Foundation, Inc.
Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005
Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
@@ -115,18 +120,12 @@ esac
# Here we must recognize all the valid KERNEL-OS combinations.
maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'`
case $maybe_os in
nto-qnx* | linux-gnu* | linux-android* | linux-dietlibc | linux-newlib* | \
linux-musl* | linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | kfreebsd*-gnu* | \
knetbsd*-gnu* | netbsd*-gnu* | \
kopensolaris*-gnu* | \
nto-qnx* | linux-gnu* | linux-dietlibc | linux-newlib* | linux-uclibc* | \
uclinux-uclibc* | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* | \
storm-chaos* | os2-emx* | rtmk-nova*)
os=-$maybe_os
basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`
;;
android-linux)
os=-linux-android
basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`-unknown
;;
*)
basic_machine=`echo $1 | sed 's/-[^-]*$//'`
if [ $basic_machine != $1 ]
@@ -149,13 +148,10 @@ case $os in
-convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\
-c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \
-harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \
-apple | -axis | -knuth | -cray | -microblaze*)
-apple | -axis | -knuth | -cray)
os=
basic_machine=$1
;;
-bluegene*)
os=-cnk
;;
-sim | -cisco | -oki | -wec | -winbond)
os=
basic_machine=$1
@@ -170,10 +166,10 @@ case $os in
os=-chorusos
basic_machine=$1
;;
-chorusrdb)
os=-chorusrdb
-chorusrdb)
os=-chorusrdb
basic_machine=$1
;;
;;
-hiux*)
os=-hiuxwe2
;;
@@ -218,12 +214,6 @@ case $os in
-isc*)
basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
;;
-lynx*178)
os=-lynxos178
;;
-lynx*5)
os=-lynxos5
;;
-lynx*)
os=-lynxos
;;
@@ -248,90 +238,59 @@ case $basic_machine in
# Some are omitted here because they have special meanings below.
1750a | 580 \
| a29k \
| aarch64 | aarch64_be \
| alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \
| alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \
| am33_2.0 \
| arc | arceb \
| arm | arm[bl]e | arme[lb] | armv[2-8] | armv[3-8][lb] | armv7[arm] \
| avr | avr32 \
| be32 | be64 \
| arc | arm | arm[bl]e | arme[lb] | armv[2345] | armv[345][lb] | avr | avr32 \
| bfin \
| c4x | c8051 | clipper \
| c4x | clipper \
| d10v | d30v | dlx | dsp16xx \
| epiphany \
| fido | fr30 | frv \
| fr30 | frv \
| h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \
| hexagon \
| i370 | i860 | i960 | ia64 \
| ip2k | iq2000 \
| k1om \
| le32 | le64 \
| lm32 \
| m32c | m32r | m32rle | m68000 | m68k | m88k \
| maxq | mb | microblaze | microblazeel | mcore | mep | metag \
| maxq | mb | microblaze | mcore \
| mips | mipsbe | mipseb | mipsel | mipsle \
| mips16 \
| mips64 | mips64el \
| mips64octeon | mips64octeonel \
| mips64orion | mips64orionel \
| mips64r5900 | mips64r5900el \
| mips64vr | mips64vrel \
| mips64orion | mips64orionel \
| mips64vr4100 | mips64vr4100el \
| mips64vr4300 | mips64vr4300el \
| mips64vr5000 | mips64vr5000el \
| mips64vr5900 | mips64vr5900el \
| mipsisa32 | mipsisa32el \
| mipsisa32r2 | mipsisa32r2el \
| mipsisa32r6 | mipsisa32r6el \
| mipsisa64 | mipsisa64el \
| mipsisa64r2 | mipsisa64r2el \
| mipsisa64r6 | mipsisa64r6el \
| mipsisa64sb1 | mipsisa64sb1el \
| mipsisa64sr71k | mipsisa64sr71kel \
| mipsr5900 | mipsr5900el \
| mipstx39 | mipstx39el \
| mn10200 | mn10300 \
| moxie \
| mt \
| msp430 \
| nds32 | nds32le | nds32be \
| nios | nios2 | nios2eb | nios2el \
| nios | nios2 \
| ns16k | ns32k \
| open8 | or1k | or1knd | or32 \
| or32 \
| pdp10 | pdp11 | pj | pjl \
| powerpc | powerpc64 | powerpc64le | powerpcle \
| powerpc | powerpc64 | powerpc64le | powerpcle | ppcbe \
| pyramid \
| riscv32 | riscv64 \
| rl78 | rx \
| score \
| sh | sh[1234] | sh[24]a | sh[24]aeb | sh[23]e | sh[34]eb | sheb | shbe | shle | sh[1234]le | sh3ele \
| sh | sh[1234] | sh[24]a | sh[23]e | sh[34]eb | sheb | shbe | shle | sh[1234]le | sh3ele \
| sh64 | sh64le \
| sparc | sparc64 | sparc64b | sparc64v | sparc86x | sparclet | sparclite \
| sparcv8 | sparcv9 | sparcv9b | sparcv9v \
| spu \
| tahoe | tic4x | tic54x | tic55x | tic6x | tic80 | tron \
| ubicom32 \
| v850 | v850e | v850e1 | v850e2 | v850es | v850e2v3 \
| visium \
| spu | strongarm \
| tahoe | thumb | tic4x | tic80 | tron \
| v850 | v850e \
| we32k \
| x86 | xc16x | xstormy16 | xtensa \
| z8k | z80)
| x86 | xc16x | xscale | xscalee[bl] | xstormy16 | xtensa \
| z8k)
basic_machine=$basic_machine-unknown
;;
c54x)
basic_machine=tic54x-unknown
;;
c55x)
basic_machine=tic55x-unknown
;;
c6x)
basic_machine=tic6x-unknown
;;
leon|leon[3-9])
basic_machine=sparc-$basic_machine
;;
m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x | nvptx | picochip)
m6811 | m68hc11 | m6812 | m68hc12)
# Motorola 68HC11/12.
basic_machine=$basic_machine-unknown
os=-none
;;
@@ -341,21 +300,6 @@ case $basic_machine in
basic_machine=mt-unknown
;;
strongarm | thumb | xscale)
basic_machine=arm-unknown
;;
xgate)
basic_machine=$basic_machine-unknown
os=-none
;;
xscaleeb)
basic_machine=armeb-unknown
;;
xscaleel)
basic_machine=armel-unknown
;;
# We use `pc' rather than `unknown'
# because (1) that's what they normally are, and
# (2) the word "unknown" tends to confuse beginning users.
@@ -370,87 +314,64 @@ case $basic_machine in
# Recognize the basic CPU types with company name.
580-* \
| a29k-* \
| aarch64-* | aarch64_be-* \
| alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \
| alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \
| alphapca5[67]-* | alpha64pca5[67]-* | arc-* | arceb-* \
| alphapca5[67]-* | alpha64pca5[67]-* | arc-* \
| arm-* | armbe-* | armle-* | armeb-* | armv*-* \
| avr-* | avr32-* \
| be32-* | be64-* \
| bfin-* | bs2000-* \
| c[123]* | c30-* | [cjt]90-* | c4x-* \
| c8051-* | clipper-* | craynv-* | cydra-* \
| c[123]* | c30-* | [cjt]90-* | c4x-* | c54x-* | c55x-* | c6x-* \
| clipper-* | craynv-* | cydra-* \
| d10v-* | d30v-* | dlx-* \
| elxsi-* \
| f30[01]-* | f700-* | fido-* | fr30-* | frv-* | fx80-* \
| f30[01]-* | f700-* | fr30-* | frv-* | fx80-* \
| h8300-* | h8500-* \
| hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \
| hexagon-* \
| i*86-* | i860-* | i960-* | ia64-* \
| ip2k-* | iq2000-* \
| k1om-* \
| le32-* | le64-* \
| lm32-* \
| m32c-* | m32r-* | m32rle-* \
| m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \
| m88110-* | m88k-* | maxq-* | mcore-* | metag-* \
| microblaze-* | microblazeel-* \
| m88110-* | m88k-* | maxq-* | mcore-* \
| mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \
| mips16-* \
| mips64-* | mips64el-* \
| mips64octeon-* | mips64octeonel-* \
| mips64orion-* | mips64orionel-* \
| mips64r5900-* | mips64r5900el-* \
| mips64vr-* | mips64vrel-* \
| mips64orion-* | mips64orionel-* \
| mips64vr4100-* | mips64vr4100el-* \
| mips64vr4300-* | mips64vr4300el-* \
| mips64vr5000-* | mips64vr5000el-* \
| mips64vr5900-* | mips64vr5900el-* \
| mipsisa32-* | mipsisa32el-* \
| mipsisa32r2-* | mipsisa32r2el-* \
| mipsisa32r6-* | mipsisa32r6el-* \
| mipsisa64-* | mipsisa64el-* \
| mipsisa64r2-* | mipsisa64r2el-* \
| mipsisa64r6-* | mipsisa64r6el-* \
| mipsisa64sb1-* | mipsisa64sb1el-* \
| mipsisa64sr71k-* | mipsisa64sr71kel-* \
| mipsr5900-* | mipsr5900el-* \
| mipstx39-* | mipstx39el-* \
| mmix-* \
| mt-* \
| msp430-* \
| nds32-* | nds32le-* | nds32be-* \
| nios-* | nios2-* | nios2eb-* | nios2el-* \
| nios-* | nios2-* \
| none-* | np1-* | ns16k-* | ns32k-* \
| open8-* \
| or1k*-* \
| orion-* \
| pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \
| powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* \
| powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* | ppcbe-* \
| pyramid-* \
| rl78-* | romp-* | rs6000-* | rx-* \
| sh-* | sh[1234]-* | sh[24]a-* | sh[24]aeb-* | sh[23]e-* | sh[34]eb-* | sheb-* | shbe-* \
| romp-* | rs6000-* \
| sh-* | sh[1234]-* | sh[24]a-* | sh[23]e-* | sh[34]eb-* | sheb-* | shbe-* \
| shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \
| sparc-* | sparc64-* | sparc64b-* | sparc64v-* | sparc86x-* | sparclet-* \
| sparclite-* \
| sparcv8-* | sparcv9-* | sparcv9b-* | sparcv9v-* | sv1-* | sx?-* \
| tahoe-* \
| sparcv8-* | sparcv9-* | sparcv9b-* | sparcv9v-* | strongarm-* | sv1-* | sx?-* \
| tahoe-* | thumb-* \
| tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \
| tile*-* \
| tron-* \
| ubicom32-* \
| v850-* | v850e-* | v850e1-* | v850es-* | v850e2-* | v850e2v3-* \
| vax-* \
| visium-* \
| v850-* | v850e-* | vax-* \
| we32k-* \
| x86-* | x86_64-* | xc16x-* | xps100-* \
| xstormy16-* | xtensa*-* \
| x86-* | x86_64-* | xc16x-* | xps100-* | xscale-* | xscalee[bl]-* \
| xstormy16-* | xtensa-* \
| ymp-* \
| z8k-* | z80-*)
;;
# Recognize the basic CPU types without company name, with glob match.
xtensa*)
basic_machine=$basic_machine-unknown
| z8k-*)
;;
# Recognize the various machine names and aliases which stand
# for a CPU type and a company and sometimes even an OS.
@@ -468,7 +389,7 @@ case $basic_machine in
basic_machine=a29k-amd
os=-udi
;;
abacus)
abacus)
basic_machine=abacus-unknown
;;
adobe68k)
@@ -514,10 +435,6 @@ case $basic_machine in
basic_machine=m68k-apollo
os=-bsd
;;
aros)
basic_machine=i386-pc
os=-aros
;;
aux)
basic_machine=m68k-apple
os=-aux
@@ -526,35 +443,10 @@ case $basic_machine in
basic_machine=ns32k-sequent
os=-dynix
;;
blackfin)
basic_machine=bfin-unknown
os=-linux
;;
blackfin-*)
basic_machine=bfin-`echo $basic_machine | sed 's/^[^-]*-//'`
os=-linux
;;
bluegene*)
basic_machine=powerpc-ibm
os=-cnk
;;
c54x-*)
basic_machine=tic54x-`echo $basic_machine | sed 's/^[^-]*-//'`
;;
c55x-*)
basic_machine=tic55x-`echo $basic_machine | sed 's/^[^-]*-//'`
;;
c6x-*)
basic_machine=tic6x-`echo $basic_machine | sed 's/^[^-]*-//'`
;;
c90)
basic_machine=c90-cray
os=-unicos
;;
cegcc)
basic_machine=arm-unknown
os=-cegcc
;;
convex-c1)
basic_machine=c1-convex
os=-bsd
@@ -583,8 +475,8 @@ case $basic_machine in
basic_machine=craynv-cray
os=-unicosmp
;;
cr16 | cr16-*)
basic_machine=cr16-unknown
cr16c)
basic_machine=cr16c-unknown
os=-elf
;;
crds | unos)
@@ -622,10 +514,6 @@ case $basic_machine in
basic_machine=m88k-motorola
os=-sysv3
;;
dicos)
basic_machine=i686-pc
os=-dicos
;;
djgpp)
basic_machine=i586-pc
os=-msdosdjgpp
@@ -741,6 +629,7 @@ case $basic_machine in
i370-ibm* | ibm*)
basic_machine=i370-ibm
;;
# I'm not sure what "Sysv32" means. Should this be sysv3.2?
i*86v32)
basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
os=-sysv32
@@ -779,17 +668,6 @@ case $basic_machine in
basic_machine=m68k-isi
os=-sysv
;;
leon-*|leon[3-9]-*)
basic_machine=sparc-`echo $basic_machine | sed 's/-.*//'`
;;
m68knommu)
basic_machine=m68k-unknown
os=-linux
;;
m68knommu-*)
basic_machine=m68k-`echo $basic_machine | sed 's/^[^-]*-//'`
os=-linux
;;
m88k-omron*)
basic_machine=m88k-omron
;;
@@ -801,21 +679,10 @@ case $basic_machine in
basic_machine=ns32k-utek
os=-sysv
;;
microblaze*)
basic_machine=microblaze-xilinx
;;
mingw64)
basic_machine=x86_64-pc
os=-mingw64
;;
mingw32)
basic_machine=i686-pc
basic_machine=i386-pc
os=-mingw32
;;
mingw32ce)
basic_machine=arm-unknown
os=-mingw32ce
;;
miniframe)
basic_machine=m68000-convergent
;;
@@ -837,10 +704,6 @@ case $basic_machine in
basic_machine=powerpc-unknown
os=-morphos
;;
moxiebox)
basic_machine=moxie-unknown
os=-moxiebox
;;
msdos)
basic_machine=i386-pc
os=-msdos
@@ -848,18 +711,10 @@ case $basic_machine in
ms1-*)
basic_machine=`echo $basic_machine | sed -e 's/ms1-/mt-/'`
;;
msys)
basic_machine=i686-pc
os=-msys
;;
mvs)
basic_machine=i370-ibm
os=-mvs
;;
nacl)
basic_machine=le32-unknown
os=-nacl
;;
ncr3000)
basic_machine=i486-ncr
os=-sysv4
@@ -924,12 +779,6 @@ case $basic_machine in
np1)
basic_machine=np1-gould
;;
neo-tandem)
basic_machine=neo-tandem
;;
nse-tandem)
basic_machine=nse-tandem
;;
nsr-tandem)
basic_machine=nsr-tandem
;;
@@ -960,14 +809,6 @@ case $basic_machine in
basic_machine=i860-intel
os=-osf
;;
parisc)
basic_machine=hppa-unknown
os=-linux
;;
parisc-*)
basic_machine=hppa-`echo $basic_machine | sed 's/^[^-]*-//'`
os=-linux
;;
pbd)
basic_machine=sparc-tti
;;
@@ -1012,10 +853,9 @@ case $basic_machine in
;;
power) basic_machine=power-ibm
;;
ppc | ppcbe) basic_machine=powerpc-unknown
ppc) basic_machine=powerpc-unknown
;;
ppc-* | ppcbe-*)
basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'`
ppc-*) basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'`
;;
ppcle | powerpclittle | ppc-le | powerpc-little)
basic_machine=powerpcle-unknown
@@ -1040,11 +880,7 @@ case $basic_machine in
basic_machine=i586-unknown
os=-pw32
;;
rdos | rdos64)
basic_machine=x86_64-pc
os=-rdos
;;
rdos32)
rdos)
basic_machine=i386-pc
os=-rdos
;;
@@ -1089,9 +925,6 @@ case $basic_machine in
basic_machine=sh-hitachi
os=-hms
;;
sh5el)
basic_machine=sh5le-unknown
;;
sh64)
basic_machine=sh64-unknown
;;
@@ -1113,9 +946,6 @@ case $basic_machine in
basic_machine=i860-stratus
os=-sysv4
;;
strongarm-* | thumb-*)
basic_machine=arm-`echo $basic_machine | sed 's/^[^-]*-//'`
;;
sun2)
basic_machine=m68000-sun
;;
@@ -1172,9 +1002,17 @@ case $basic_machine in
basic_machine=t90-cray
os=-unicos
;;
tile*)
basic_machine=$basic_machine-unknown
os=-linux-gnu
tic54x | c54x*)
basic_machine=tic54x-unknown
os=-coff
;;
tic55x | c55x*)
basic_machine=tic55x-unknown
os=-coff
;;
tic6x | c6x*)
basic_machine=tic6x-unknown
os=-coff
;;
tx39)
basic_machine=mipstx39-unknown
@@ -1243,9 +1081,6 @@ case $basic_machine in
xps | xps100)
basic_machine=xps100-honeywell
;;
xscale-* | xscalee[bl]-*)
basic_machine=`echo $basic_machine | sed 's/^xscale/arm/'`
;;
ymp)
basic_machine=ymp-cray
os=-unicos
@@ -1254,10 +1089,6 @@ case $basic_machine in
basic_machine=z8k-unknown
os=-sim
;;
z80-*-coff)
basic_machine=z80-unknown
os=-sim
;;
none)
basic_machine=none-none
os=-none
@@ -1296,7 +1127,7 @@ case $basic_machine in
we32k)
basic_machine=we32k-att
;;
sh[1234] | sh[24]a | sh[24]aeb | sh[34]eb | sh[1234]le | sh[23]ele)
sh[1234] | sh[24]a | sh[34]eb | sh[1234]le | sh[23]ele)
basic_machine=sh-unknown
;;
sparc | sparcv8 | sparcv9 | sparcv9b | sparcv9v)
@@ -1343,12 +1174,9 @@ esac
if [ x"$os" != x"" ]
then
case $os in
# First match some system type aliases
# that might get confused with valid system types.
# First match some system type aliases
# that might get confused with valid system types.
# -solaris* is a basic system type, with this one exception.
-auroraux)
os=-auroraux
;;
-solaris1 | -solaris1.*)
os=`echo $os | sed -e 's|solaris1|sunos4|'`
;;
@@ -1369,31 +1197,29 @@ case $os in
# Each alternative MUST END IN A *, to match a version number.
# -sysv* is not here because it comes later, after sysvr4.
-gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \
| -*vms* | -sco* | -esix* | -isc* | -aix* | -cnk* | -sunos | -sunos[34]*\
| -hpux* | -unos* | -osf* | -luna* | -dgux* | -auroraux* | -solaris* \
| -sym* | -kopensolaris* | -plan9* \
| -*vms* | -sco* | -esix* | -isc* | -aix* | -sunos | -sunos[34]*\
| -hpux* | -unos* | -osf* | -luna* | -dgux* | -solaris* | -sym* \
| -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \
| -aos* | -aros* \
| -aos* \
| -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \
| -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \
| -hiux* | -386bsd* | -knetbsd* | -mirbsd* | -netbsd* \
| -bitrig* | -openbsd* | -solidbsd* \
| -openbsd* | -solidbsd* \
| -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \
| -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \
| -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \
| -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \
| -chorusos* | -chorusrdb* | -cegcc* \
| -cygwin* | -msys* | -pe* | -psos* | -moss* | -proelf* | -rtems* \
| -mingw32* | -mingw64* | -linux-gnu* | -linux-android* \
| -linux-newlib* | -linux-musl* | -linux-uclibc* \
| -uxpv* | -beos* | -mpeix* | -udk* | -moxiebox* \
| -chorusos* | -chorusrdb* \
| -cygwin* | -pe* | -psos* | -moss* | -proelf* | -rtems* \
| -mingw32* | -linux-gnu* | -linux-newlib* | -linux-uclibc* \
| -uxpv* | -beos* | -mpeix* | -udk* \
| -interix* | -uwin* | -mks* | -rhapsody* | -darwin* | -opened* \
| -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \
| -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \
| -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \
| -morphos* | -superux* | -rtmk* | -rtmk-nova* | -windiss* \
| -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly* \
| -skyos* | -haiku* | -rdos* | -toppers* | -drops* | -es* | -tirtos*)
| -skyos* | -haiku* | -rdos* | -toppers*)
# Remember, each alternative MUST END IN *, to match a version number.
;;
-qnx*)
@@ -1432,7 +1258,7 @@ case $os in
-opened*)
os=-openedition
;;
-os400*)
-os400*)
os=-os400
;;
-wince*)
@@ -1481,7 +1307,7 @@ case $os in
-sinix*)
os=-sysv4
;;
-tpf*)
-tpf*)
os=-tpf
;;
-triton*)
@@ -1517,14 +1343,12 @@ case $os in
-aros*)
os=-aros
;;
-kaos*)
os=-kaos
;;
-zvmoe)
os=-zvmoe
;;
-dicos*)
os=-dicos
;;
-nacl*)
;;
-none)
;;
*)
@@ -1547,10 +1371,10 @@ else
# system, and we'll never get to this point.
case $basic_machine in
score-*)
score-*)
os=-elf
;;
spu-*)
spu-*)
os=-elf
;;
*-acorn)
@@ -1562,23 +1386,8 @@ case $basic_machine in
arm*-semi)
os=-aout
;;
c4x-* | tic4x-*)
os=-coff
;;
c8051-*)
os=-elf
;;
hexagon-*)
os=-elf
;;
tic54x-*)
os=-coff
;;
tic55x-*)
os=-coff
;;
tic6x-*)
os=-coff
c4x-* | tic4x-*)
os=-coff
;;
# This must come before the *-dec entry.
pdp10-*)
@@ -1598,13 +1407,13 @@ case $basic_machine in
;;
m68000-sun)
os=-sunos3
# This also exists in the configure program, but was not the
# default.
# os=-sunos4
;;
m68*-cisco)
os=-aout
;;
mep-*)
os=-elf
;;
mips*-cisco)
os=-elf
;;
@@ -1629,7 +1438,7 @@ case $basic_machine in
*-ibm)
os=-aix
;;
*-knuth)
*-knuth)
os=-mmixware
;;
*-wec)
@@ -1734,7 +1543,7 @@ case $basic_machine in
-sunos*)
vendor=sun
;;
-cnk*|-aix*)
-aix*)
vendor=ibm
;;
-beos*)

13305
configure vendored
View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,72 +1,73 @@
#
# This file is part of nzbget. See <http://nzbget.net>.
#
# Copyright (C) 2008-2021 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/>.
#
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.65)
AC_INIT(nzbget, 21.2-testing, hugbug@users.sourceforge.net)
AC_CONFIG_AUX_DIR(posix)
AC_CANONICAL_TARGET
AM_INIT_AUTOMAKE([foreign subdir-objects])
AC_CONFIG_SRCDIR([daemon/main/nzbget.cpp])
AC_PREREQ(2.59)
AC_INIT(nzbget, 0.3.1, hugbug@users.sourceforge.net)
AM_INIT_AUTOMAKE(nzbget, 0.3.1)
AC_CONFIG_SRCDIR([nzbget.cpp])
AC_CONFIG_HEADERS([config.h])
AM_MAINTAINER_MODE
m4_include([posix/ax_cxx_compile_stdcxx.m4])
dnl Architecture check
AC_CANONICAL_HOST
case "$host" in
*86-*-linux*)
LIBPREF1="/usr"
CFLAGS1="${CFLAGS} -m486"
CPPFLAGS1="${CPPFLAGS} -D_GNU_SOURCE"
;;
*-linux*)
LIBPREF1="/usr"
CPPFLAGS1="${CPPFLAGS} -D_GNU_SOURCE"
;;
*-freebsd*)
LIBPREF1="/usr/local"
;;
*-solaris*)
LIBPREF1="/usr"
;;
esac
if test "$LIBPREF" = ""; then
LIBPREF="$LIBPREF1"
fi
if test "$CFLAGS" = ""; then
CFLAGS="$CFLAGS1"
fi
if test "$CPPFLAGS" = ""; then
CPPFLAGS="$CPPFLAGS1"
fi
dnl
dnl Check for programs.
dnl
AC_PROG_CXX
AC_PROG_CC
AC_PROG_GCC_TRADITIONAL
AC_PROG_RANLIB
AC_PROG_MAKE_SET
AC_PATH_PROG(FALSE, false, /usr/bin/false)
AC_PATH_PROG(TRUE, true, /usr/bin/true)
AC_PATH_PROG(RM, rm, $FALSE)
AC_PATH_PROG(LN, ln, $FALSE)
AC_PATH_PROG(TAR, tar, $FALSE)
AC_PATH_PROG(AR, ar, $FALSE)
AC_PATH_PROG(MAKE, make, $FALSE)
AC_PATH_PROG(CXXCPP, cpp, $FALSE)
AC_PATH_PROG(MV, mv, $FALSE)
AC_PATH_PROG(MKDIR, mkdir, $FALSE)
AC_PATH_PROG(CP, cp, $FALSE)
AC_PROG_INSTALL
dnl
dnl Do all tests with c++ compiler.
dnl
AC_LANG(C++)
dnl
dnl Determine compiler switches to support C++14 standard.
dnl
AC_MSG_CHECKING(whether to test compiler features)
AC_ARG_ENABLE(cpp-check,
[AS_HELP_STRING([--disable-cpp-check], [disable check for C++14 compiler features])],
[ ENABLECPPCHECK=$enableval ],
[ ENABLECPPCHECK=yes] )
AC_MSG_RESULT($ENABLECPPCHECK)
if test "$ENABLECPPCHECK" = "yes"; then
AX_CXX_COMPILE_STDCXX(14,,[optional])
if test "$HAVE_CXX14" != "1"; then
AC_MSG_ERROR("A compiler with support for C++14 language features is required. For details visit http://nzbget.net/cpp14")
fi
fi
dnl
dnl Checks for header files.
dnl
AC_CHECK_HEADERS(sys/prctl.h regex.h endian.h getopt.h)
dnl AC_CHECK_HEADERS(stdarg.h time.h stdlib.h stdio.h unistd.h errno.h string.h sys/stat.h sys/time.h)
dnl AC_CHECK_HEADERS(libgen.h pwd.h getopt.h dirent.h fcntl.h pthread.h semaphore.h)
dnl AC_CHECK_HEADERS(sys/socket.h sys/types.h netinet/in.h arpa/inet.h netdb.h)
dnl
dnl Check for libs
@@ -74,41 +75,16 @@ dnl
AC_SEARCH_LIBS([pthread_create], [pthread])
AC_SEARCH_LIBS([socket], [socket])
AC_SEARCH_LIBS([inet_addr], [nsl])
AC_SEARCH_LIBS([gethostbyname_r], [nsl])
AC_SEARCH_LIBS([hstrerror], [resolv])
dnl
dnl Android NDK restrictions
dnl
AC_CHECK_FUNC(lockf,
[AC_CHECK_DECL(lockf,
[AC_DEFINE([HAVE_LOCKF], 1, [Define to 1 if lockf is supported])],,
[#include <unistd.h>])])
AC_CHECK_FUNC(pthread_cancel,
[AC_CHECK_DECL(pthread_cancel,
[AC_DEFINE([HAVE_PTHREAD_CANCEL], 1, [Define to 1 if pthread_cancel is supported])],,
[#include <pthread.h>])])
dnl
dnl Getopt
dnl
AC_CHECK_FUNC(getopt_long,
[AC_DEFINE([HAVE_GETOPT_LONG], 1, [Define to 1 if getopt_long is supported])],)
dnl
dnl fsync
dnl
AC_CHECK_FUNC(fdatasync,
[AC_DEFINE([HAVE_FDATASYNC], 1, [Define to 1 if fdatasync is supported])],)
AC_CHECK_DECL(F_FULLFSYNC,
[AC_DEFINE([HAVE_FULLFSYNC], 1, [Define to 1 if F_FULLFSYNC is supported])],,[#include <fcntl.h>])
dnl
dnl use 64-Bits for file sizes
dnl
AC_SYS_LARGEFILE
[AC_DEFINE([HAVE_GETOPT_LONG], 1, [Define to 1 if getopt_long is supported])],
[AC_LIBOBJ(getopt) AC_LIBOBJ(getopt1)])
dnl
@@ -119,7 +95,6 @@ AC_TRY_COMPILE(
[#include <time.h>],
[ time_t clock; char buf[26]; ctime_r(&clock, buf, 26); ],
AC_MSG_RESULT([[yes, and it takes 3 arguments]])
FOUND="yes"
AC_DEFINE([HAVE_CTIME_R_3], 1, [Define to 1 if ctime_r takes 3 arguments]),
FOUND="no")
if test "$FOUND" = "no"; then
@@ -133,141 +108,93 @@ AC_TRY_COMPILE(
fi
if test "$FOUND" = "no"; then
AC_MSG_RESULT([no])
AC_MSG_ERROR("function ctime_r not found")
AC_MSG_ERROR("function ctime_r not found.")
fi
dnl
dnl check getaddrinfo
dnl check gethostbyname_r
dnl
AC_CHECK_FUNC(getaddrinfo,
FOUND="yes"
[AC_DEFINE([HAVE_GETADDRINFO], 1, [Define to 1 if getaddrinfo is supported])]
AC_SEARCH_LIBS([getaddrinfo], [nsl]),
FOUND="no")
dnl
dnl check gethostbyname_r, if getaddrinfo is not available
dnl
if test "$FOUND" = "no"; then
AC_MSG_CHECKING(for gethostbyname_r)
AC_TRY_COMPILE(
[#include <netdb.h>],
[ char* szHost; struct hostent hinfobuf; char* strbuf; int h_errnop;
struct hostent* hinfo = gethostbyname_r(szHost, &hinfobuf, strbuf, 1024, &h_errnop); ],
AC_MSG_RESULT([[yes, and it takes 5 arguments]])
FOUND="yes"
AC_DEFINE([HAVE_GETHOSTBYNAME_R_5], 1, [Define to 1 if gethostbyname_r takes 5 arguments]),
FOUND="no")
if test "$FOUND" = "no"; then
AC_TRY_COMPILE(
[#include <netdb.h>],
[ char* szHost; struct hostent* hinfo; struct hostent hinfobuf; char* strbuf; int h_errnop;
int err = gethostbyname_r(szHost, &hinfobuf, strbuf, 1024, &hinfo, &h_errnop); ],
AC_MSG_RESULT([[yes, and it takes 6 arguments]])
FOUND="yes"
AC_DEFINE([HAVE_GETHOSTBYNAME_R_6], 1, [Define to 1 if gethostbyname_r takes 6 arguments]),
FOUND="no")
fi
if test "$FOUND" = "no"; then
AC_TRY_COMPILE(
[#include <netdb.h>],
[ char* szHost; struct hostent hinfo; struct hostent_data hinfobuf;
int err = gethostbyname_r(szHost, &hinfo, &hinfobuf); ],
AC_MSG_RESULT([[yes, and it takes 3 arguments]])
FOUND="yes"
AC_DEFINE([HAVE_GETHOSTBYNAME_R_3], 1, [Define to 1 if gethostbyname_r takes 3 arguments]),
AC_MSG_RESULT([[no]])
FOUND="no")
fi
if test "$FOUND" = "yes"; then
AC_DEFINE([HAVE_GETHOSTBYNAME_R], 1, [Define to 1 if gethostbyname_r is supported])
AC_SEARCH_LIBS([gethostbyname_r], [nsl])
fi
fi
dnl
dnl Determine what socket length (socklen_t) data type is
dnl
AC_MSG_CHECKING([for type of socket length (socklen_t)])
AC_TRY_COMPILE([
#include <stddef.h>
#include <sys/types.h>
#include <sys/socket.h>],[
(void)getsockopt (1, 1, 1, NULL, (socklen_t*)NULL)],[
AC_MSG_RESULT(socklen_t)
SOCKLEN_T=socklen_t],[
AC_TRY_COMPILE([
#include <stddef.h>
#include <sys/types.h>
#include <sys/socket.h>],[
(void)getsockopt (1, 1, 1, NULL, (size_t*)NULL)],[
AC_MSG_RESULT(size_t)
SOCKLEN_T=size_t],[
AC_TRY_COMPILE([
#include <stddef.h>
#include <sys/types.h>
#include <sys/socket.h>],[
(void)getsockopt (1, 1, 1, NULL, (int*)NULL)],[
AC_MSG_RESULT(int)
SOCKLEN_T=int],[
AC_MSG_WARN(could not determine)
SOCKLEN_T=int])])])
AC_DEFINE_UNQUOTED(SOCKLEN_T, $SOCKLEN_T, [Determine what socket length (socklen_t) data type is])
dnl
dnl check cpu cores via sysconf
dnl
AC_MSG_CHECKING(for cpu cores via sysconf)
AC_MSG_CHECKING(for gethostbyname_r)
AC_TRY_COMPILE(
[#include <unistd.h>],
[ int a = _SC_NPROCESSORS_ONLN; ],
FOUND="yes"
AC_MSG_RESULT([[yes]])
AC_DEFINE([HAVE_SC_NPROCESSORS_ONLN], 1, [Define to 1 if _SC_NPROCESSORS_ONLN is present in unistd.h]),
[#include <netdb.h>],
[ char* szHost; struct hostent hinfobuf; char* strbuf; int h_errnop;
struct hostent* hinfo = gethostbyname_r(szHost, &hinfobuf, strbuf, 1024, &h_errnop); ],
AC_MSG_RESULT([[yes, and it takes 5 arguments]])
FOUND="yes"
AC_DEFINE([HAVE_GETHOSTBYNAME_R_5], 1, [Define to 1 if gethostbyname_r takes 5 arguments]),
FOUND="no")
if test "$FOUND" = "no"; then
AC_TRY_COMPILE(
[#include <netdb.h>],
[ char* szHost; struct hostent* hinfo; struct hostent hinfobuf; char* strbuf; int h_errnop;
int err = gethostbyname_r(szHost, &hinfobuf, strbuf, 1024, &hinfo, &h_errnop); ],
AC_MSG_RESULT([[yes, and it takes 6 arguments]])
FOUND="yes"
AC_DEFINE([HAVE_GETHOSTBYNAME_R_6], 1, [Define to 1 if gethostbyname_r takes 6 arguments]),
FOUND="no")
fi
if test "$FOUND" = "no"; then
AC_MSG_RESULT([no])
AC_MSG_ERROR("function gethostbyname_r not found.")
fi
dnl
dnl check for __FUNCTION__ or __func__ macro
dnl
AC_MSG_CHECKING(for __FUNCTION__ macro)
AC_LANG_PUSH(C++)
AC_TRY_COMPILE([#include <stdio.h>], [printf("%s\n", __FUNCTION__);],
AC_MSG_RESULT([yes])
AC_DEFINE([FUNCTION_MACRO_NAME],[__FUNCTION__],[Define to the name of macro which returns the name of funtion being compiled])
HAVE_FUNCTION_MACRO=yes,
AC_MSG_RESULT([no]))
AC_LANG_POP(C++)
if test "$HAVE_FUNCTION_MACRO" != "yes"; then
AC_MSG_CHECKING(for __func__ macro)
AC_LANG_PUSH(C++)
AC_TRY_COMPILE([#include <stdio.h>], [printf("%s\n", __func__);],
AC_MSG_RESULT([yes])
AC_DEFINE([FUNCTION_MACRO_NAME],[__func__],[Define to the name of macro which returns the name of function being compiled])
HAVE_FUNCTION_MACRO=yes,
AC_MSG_RESULT([no]))
AC_LANG_POP(C++)
fi
if test "$HAVE_FUNCTION_MACRO" != "yes"; then
AC_DEFINE([FUNCTION_MACRO_NAME],[NULL],[Define to the name of macro which returns the name of function being compiled])
fi
dnl
dnl check for pragma pack
dnl
AC_MSG_CHECKING(for pragma pack)
AC_TRY_COMPILE([#pragma pack(1)
#pragma pack()],,
AC_MSG_RESULT([yes])
AC_DEFINE([HAVE_PRAGMA_PACK],1,[Define to 1 to use pragma pack directive in MessageBase.h]),
AC_MSG_RESULT([no]))
dnl
dnl checks for libxml2 includes and libraries.
dnl
AC_MSG_CHECKING(whether to use libxml2)
AC_ARG_ENABLE(libxml2,
[AS_HELP_STRING([--disable-libxml2], [do not use libxml2 (removes dependency from libxml2-library, only for development purposes)])],
[USELIBXML2=$enableval],
[USELIBXML2=yes] )
AC_MSG_RESULT($USELIBXML2)
if test "$USELIBXML2" = "yes"; then
AC_ARG_WITH(libxml2_includes,
[AS_HELP_STRING([--with-libxml2-includes=DIR], [libxml2 include directory])],
[CPPFLAGS="${CPPFLAGS} -I${withval}"]
[INCVAL="yes"],
[INCVAL="no"])
AC_ARG_WITH(libxml2_libraries,
[AS_HELP_STRING([--with-libxml2-libraries=DIR], [libxml2 library directory])],
[LDFLAGS="${LDFLAGS} -L${withval}"]
[LIBVAL="yes"],
[LIBVAL="no"])
if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then
PKG_CHECK_MODULES(libxml2, libxml-2.0,
[LIBS="${LIBS} $libxml2_LIBS"]
[CPPFLAGS="${CPPFLAGS} $libxml2_CFLAGS"],
AC_MSG_ERROR("libxml2 library not found"))
fi
AC_CHECK_HEADER(libxml/tree.h,,
AC_MSG_ERROR("libxml2 header files not found"))
AC_SEARCH_LIBS([xmlNewNode], [xml2], ,
AC_MSG_ERROR("libxml2 library not found"))
else
AC_DEFINE([DISABLE_LIBXML2],1,[Define to 1 to not use libxml2, only for development purposes])
fi
dnl
INCVAL="${LIBPREF}/include/libxml2"
LIBVAL="${LIBPREF}/lib"
AC_ARG_WITH(libxml2_includes,
[ --with-libxml2-includes=DIR libxml2 include directory],
[INCVAL="$withval"])
CPPFLAGS="${CPPFLAGS} -I${INCVAL}"
CFLAGS="${CFLAGS} -I${INCVAL}"
AC_CHECK_HEADER(libxml/tree.h,,
AC_MSG_ERROR("libxml2 header files were not found."))
AC_ARG_WITH(libxml2_libraries,
[ --with-libxml2-libraries=DIR libxml2 library directory],
[LIBVAL="$withval"])
LDFLAGS="${LDFLAGS} -L${LIBVAL}"
AC_SEARCH_LIBS([xmlNewNode], [xml2], ,
AC_MSG_ERROR("libxml2 library not found in $LIBVAL."))
dnl
@@ -275,28 +202,23 @@ dnl Use curses. Deafult: yes
dnl
AC_MSG_CHECKING(whether to use curses)
AC_ARG_ENABLE(curses,
[AS_HELP_STRING([--disable-curses], [do not use curses (removes dependency from curses-library)])],
[USECURSES=$enableval],
[USECURSES=yes] )
[ --disable-curses do not use curses (removes dependency from curses-library and makes executable smaller)],
[ USECURSES=$enableval ],
[ USECURSES=yes] )
AC_MSG_RESULT($USECURSES)
if test "$USECURSES" = "yes"; then
INCVAL="${LIBPREF}/include"
LIBVAL="${LIBPREF}/lib"
AC_ARG_WITH(libcurses_includes,
[AS_HELP_STRING([--with-libcurses-includes=DIR], [libcurses include directory])],
[CPPFLAGS="${CPPFLAGS} -I${withval}"]
[INCVAL="yes"],
[INCVAL="no"])
[ --with-libcurses-includes=DIR libcurses include directory],
[INCVAL="$withval"])
CPPFLAGS="${CPPFLAGS} -I${INCVAL}"
CFLAGS="${CFLAGS} -I${INCVAL}"
AC_ARG_WITH(libcurses_libraries,
[AS_HELP_STRING([--with-libcurses-libraries=DIR], [libcurses library directory])],
[LDFLAGS="${LDFLAGS} -L${withval}"]
[LIBVAL="yes"],
[LIBVAL="no"])
if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then
PKG_CHECK_MODULES(ncurses, ncurses,
[LIBS="${LIBS} $ncurses_LIBS"]
[CPPFLAGS="${CPPFLAGS} $ncurses_CFLAGS"],
AC_MSG_ERROR("ncurses library not found"))
fi
[ --with-libcurses-libraries=DIR libcurses library directory],
[LIBVAL="$withval"])
LDFLAGS="${LDFLAGS} -L${LIBVAL}"
AC_CHECK_HEADER(ncurses.h,
FOUND=yes
AC_DEFINE([HAVE_NCURSES_H],1,[Define to 1 if you have the <ncurses.h> header file.]),
@@ -314,284 +236,114 @@ if test "$USECURSES" = "yes"; then
FOUND=no)
fi
if test "$FOUND" = "no"; then
AC_MSG_ERROR([Couldn't find curses headers (ncurses.h or curses.h)])
AC_MSG_ERROR([Couldn't find curses headers (ncurses.h or curses.h).])
fi
AC_SEARCH_LIBS([refresh], [ncurses curses],,
AC_ERROR([Couldn't find curses library]))
AC_SEARCH_LIBS([nodelay], [ncurses curses tinfo],,
AC_ERROR([Couldn't find curses library]))
else
AC_DEFINE([DISABLE_CURSES],1,[Define to 1 to not use curses])
fi
dnl
dnl Use par-checking. Deafult: yes.
dnl Use uulib. Deafult: no
dnl
AC_MSG_CHECKING(whether to use uulib for decoding and joining)
AC_ARG_ENABLE(uulib,
[ --enable-uulib use uulib for decoding and joining],
[ ENABLEUULIB=$enableval ],
[ ENABLEUULIB=no] )
AC_MSG_RESULT($ENABLEUULIB)
if test "$ENABLEUULIB" = "yes"; then
AC_DEFINE([ENABLE_UULIB],1,[Define to 1 to include support for uulib])
dnl
dnl checks for uulib includes and libraries.
dnl
INCVAL="${LIBPREF}/include"
LIBVAL="${LIBPREF}/lib"
AC_ARG_WITH(uulib_includes,
[ --with-uulib-includes=DIR uulib include directory],
[INCVAL="$withval"])
CPPFLAGS="${CPPFLAGS} -I${INCVAL}"
AC_CHECK_HEADER($INCVAL/uudeview.h,,
AC_MSG_ERROR("uulib header files were not found in $INCVAL."))
AC_ARG_WITH(uulib_libraries,
[ --with-uulib-libraries=DIR uulib library directory],
[LIBVAL="$withval"])
LDFLAGS="${LDFLAGS} -L${LIBVAL}"
AC_SEARCH_LIBS([UUInitialize], [uu],,
AC_MSG_ERROR("uulib library not found in $LIBVAL."))
fi
dnl
dnl Use lib2par for par-checking. Deafult: no
dnl
AC_MSG_CHECKING(whether to include code for par-checking)
AC_ARG_ENABLE(parcheck,
[AS_HELP_STRING([--disable-parcheck], [do not include par-check/-repair-support])],
[ --enable-parcheck include code for par-checking],
[ ENABLEPARCHECK=$enableval ],
[ ENABLEPARCHECK=yes] )
AC_MSG_RESULT($ENABLEPARCHECK)
if test "$ENABLEPARCHECK" = "yes"; then
dnl PAR2 checks.
dnl
dnl Checks for typedefs, structures, and compiler characteristics.
AC_TYPE_SIZE_T
AC_FUNC_FSEEKO
dnl Checks for library functions.
AC_CHECK_FUNCS([stricmp])
AC_CHECK_FUNCS([getopt])
AM_CONDITIONAL(WITH_PAR2, true)
else
AC_DEFINE([DISABLE_PARCHECK],1,[Define to 1 to disable par-verification and repair])
AM_CONDITIONAL(WITH_PAR2, false)
fi
dnl
dnl Use TLS/SSL. Deafult: yes
dnl
AC_MSG_CHECKING(whether to use TLS/SSL)
AC_ARG_ENABLE(tls,
[AS_HELP_STRING([--disable-tls], [do not use TLS/SSL (removes dependency from TLS/SSL-libraries)])],
[ USETLS=$enableval ],
[ USETLS=yes] )
AC_MSG_RESULT($USETLS)
if test "$USETLS" = "yes"; then
AC_ARG_WITH(tlslib,
[AS_HELP_STRING([--with-tlslib=(OpenSSL, GnuTLS)], [TLS/SSL library to use])],
[TLSLIB="$withval"])
if test "$TLSLIB" != "GnuTLS" -a "$TLSLIB" != "OpenSSL" -a "$TLSLIB" != ""; then
AC_MSG_ERROR([Invalid argument for option --with-tlslib])
fi
if test "$TLSLIB" = "OpenSSL" -o "$TLSLIB" = ""; then
AC_ARG_WITH(openssl_includes,
[AS_HELP_STRING([--with-openssl-includes=DIR], [OpenSSL include directory])],
[CPPFLAGS="${CPPFLAGS} -I${withval}"]
[INCVAL="yes"],
[INCVAL="no"])
AC_ARG_WITH(openssl_libraries,
[AS_HELP_STRING([--with-openssl-libraries=DIR], [OpenSSL library directory])],
[LDFLAGS="${LDFLAGS} -L${withval}"]
[LIBVAL="yes"],
[LIBVAL="no"])
if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then
PKG_CHECK_MODULES([openssl], [openssl],
[LIBS="${LIBS} $openssl_LIBS"]
[CPPFLAGS="${CPPFLAGS} $openssl_CFLAGS"])
fi
AC_CHECK_HEADER(openssl/ssl.h,
FOUND=yes
TLSHEADERS=yes,
FOUND=no)
if test "$FOUND" = "no" -a "$TLSLIB" = "OpenSSL"; then
AC_MSG_ERROR([Couldn't find OpenSSL headers (ssl.h)])
fi
if test "$FOUND" = "yes"; then
AC_SEARCH_LIBS([ASN1_OBJECT_free], [crypto],
AC_SEARCH_LIBS([SSL_CTX_new], [ssl],
FOUND=yes,
FOUND=no),
FOUND=no)
if test "$FOUND" = "no" -a "$TLSLIB" = "OpenSSL"; then
AC_MSG_ERROR([Couldn't find OpenSSL library])
fi
if test "$FOUND" = "yes"; then
TLSLIB="OpenSSL"
AC_DEFINE([HAVE_OPENSSL],1,[Define to 1 to use OpenSSL library for TLS/SSL-support and decryption.])
AC_SEARCH_LIBS([X509_check_host], [crypto],
AC_DEFINE([HAVE_X509_CHECK_HOST],1,[Define to 1 if OpenSSL supports function "X509_check_host".]))
fi
fi
fi
if test "$TLSLIB" = "GnuTLS" -o "$TLSLIB" = ""; then
AC_ARG_WITH(libgnutls_includes,
[AS_HELP_STRING([--with-libgnutls-includes=DIR], [GnuTLS include directory])],
[CPPFLAGS="${CPPFLAGS} -I${withval}"]
[INCVAL="yes"],
[INCVAL="no"])
AC_ARG_WITH(libgnutls_libraries,
[AS_HELP_STRING([--with-libgnutls-libraries=DIR], [GnuTLS library directory])],
[LDFLAGS="${LDFLAGS} -L${withval}"]
[LIBVAL="yes"],
[LIBVAL="no"])
if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then
PKG_CHECK_MODULES([gnutls], [gnutls],
[LIBS="${LIBS} $gnutls_LIBS"]
[CPPFLAGS="${CPPFLAGS} $gnutls_CFLAGS"])
fi
AC_CHECK_HEADER(gnutls/gnutls.h,
FOUND=yes
TLSHEADERS=yes,
FOUND=no)
if test "$FOUND" = "no" -a "$TLSLIB" = "GnuTLS"; then
AC_MSG_ERROR([Couldn't find GnuTLS headers (gnutls.h)])
fi
if test "$FOUND" = "yes"; then
AC_SEARCH_LIBS([gnutls_global_init], [gnutls],
FOUND=yes,
FOUND=no)
if test "$FOUND" = "yes"; then
dnl gcrypt is optional
AC_MSG_CHECKING([whether gcrypt is needed])
AC_TRY_COMPILE(
[#include <gnutls/gnutls.h>]
[#if GNUTLS_VERSION_NUMBER <= 0x020b00]
[compile error]
[#endif],
[int a;],
AC_MSG_RESULT([no])
GCRYPT=no,
AC_MSG_RESULT([yes])
GCRYPT=yes)
if test "$GCRYPT" = "yes"; then
AC_CHECK_HEADER([gcrypt.h],
AC_SEARCH_LIBS([gcry_control], [gnutls gcrypt],
FOUND=yes,
FOUND=no),
FOUND=yes)
fi
fi
if test "$FOUND" = "no" -a "$TLSLIB" = "GnuTLS"; then
AC_MSG_ERROR([Couldn't find GnuTLS library])
fi
if test "$FOUND" = "yes"; then
TLSLIB="GnuTLS"
AC_DEFINE([HAVE_LIBGNUTLS],1,[Define to 1 to use GnuTLS library for TLS/SSL-support.])
fi
fi
if test "$TLSLIB" = "GnuTLS"; then
AC_ARG_WITH(libnettle_includes,
[AS_HELP_STRING([--with-libnettle-includes=DIR], [Nettle include directory])],
[CPPFLAGS="${CPPFLAGS} -I${withval}"]
[INCVAL="yes"],
[INCVAL="no"])
AC_ARG_WITH(libnettle_libraries,
[AS_HELP_STRING([--with-libnettle-libraries=DIR], [Nettle library directory])],
[LDFLAGS="${LDFLAGS} -L${withval}"]
[LIBVAL="yes"],
[LIBVAL="no"])
if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then
PKG_CHECK_MODULES([nettle], [nettle],
[LIBS="${LIBS} $nettle_LIBS"]
[CPPFLAGS="${CPPFLAGS} $nettle_CFLAGS"])
fi
AC_CHECK_HEADER(nettle/sha.h,
FOUND=yes,
FOUND=no)
if test "$FOUND" = "no"; then
AC_MSG_ERROR([Couldn't find Nettle headers (sha.h)])
fi
AC_SEARCH_LIBS([nettle_pbkdf2_hmac_sha256], [nettle],
FOUND=yes,
FOUND=no)
if test "$FOUND" = "no"; then
AC_MSG_ERROR([Couldn't find Nettle library, required when using GnuTLS])
fi
if test "$FOUND" = "yes"; then
AC_DEFINE([HAVE_NETTLE],1,[Define to 1 to use Nettle library for decryption.])
fi
fi
fi
if test "$TLSLIB" = ""; then
if test "$TLSHEADERS" = ""; then
AC_MSG_ERROR([Couldn't find neither OpenSSL nor GnuTLS headers (ssl.h or gnutls.h)])
else
AC_MSG_ERROR([Couldn't find neither OpenSSL nor GnuTLS library])
fi
fi
else
AC_DEFINE([DISABLE_TLS],1,[Define to 1 to not use TLS/SSL])
fi
dnl
dnl checks for zlib includes and libraries.
dnl
AC_MSG_CHECKING(whether to use gzip)
AC_ARG_ENABLE(gzip,
[AS_HELP_STRING([--disable-gzip], [disable gzip-compression/decompression (removes dependency from zlib-library)])],
[USEZLIB=$enableval],
[USEZLIB=yes] )
AC_MSG_RESULT($USEZLIB)
if test "$USEZLIB" = "yes"; then
AC_ARG_WITH(zlib_includes,
[AS_HELP_STRING([--with-zlib-includes=DIR], [zlib include directory])],
[CPPFLAGS="${CPPFLAGS} -I${withval}"]
[INCVAL="yes"],
[INCVAL="no"])
AC_ARG_WITH(zlib_libraries,
[AS_HELP_STRING([--with-zlib-libraries=DIR], [zlib library directory])],
[LDFLAGS="${LDFLAGS} -L${withval}"]
[LIBVAL="yes"],
[LIBVAL="no"])
if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then
PKG_CHECK_MODULES([zlib], [zlib],
[LIBS="${LIBS} $zlib_LIBS"]
[CPPFLAGS="${CPPFLAGS} $zlib_CFLAGS"])
fi
dnl checks for libsigc++ includes and libraries (required for libpar2).
dnl
INCVAL="${LIBPREF}/include/sigc++-2.0"
LIBVAL="${LIBPREF}/lib"
AC_ARG_WITH(libsigc_includes,
[ --with-libsigc-includes=DIR libsigc++-2.0 include directory],
[INCVAL="$withval"])
AC_CHECK_HEADER(zlib.h,,
AC_MSG_ERROR("zlib header files not found"))
AC_SEARCH_LIBS([deflateBound], [z], ,
AC_MSG_ERROR("zlib library not found"))
AC_ARG_WITH(libsigc_libraries,
[ --with-libsigc-libraries=DIR libsigc++-2.0 library directory],
[LIBVAL="$withval"])
LDFLAGS="${LDFLAGS} -L${LIBVAL}"
CPPFLAGS="${CPPFLAGS} -I${INCVAL} -I${LIBVAL}/sigc++-2.0/include"
AC_LANG_PUSH(C++)
AC_CHECK_HEADER(sigc++/type_traits.h,,
AC_MSG_ERROR("libsigc++-2.0 header files were not found in $INCVAL."))
AC_LANG_POP(C++)
dnl
dnl checks for libpar2 includes and libraries.
dnl
INCVAL="${LIBPREF}/include"
LIBVAL="${LIBPREF}/lib"
AC_ARG_WITH(libpar2_includes,
[ --with-libpar2-includes=DIR libpar2 include directory],
[INCVAL="$withval"])
CPPFLAGS="${CPPFLAGS} -I${INCVAL}"
AC_LANG_PUSH(C++)
AC_CHECK_HEADER(libpar2/libpar2.h,,
AC_MSG_ERROR("libpar2 header files were not found in $INCVAL."))
AC_LANG_POP(C++)
AC_ARG_WITH(libpar2_libraries,
[ --with-libpar2-libraries=DIR libpar2 library directory],
[LIBVAL="$withval"])
LDFLAGS="${LDFLAGS} -L${LIBVAL}"
dnl Q: How to check for c++-class in library?
LIBS="${LIBS} -lpar2"
dnl AC_CHECK_LIB(par2, GenerateCRC32Table, , FOUND=no)
dnl if test "$FOUND" = "no"; then
dnl AC_MSG_ERROR("libpar2 library not found in $LIBVAL.")
dnl fi
else
AC_DEFINE([DISABLE_GZIP],1,[Define to 1 to disable gzip-support])
fi
dnl
dnl Determine if CPU supports SIMD instructions
dnl
AC_MSG_CHECKING(whether to use SIMD-optimized routines)
USE_SIMD=no
case $host_cpu in
i?86|x86_64)
SSE2_CXXFLAGS="-msse2"
SSSE3_CXXFLAGS="-mssse3"
PCLMUL_CXXFLAGS="-msse4.1 -mpclmul"
USE_SIMD=yes
;;
arm*)
NEON_CXXFLAGS="-mfpu=neon"
ACLECRC_CXXFLAGS="-march=armv8-a+crc -fpermissive"
USE_SIMD=yes
;;
aarch64)
ACLECRC_CXXFLAGS="-march=armv8-a+crc -fpermissive"
USE_SIMD=yes
;;
esac
AC_MSG_RESULT($USE_SIMD)
AC_SUBST([SSE2_CXXFLAGS])
AC_SUBST([SSSE3_CXXFLAGS])
AC_SUBST([PCLMUL_CXXFLAGS])
AC_SUBST([NEON_CXXFLAGS])
AC_SUBST([ACLECRC_CXXFLAGS])
dnl
dnl Some Linux systems require an empty signal handler for SIGCHLD
dnl in order for exit codes to be correctly delivered to parent process.
dnl Some 32-Bit BSD systems however may not function properly if the handler is installed.
dnl The default behavior is to install the handler.
dnl
AC_MSG_CHECKING(whether to use an empty SIGCHLD handler)
AC_ARG_ENABLE(sigchld-handler,
[AS_HELP_STRING([--disable-sigchld-handler], [do not use sigchld-handler (the disabling may be neccessary on 32-Bit BSD)])],
[SIGCHLDHANDLER=$enableval],
[SIGCHLDHANDLER=yes])
AC_MSG_RESULT($SIGCHLDHANDLER)
if test "$SIGCHLDHANDLER" = "yes"; then
AC_DEFINE([SIGCHLD_HANDLER], 1, [Define to 1 to install an empty signal handler for SIGCHLD])
AC_DEFINE([DISABLE_PARCHECK],1,[Define to 1 to disable smart par-verification and restoration])
fi
@@ -600,103 +352,43 @@ dnl Debugging. Default: no
dnl
AC_MSG_CHECKING(whether to include all debugging code)
AC_ARG_ENABLE(debug,
[AS_HELP_STRING([--enable-debug], [enable debugging])],
[ --enable-debug enable debugging],
[ ENABLEDEBUG=$enableval ],
[ ENABLEDEBUG=no] )
AC_MSG_RESULT($ENABLEDEBUG)
if test "$ENABLEDEBUG" = "yes"; then
dnl
dnl Begin of debugging code
dnl
AC_DEFINE([DEBUG],1,Define to 1 to include debug-code)
dnl
dnl check for __FUNCTION__ or __func__ macro
dnl
AC_MSG_CHECKING(for macro returning current function name)
AC_TRY_COMPILE(
[#include <stdio.h>], [printf("%s\n", __FUNCTION__);],
AC_MSG_RESULT(__FUNCTION__)
FUNCTION_MACRO_NAME=__FUNCTION__,
AC_TRY_COMPILE([#include <stdio.h>], [printf("%s\n", __func__);],
AC_MSG_RESULT(__func__)
FUNCTION_MACRO_NAME=__func__,
AC_MSG_RESULT(none)))
if test "$FUNCTION_MACRO_NAME" != ""; then
AC_DEFINE_UNQUOTED(FUNCTION_MACRO_NAME, $FUNCTION_MACRO_NAME, [Define to the name of macro which returns the name of function being compiled])
AC_DEFINE([DEBUG],1,Define to 1 to include debug-code)
if test "$CC" = "gcc"; then
CXXFLAGS="-g -Wall"
else
CXXFLAGS=""
fi
fi
AC_MSG_RESULT($ENABLEDEBUG)
dnl
dnl variadic macros
dnl
AC_MSG_CHECKING(for variadic macros)
AC_TRY_COMPILE(
[ #define macro(...) macrofunc(__VA_ARGS__) ]
[ int macrofunc(int a, int b) { return a + b; } ],
[ int a=macro(1, 2); ],
AC_COMPILE_IFELSE([
#define macro(...) macrofunc(__VA_ARGS__)
int macrofunc(int a, int b) { return a + b; }
int test() { return macro(1, 2); }
],
AC_MSG_RESULT([yes])
AC_DEFINE([HAVE_VARIADIC_MACROS], 1, Define to 1 if variadic macros are supported),
AC_MSG_RESULT([no]))
dnl
dnl Backtracing on segmentation faults
dnl
AC_MSG_CHECKING(for backtrace)
AC_TRY_COMPILE(
[#include <execinfo.h>]
[#include <stdio.h>]
[#include <stdlib.h>],
[ void *array[100]; size_t size; char **strings; ]
[ size = backtrace(array, 100); ]
[ strings = backtrace_symbols(array, size); ],
FOUND=yes
AC_MSG_RESULT([[yes]])
AC_DEFINE([HAVE_BACKTRACE], 1, [Define to 1 to create stacktrace on segmentation faults]),
FOUND=no
AC_MSG_RESULT([[no]]))
dnl Substitute flags.
AC_SUBST(CFLAGS)
AC_SUBST(CPPFLAGS)
AC_SUBST(LDFLAGS)
AC_SUBST(CXXFLAGS)
AC_SUBST(TAR)
AC_SUBST(AR)
AC_SUBST(ADDSRCS)
dnl
dnl "rdynamic" linker flag
dnl
AC_MSG_CHECKING(for rdynamic linker flag)
old_LDFLAGS="$LDFLAGS"
LDFLAGS="$LDFLAGS -rdynamic"
AC_TRY_LINK([], [],
AC_MSG_RESULT([[yes]]),
AC_MSG_RESULT([[no]])
[LDFLAGS="$old_LDFLAGS"])
dnl
dnl End of debugging code
dnl
else
AC_DEFINE([NDEBUG],1,Define to 1 to exclude debug-code)
fi
dnl
dnl Enable test suite. Deafult: no.
dnl
AC_MSG_CHECKING(whether to enable unit and integration tests)
AC_ARG_ENABLE(tests,
[AS_HELP_STRING([--enable-tests], [enable unit and integration tests])],
[ ENABLETESTS=$enableval ],
[ ENABLETESTS=no] )
AC_MSG_RESULT($ENABLETESTS)
if test "$ENABLETESTS" = "yes"; then
AC_DEFINE([ENABLE_TESTS],1,[Define to 1 to enable unit and integration tests])
AM_CONDITIONAL(WITH_TESTS, true)
else
AM_CONDITIONAL(WITH_TESTS, false)
fi
AC_CONFIG_FILES([Makefile])
AC_OUTPUT

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,157 +0,0 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2017 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/>.
*/
#ifndef CONNECTION_H
#define CONNECTION_H
#include "NString.h"
#ifndef HAVE_GETADDRINFO
#ifndef HAVE_GETHOSTBYNAME_R
#include "Thread.h"
#endif
#endif
#ifndef DISABLE_TLS
#include "TlsSocket.h"
#endif
class Connection
{
public:
enum EStatus
{
csConnected,
csDisconnected,
csListening,
csCancelled,
csBroken
};
enum EIPVersion
{
ipAuto,
ipV4,
ipV6
};
Connection(const char* host, int port, bool tls);
Connection(SOCKET socket, bool tls);
virtual ~Connection();
static void Init();
static void Final();
virtual bool Connect();
virtual bool Disconnect();
bool Bind();
bool Send(const char* buffer, int size);
bool Recv(char* buffer, int size);
int TryRecv(char* buffer, int size);
char* ReadLine(char* buffer, int size, int* bytesRead);
void ReadBuffer(char** buffer, int *bufLen);
int WriteLine(const char* buffer);
std::unique_ptr<Connection> Accept();
void Cancel();
const char* GetHost() { return m_host; }
int GetPort() { return m_port; }
bool GetTls() { return m_tls; }
const char* GetCipher() { return m_cipher; }
void SetCipher(const char* cipher) { m_cipher = cipher; }
void SetTimeout(int timeout) { m_timeout = timeout; }
void SetIPVersion(EIPVersion ipVersion) { m_ipVersion = ipVersion; }
EStatus GetStatus() { return m_status; }
void SetSuppressErrors(bool suppressErrors);
bool GetSuppressErrors() { return m_suppressErrors; }
const char* GetRemoteAddr();
bool GetGracefull() { return m_gracefull; }
void SetGracefull(bool gracefull) { m_gracefull = gracefull; }
void SetForceClose(bool forceClose) { m_forceClose = forceClose; }
#ifndef DISABLE_TLS
bool StartTls(bool isClient, const char* certFile, const char* keyFile);
#endif
int FetchTotalBytesRead();
protected:
CString m_host;
int m_port;
bool m_tls;
EIPVersion m_ipVersion = ipAuto;
SOCKET m_socket = INVALID_SOCKET;
CString m_cipher;
CharBuffer m_readBuf;
int m_bufAvail = 0;
char* m_bufPtr = nullptr;
EStatus m_status = csDisconnected;
int m_timeout = 60;
bool m_suppressErrors = true;
BString<100> m_remoteAddr;
int m_totalBytesRead = 0;
bool m_gracefull = false;
bool m_forceClose = false;
struct SockAddr
{
int ai_family;
int ai_socktype;
int ai_protocol;
bool operator==(const SockAddr& rhs) const
{ return memcmp(this, &rhs, sizeof(SockAddr)) == 0; }
};
#ifndef DISABLE_TLS
class ConTlsSocket: public TlsSocket
{
public:
ConTlsSocket(SOCKET socket, bool isClient, const char* host,
const char* certFile, const char* keyFile, const char* cipher, Connection* owner) :
TlsSocket(socket, isClient, host, certFile, keyFile, cipher), m_owner(owner) {}
protected:
virtual void PrintError(const char* errMsg) { m_owner->PrintError(errMsg); }
private:
Connection* m_owner;
};
std::unique_ptr<ConTlsSocket> m_tlsSocket;
bool m_tlsError = false;
#endif
#ifndef HAVE_GETADDRINFO
#ifndef HAVE_GETHOSTBYNAME_R
static std::unique_ptr<Mutex> m_getHostByNameMutex;
#endif
#endif
void ReportError(const char* msgPrefix, const char* msgArg, bool printErrCode, int errCode = 0,
const char* errMsg = nullptr);
virtual void PrintError(const char* errMsg);
int GetLastNetworkError();
bool DoConnect();
bool DoDisconnect();
bool InitSocketOpts(SOCKET socket);
bool ConnectWithTimeout(void* address, int address_len);
#ifndef HAVE_GETADDRINFO
in_addr_t ResolveHostAddr(const char* host);
#endif
#ifndef DISABLE_TLS
int recv(SOCKET s, char* buf, int len, int flags);
int send(SOCKET s, const char* buf, int len, int flags);
void CloseTls();
#endif
};
#endif

View File

@@ -1,696 +0,0 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2008-2017 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"
#ifndef DISABLE_TLS
#include "TlsSocket.h"
#include "Thread.h"
#include "Log.h"
#include "Util.h"
#include "FileSystem.h"
CString TlsSocket::m_certStore;
#ifdef HAVE_LIBGNUTLS
#ifdef NEED_GCRYPT_LOCKING
/**
* Mutexes for gcryptlib
*/
std::vector<std::unique_ptr<Mutex>> g_GCryptLibMutexes;
static int gcry_mutex_init(void **priv)
{
g_GCryptLibMutexes.emplace_back(std::make_unique<Mutex>());
*priv = g_GCryptLibMutexes.back().get();
return 0;
}
static int gcry_mutex_destroy(void **lock)
{
Mutex* mutex = ((Mutex*)*lock);
g_GCryptLibMutexes.erase(std::find_if(g_GCryptLibMutexes.begin(), g_GCryptLibMutexes.end(),
[mutex](std::unique_ptr<Mutex>& itMutex)
{
return itMutex.get() == mutex;
}));
return 0;
}
static int gcry_mutex_lock(void **lock)
{
((Mutex*)*lock)->Lock();
return 0;
}
static int gcry_mutex_unlock(void **lock)
{
((Mutex*)*lock)->Unlock();
return 0;
}
static struct gcry_thread_cbs gcry_threads_Mutex =
{ GCRY_THREAD_OPTION_USER, nullptr,
gcry_mutex_init, gcry_mutex_destroy,
gcry_mutex_lock, gcry_mutex_unlock,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr
};
#endif /* NEED_GCRYPT_LOCKING */
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
#ifndef CRYPTO_set_locking_callback
#define NEED_CRYPTO_LOCKING
#endif
#ifdef NEED_CRYPTO_LOCKING
/**
* Mutexes for OpenSSL
*/
std::vector<std::unique_ptr<Mutex>> g_OpenSSLMutexes;
static void openssl_locking(int mode, int n, const char* file, int line)
{
Mutex* mutex = g_OpenSSLMutexes[n].get();
if (mode & CRYPTO_LOCK)
{
mutex->Lock();
}
else
{
mutex->Unlock();
}
}
static struct CRYPTO_dynlock_value* openssl_dynlock_create(const char *file, int line)
{
return (CRYPTO_dynlock_value*)new Mutex();
}
static void openssl_dynlock_destroy(struct CRYPTO_dynlock_value *l, const char *file, int line)
{
Mutex* mutex = (Mutex*)l;
delete mutex;
}
static void openssl_dynlock_lock(int mode, struct CRYPTO_dynlock_value *l, const char *file, int line)
{
Mutex* mutex = (Mutex*)l;
if (mode & CRYPTO_LOCK)
{
mutex->Lock();
}
else
{
mutex->Unlock();
}
}
#endif /* NEED_CRYPTO_LOCKING */
#endif /* HAVE_OPENSSL */
void TlsSocket::Init()
{
debug("Initializing TLS library");
#ifdef HAVE_LIBGNUTLS
int error_code;
#ifdef NEED_GCRYPT_LOCKING
error_code = gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_Mutex);
if (error_code != 0)
{
error("Could not initialize libcrypt");
return;
}
#endif /* NEED_GCRYPT_LOCKING */
error_code = gnutls_global_init();
if (error_code != 0)
{
error("Could not initialize libgnutls");
return;
}
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
#ifdef NEED_CRYPTO_LOCKING
for (int i = 0, num = CRYPTO_num_locks(); i < num; i++)
{
g_OpenSSLMutexes.emplace_back(std::make_unique<Mutex>());
}
CRYPTO_set_locking_callback(openssl_locking);
CRYPTO_set_dynlock_create_callback(openssl_dynlock_create);
CRYPTO_set_dynlock_destroy_callback(openssl_dynlock_destroy);
CRYPTO_set_dynlock_lock_callback(openssl_dynlock_lock);
#endif /* NEED_CRYPTO_LOCKING */
SSL_load_error_strings();
SSL_library_init();
OpenSSL_add_all_algorithms();
#endif /* HAVE_OPENSSL */
}
void TlsSocket::Final()
{
#ifdef HAVE_LIBGNUTLS
gnutls_global_deinit();
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
#ifndef LIBRESSL_VERSION_NUMBER
FIPS_mode_set(0);
#endif
#ifdef NEED_CRYPTO_LOCKING
CRYPTO_set_locking_callback(nullptr);
CRYPTO_set_id_callback(nullptr);
#endif
#if OPENSSL_VERSION_NUMBER < 0x10100000L
ERR_remove_state(0);
#endif
#if OPENSSL_VERSION_NUMBER >= 0x10002000L && ! defined (LIBRESSL_VERSION_NUMBER)
SSL_COMP_free_compression_methods();
#endif
//ENGINE_cleanup();
CONF_modules_free();
CONF_modules_unload(1);
#ifndef OPENSSL_NO_COMP
COMP_zlib_cleanup();
#endif
ERR_free_strings();
EVP_cleanup();
CRYPTO_cleanup_all_ex_data();
#endif /* HAVE_OPENSSL */
}
TlsSocket::~TlsSocket()
{
Close();
#ifdef HAVE_OPENSSL
#if OPENSSL_VERSION_NUMBER < 0x10100000L
ERR_remove_state(0);
#endif
#endif
}
void TlsSocket::ReportError(const char* errMsg, bool suppressable)
{
#ifdef HAVE_LIBGNUTLS
const char* errstr = gnutls_strerror(m_retCode);
if (suppressable && m_suppressErrors)
{
debug("%s: %s", errMsg, errstr);
}
else
{
PrintError(BString<1024>("%s: %s", errMsg, errstr));
}
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
int errcode = ERR_get_error();
do
{
char errstr[1024];
ERR_error_string_n(errcode, errstr, sizeof(errstr));
errstr[1024-1] = '\0';
if (suppressable && m_suppressErrors)
{
debug("%s: %s", errMsg, errstr);
}
else if (errcode != 0)
{
PrintError(BString<1024>("%s: %s", errMsg, errstr));
}
else
{
PrintError(errMsg);
}
errcode = ERR_get_error();
} while (errcode);
#endif /* HAVE_OPENSSL */
}
void TlsSocket::PrintError(const char* errMsg)
{
error("%s", errMsg);
}
bool TlsSocket::Start()
{
#ifdef HAVE_LIBGNUTLS
gnutls_certificate_credentials_t cred;
m_retCode = gnutls_certificate_allocate_credentials(&cred);
if (m_retCode != 0)
{
ReportError("Could not create TLS context", false);
return false;
}
m_context = cred;
if (m_certFile && m_keyFile)
{
m_retCode = gnutls_certificate_set_x509_key_file((gnutls_certificate_credentials_t)m_context,
m_certFile, m_keyFile, GNUTLS_X509_FMT_PEM);
if (m_retCode != 0)
{
ReportError("Could not load certificate or key file", false);
Close();
return false;
}
}
gnutls_session_t sess;
m_retCode = gnutls_init(&sess, m_isClient ? GNUTLS_CLIENT : GNUTLS_SERVER);
if (m_retCode != 0)
{
ReportError("Could not create TLS session", false);
Close();
return false;
}
m_session = sess;
m_initialized = true;
const char* priority = !m_cipher.Empty() ? m_cipher.Str() :
(m_certFile && m_keyFile ? "NORMAL:!VERS-SSL3.0" : "NORMAL");
m_retCode = gnutls_priority_set_direct((gnutls_session_t)m_session, priority, nullptr);
if (m_retCode != 0)
{
ReportError("Could not select cipher for TLS", false);
Close();
return false;
}
if (m_host)
{
m_retCode = gnutls_server_name_set((gnutls_session_t)m_session, GNUTLS_NAME_DNS, m_host, m_host.Length());
if (m_retCode != 0)
{
ReportError("Could not set hostname for TLS");
Close();
return false;
}
}
m_retCode = gnutls_credentials_set((gnutls_session_t)m_session, GNUTLS_CRD_CERTIFICATE,
(gnutls_certificate_credentials_t*)m_context);
if (m_retCode != 0)
{
ReportError("Could not initialize TLS session", false);
Close();
return false;
}
gnutls_transport_set_ptr((gnutls_session_t)m_session, (gnutls_transport_ptr_t)(size_t)m_socket);
m_retCode = gnutls_handshake((gnutls_session_t)m_session);
if (m_retCode != 0)
{
ReportError(BString<1024>("TLS handshake failed for %s", *m_host));
Close();
return false;
}
if (m_isClient && !m_certStore.Empty() && !ValidateCert())
{
Close();
return false;
}
m_connected = true;
return true;
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
m_context = SSL_CTX_new(SSLv23_method());
if (!m_context)
{
ReportError("Could not create TLS context", false);
return false;
}
if (m_certFile && m_keyFile)
{
if (SSL_CTX_use_certificate_chain_file((SSL_CTX*)m_context, m_certFile) != 1)
{
ReportError("Could not load certificate file", false);
Close();
return false;
}
if (SSL_CTX_use_PrivateKey_file((SSL_CTX*)m_context, m_keyFile, SSL_FILETYPE_PEM) != 1)
{
ReportError("Could not load key file", false);
Close();
return false;
}
if (!SSL_CTX_set_options((SSL_CTX*)m_context, SSL_OP_NO_SSLv3))
{
ReportError("Could not select minimum protocol version for TLS", false);
Close();
return false;
}
// For ECC certificates
EC_KEY* ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
if (!ecdh)
{
ReportError("Could not generate ecdh parameters for TLS", false);
Close();
return false;
}
if (!SSL_CTX_set_tmp_ecdh((SSL_CTX*)m_context, ecdh))
{
ReportError("Could not set ecdh parameters for TLS", false);
EC_KEY_free(ecdh);
Close();
return false;
}
EC_KEY_free(ecdh);
}
if (m_isClient && !m_certStore.Empty())
{
// Enable certificate validation
if (SSL_CTX_load_verify_locations((SSL_CTX*)m_context, m_certStore, nullptr) != 1)
{
ReportError("Could not set certificate store location", false);
Close();
return false;
}
SSL_CTX_set_verify((SSL_CTX*)m_context, SSL_VERIFY_PEER, nullptr);
}
m_session = SSL_new((SSL_CTX*)m_context);
if (!m_session)
{
ReportError("Could not create TLS session", false);
Close();
return false;
}
if (!m_cipher.Empty() && !SSL_set_cipher_list((SSL*)m_session, m_cipher))
{
ReportError("Could not select cipher for TLS", false);
Close();
return false;
}
if (m_isClient && m_host && !SSL_set_tlsext_host_name((SSL*)m_session, m_host))
{
ReportError("Could not set host name for TLS");
Close();
return false;
}
if (!SSL_set_fd((SSL*)m_session, (int)m_socket))
{
ReportError("Could not set the file descriptor for TLS");
Close();
return false;
}
int error_code = m_isClient ? SSL_connect((SSL*)m_session) : SSL_accept((SSL*)m_session);
if (error_code < 1)
{
long verifyRes = SSL_get_verify_result((SSL*)m_session);
if (verifyRes != X509_V_OK)
{
PrintError(BString<1024>("TLS certificate verification failed for %s: %s."
" For more info visit http://nzbget.net/certificate-verification",
*m_host, X509_verify_cert_error_string(verifyRes)));
}
else
{
ReportError(BString<1024>("TLS handshake failed for %s", *m_host));
}
Close();
return false;
}
if (m_isClient && !m_certStore.Empty() && !ValidateCert())
{
Close();
return false;
}
m_connected = true;
return true;
#endif /* HAVE_OPENSSL */
}
bool TlsSocket::ValidateCert()
{
#ifdef HAVE_LIBGNUTLS
#if GNUTLS_VERSION_NUMBER >= 0x030104
#if GNUTLS_VERSION_NUMBER >= 0x030306
if (FileSystem::DirectoryExists(m_certStore))
{
if (gnutls_certificate_set_x509_trust_dir((gnutls_certificate_credentials_t)m_context, m_certStore, GNUTLS_X509_FMT_PEM) < 0)
{
ReportError("Could not set certificate store location");
return false;
}
}
else
#endif
{
if (gnutls_certificate_set_x509_trust_file((gnutls_certificate_credentials_t)m_context, m_certStore, GNUTLS_X509_FMT_PEM) < 0)
{
ReportError("Could not set certificate store location");
return false;
}
}
unsigned int status = 0;
if (gnutls_certificate_verify_peers3((gnutls_session_t)m_session, m_host, &status) != 0 ||
gnutls_certificate_type_get((gnutls_session_t)m_session) != GNUTLS_CRT_X509)
{
ReportError("Could not verify TLS certificate");
return false;
}
if (status != 0)
{
if (status & GNUTLS_CERT_UNEXPECTED_OWNER)
{
// Extracting hostname from the certificate
unsigned int cert_list_size = 0;
const gnutls_datum_t* cert_list = gnutls_certificate_get_peers((gnutls_session_t)m_session, &cert_list_size);
if (cert_list_size > 0)
{
gnutls_x509_crt_t cert;
gnutls_x509_crt_init(&cert);
gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER);
char dn[256];
size_t size = sizeof(dn);
if (gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, dn, &size) == 0)
{
PrintError(BString<1024>("TLS certificate verification failed for %s: certificate hostname mismatch (%s)."
" For more info visit http://nzbget.net/certificate-verification", *m_host, dn));
gnutls_x509_crt_deinit(cert);
return false;
}
gnutls_x509_crt_deinit(cert);
}
}
gnutls_datum_t msgdata;
if (gnutls_certificate_verification_status_print(status, GNUTLS_CRT_X509, &msgdata, 0) == 0)
{
PrintError(BString<1024>("TLS certificate verification failed for %s: %s."
" For more info visit http://nzbget.net/certificate-verification", *m_host, msgdata.data));
gnutls_free(&msgdata);
}
else
{
ReportError(BString<1024>("TLS certificate verification failed for %s."
" For more info visit http://nzbget.net/certificate-verification", *m_host));
}
return false;
}
#endif
return true;
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
// verify a server certificate was presented during the negotiation
X509* cert = SSL_get_peer_certificate((SSL*)m_session);
if (!cert)
{
PrintError(BString<1024>("TLS certificate verification failed for %s: no certificate provided by server."
" For more info visit http://nzbget.net/certificate-verification", *m_host));
return false;
}
#ifdef HAVE_X509_CHECK_HOST
// hostname verification
if (!m_host.Empty() && X509_check_host(cert, m_host, m_host.Length(), 0, nullptr) != 1)
{
const unsigned char* certHost = nullptr;
// Find the position of the CN field in the Subject field of the certificate
int common_name_loc = X509_NAME_get_index_by_NID(X509_get_subject_name(cert), NID_commonName, -1);
if (common_name_loc >= 0)
{
// Extract the CN field
X509_NAME_ENTRY* common_name_entry = X509_NAME_get_entry(X509_get_subject_name(cert), common_name_loc);
if (common_name_entry != nullptr)
{
// Convert the CN field to a C string
ASN1_STRING* common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry);
if (common_name_asn1 != nullptr)
{
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
certHost = ASN1_STRING_get0_data(common_name_asn1);
#else
certHost = ASN1_STRING_data(common_name_asn1);
#endif
}
}
}
PrintError(BString<1024>("TLS certificate verification failed for %s: certificate hostname mismatch (%s)."
" For more info visit http://nzbget.net/certificate-verification", *m_host, certHost));
X509_free(cert);
return false;
}
#endif
X509_free(cert);
return true;
#endif /* HAVE_OPENSSL */
}
void TlsSocket::Close()
{
if (m_session)
{
#ifdef HAVE_LIBGNUTLS
if (m_connected)
{
gnutls_bye((gnutls_session_t)m_session, GNUTLS_SHUT_WR);
}
if (m_initialized)
{
gnutls_deinit((gnutls_session_t)m_session);
}
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
if (m_connected)
{
SSL_shutdown((SSL*)m_session);
}
SSL_free((SSL*)m_session);
#endif /* HAVE_OPENSSL */
m_session = nullptr;
}
if (m_context)
{
#ifdef HAVE_LIBGNUTLS
gnutls_certificate_free_credentials((gnutls_certificate_credentials_t)m_context);
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
SSL_CTX_free((SSL_CTX*)m_context);
#endif /* HAVE_OPENSSL */
m_context = nullptr;
}
}
int TlsSocket::Send(const char* buffer, int size)
{
#ifdef HAVE_LIBGNUTLS
m_retCode = gnutls_record_send((gnutls_session_t)m_session, buffer, size);
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
m_retCode = SSL_write((SSL*)m_session, buffer, size);
#endif /* HAVE_OPENSSL */
if (m_retCode < 0)
{
#ifdef HAVE_OPENSSL
if (ERR_peek_error() == 0)
{
ReportError("Could not write to TLS-Socket: Connection closed by remote host");
}
else
#endif /* HAVE_OPENSSL */
ReportError("Could not write to TLS-Socket");
return -1;
}
return m_retCode;
}
int TlsSocket::Recv(char* buffer, int size)
{
#ifdef HAVE_LIBGNUTLS
m_retCode = gnutls_record_recv((gnutls_session_t)m_session, buffer, size);
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
m_retCode = SSL_read((SSL*)m_session, buffer, size);
#endif /* HAVE_OPENSSL */
if (m_retCode < 0)
{
#ifdef HAVE_OPENSSL
if (ERR_peek_error() == 0)
{
ReportError("Could not read from TLS-Socket: Connection closed by remote host");
}
else
#endif /* HAVE_OPENSSL */
{
ReportError("Could not read from TLS-Socket");
}
return -1;
}
return m_retCode;
}
#endif

View File

@@ -1,69 +0,0 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2008-2016 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/>.
*/
#ifndef TLSSOCKET_H
#define TLSSOCKET_H
#ifndef DISABLE_TLS
#include "NString.h"
class TlsSocket
{
public:
TlsSocket(SOCKET socket, bool isClient, const char* host,
const char* certFile, const char* keyFile, const char* cipher) :
m_socket(socket), m_isClient(isClient), m_host(host),
m_certFile(certFile), m_keyFile(keyFile), m_cipher(cipher) {}
virtual ~TlsSocket();
static void Init();
static void InitOptions(const char* certStore) { m_certStore = certStore; }
static void Final();
bool Start();
void Close();
int Send(const char* buffer, int size);
int Recv(char* buffer, int size);
void SetSuppressErrors(bool suppressErrors) { m_suppressErrors = suppressErrors; }
protected:
virtual void PrintError(const char* errMsg);
private:
SOCKET m_socket;
bool m_isClient;
CString m_host;
CString m_certFile;
CString m_keyFile;
CString m_cipher;
bool m_suppressErrors = false;
bool m_initialized = false;
bool m_connected = false;
int m_retCode;
static CString m_certStore;
// using "void*" to prevent the including of GnuTLS/OpenSSL header files into TlsSocket.h
void* m_context = nullptr;
void* m_session = nullptr;
void ReportError(const char* errMsg, bool suppressable = true);
bool ValidateCert();
};
#endif
#endif

View File

@@ -1,666 +0,0 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2012-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 "WebDownloader.h"
#include "Log.h"
#include "Options.h"
#include "WorkState.h"
#include "Util.h"
#include "FileSystem.h"
WebDownloader::WebDownloader()
{
debug("Creating WebDownloader");
SetLastUpdateTimeNow();
}
void WebDownloader::SetUrl(const char* url)
{
m_url = WebUtil::UrlEncode(url);
}
void WebDownloader::SetStatus(EStatus status)
{
m_status = status;
Notify(nullptr);
}
void WebDownloader::SetLastUpdateTimeNow()
{
m_lastUpdateTime = Util::CurrentTime();
}
void WebDownloader::Run()
{
debug("Entering WebDownloader-loop");
SetStatus(adRunning);
int remainedDownloadRetries = g_Options->GetUrlRetries() > 0 ? g_Options->GetUrlRetries() : 1;
int remainedConnectRetries = remainedDownloadRetries > 10 ? remainedDownloadRetries : 10;
if (!m_retry)
{
remainedDownloadRetries = 1;
remainedConnectRetries = 1;
}
EStatus Status = adFailed;
while (!IsStopped() && remainedDownloadRetries > 0 && remainedConnectRetries > 0)
{
SetLastUpdateTimeNow();
Status = DownloadWithRedirects(5);
if ((((Status == adFailed) && (remainedDownloadRetries > 1)) ||
((Status == adConnectError) && (remainedConnectRetries > 1)))
&& !IsStopped() && !(!m_force && g_WorkState->GetPauseDownload()))
{
detail("Waiting %i sec to retry", g_Options->GetUrlInterval());
int msec = 0;
while (!IsStopped() && (msec < g_Options->GetUrlInterval() * 1000) &&
!(!m_force && g_WorkState->GetPauseDownload()))
{
Util::Sleep(100);
msec += 100;
}
}
if (IsStopped() || (!m_force && g_WorkState->GetPauseDownload()))
{
Status = adRetry;
break;
}
if (Status == adFinished || Status == adFatalError || Status == adNotFound)
{
break;
}
if (Status != adConnectError)
{
remainedDownloadRetries--;
}
else
{
remainedConnectRetries--;
}
}
if (Status != adFinished && Status != adRetry)
{
Status = adFailed;
}
if (Status == adFailed)
{
if (IsStopped())
{
detail("Download %s cancelled", *m_infoName);
}
else
{
error("Download %s failed", *m_infoName);
}
}
if (Status == adFinished)
{
detail("Download %s completed", *m_infoName);
}
SetStatus(Status);
debug("Exiting WebDownloader-loop");
}
WebDownloader::EStatus WebDownloader::Download()
{
EStatus Status = adRunning;
URL url(m_url);
Status = CreateConnection(&url);
if (Status != adRunning)
{
return Status;
}
m_connection->SetTimeout(g_Options->GetUrlTimeout());
m_connection->SetSuppressErrors(false);
// connection
bool connected = m_connection->Connect();
if (!connected || IsStopped())
{
FreeConnection();
return adConnectError;
}
// Okay, we got a Connection. Now start downloading.
detail("Downloading %s", *m_infoName);
SendHeaders(&url);
Status = DownloadHeaders();
if (Status == adRunning)
{
Status = DownloadBody();
}
if (IsStopped())
{
Status = adFailed;
}
FreeConnection();
if (Status != adFinished)
{
// Download failed, delete broken output file
FileSystem::DeleteFile(m_outputFilename);
}
return Status;
}
WebDownloader::EStatus WebDownloader::DownloadWithRedirects(int maxRedirects)
{
// do sync download, following redirects
EStatus status = adRedirect;
while (status == adRedirect && maxRedirects >= 0)
{
maxRedirects--;
status = Download();
}
if (status == adRedirect && maxRedirects < 0)
{
warn("Too many redirects for %s", *m_infoName);
status = adFailed;
}
return status;
}
WebDownloader::EStatus WebDownloader::CreateConnection(URL *url)
{
if (!url->IsValid())
{
error("URL is not valid: %s", url->GetAddress());
return adFatalError;
}
int port = url->GetPort();
if (port == 0 && !strcasecmp(url->GetProtocol(), "http"))
{
port = 80;
}
if (port == 0 && !strcasecmp(url->GetProtocol(), "https"))
{
port = 443;
}
if (strcasecmp(url->GetProtocol(), "http") && strcasecmp(url->GetProtocol(), "https"))
{
error("Unsupported protocol in URL: %s", url->GetAddress());
return adFatalError;
}
#ifdef DISABLE_TLS
if (!strcasecmp(url->GetProtocol(), "https"))
{
error("Program was compiled without TLS/SSL-support. Cannot download using https protocol. URL: %s", url->GetAddress());
return adFatalError;
}
#endif
bool tls = !strcasecmp(url->GetProtocol(), "https");
m_connection = std::make_unique<Connection>(url->GetHost(), port, tls);
return adRunning;
}
void WebDownloader::SendHeaders(URL *url)
{
// retrieve file
m_connection->WriteLine(BString<1024>("GET %s HTTP/1.0\r\n", url->GetResource()));
m_connection->WriteLine(BString<1024>("User-Agent: nzbget/%s\r\n", Util::VersionRevision()));
if ((!strcasecmp(url->GetProtocol(), "http") && (url->GetPort() == 80 || url->GetPort() == 0)) ||
(!strcasecmp(url->GetProtocol(), "https") && (url->GetPort() == 443 || url->GetPort() == 0)))
{
m_connection->WriteLine(BString<1024>("Host: %s\r\n", url->GetHost()));
}
else
{
m_connection->WriteLine(BString<1024>("Host: %s:%i\r\n", url->GetHost(), url->GetPort()));
}
m_connection->WriteLine("Accept: */*\r\n");
#ifndef DISABLE_GZIP
m_connection->WriteLine("Accept-Encoding: gzip\r\n");
#endif
m_connection->WriteLine("Connection: close\r\n");
m_connection->WriteLine("\r\n");
}
WebDownloader::EStatus WebDownloader::DownloadHeaders()
{
EStatus Status = adRunning;
m_confirmedLength = false;
CharBuffer lineBuf(1024*10);
m_contentLen = -1;
bool firstLine = true;
m_gzip = false;
m_redirecting = false;
m_redirected = false;
// Headers
while (!IsStopped())
{
SetLastUpdateTimeNow();
int len = 0;
char* line = m_connection->ReadLine(lineBuf, lineBuf.Size(), &len);
if (firstLine)
{
Status = CheckResponse(lineBuf);
if (Status != adRunning)
{
break;
}
firstLine = false;
}
// Have we encountered a timeout?
if (!line)
{
if (!IsStopped())
{
warn("URL %s failed: Unexpected end of file", *m_infoName);
}
Status = adFailed;
break;
}
debug("Header: %s", line);
// detect body of response
if (*line == '\r' || *line == '\n')
{
break;
}
Util::TrimRight(line);
ProcessHeader(line);
if (m_redirected)
{
Status = adRedirect;
break;
}
}
return Status;
}
WebDownloader::EStatus WebDownloader::DownloadBody()
{
EStatus Status = adRunning;
m_outFile.Close();
bool end = false;
CharBuffer lineBuf(1024*10);
int writtenLen = 0;
#ifndef DISABLE_GZIP
m_gUnzipStream.reset();
if (m_gzip)
{
m_gUnzipStream = std::make_unique<GUnzipStream>(1024*10);
}
#endif
// Body
while (!IsStopped())
{
SetLastUpdateTimeNow();
char* buffer;
int len;
m_connection->ReadBuffer(&buffer, &len);
if (len == 0)
{
len = m_connection->TryRecv(lineBuf, lineBuf.Size());
buffer = lineBuf;
}
// Connection closed or timeout?
if (len <= 0)
{
if (len == 0 && m_contentLen == -1 && writtenLen > 0)
{
end = true;
break;
}
if (!IsStopped())
{
warn("URL %s failed: Unexpected end of file", *m_infoName);
}
Status = adFailed;
break;
}
// write to output file
if (!Write(buffer, len))
{
Status = adFatalError;
break;
}
writtenLen += len;
//detect end of file
if (writtenLen == m_contentLen || (m_contentLen == -1 && m_gzip && m_confirmedLength))
{
end = true;
break;
}
}
#ifndef DISABLE_GZIP
m_gUnzipStream.reset();
#endif
m_outFile.Close();
if (!end && Status == adRunning && !IsStopped())
{
warn("URL %s failed: file incomplete", *m_infoName);
Status = adFailed;
}
if (end)
{
Status = adFinished;
}
return Status;
}
WebDownloader::EStatus WebDownloader::CheckResponse(const char* response)
{
if (!response)
{
if (!IsStopped())
{
warn("URL %s: Connection closed by remote host", *m_infoName);
}
return adConnectError;
}
const char* hTTPResponse = strchr(response, ' ');
if (strncmp(response, "HTTP", 4) || !hTTPResponse)
{
warn("URL %s failed: %s", *m_infoName, response);
return adFailed;
}
hTTPResponse++;
if (!strncmp(hTTPResponse, "400", 3) || !strncmp(hTTPResponse, "499", 3))
{
warn("URL %s failed: %s", *m_infoName, hTTPResponse);
return adConnectError;
}
else if (!strncmp(hTTPResponse, "404", 3))
{
warn("URL %s failed: %s", *m_infoName, hTTPResponse);
return adNotFound;
}
else if (!strncmp(hTTPResponse, "301", 3) || !strncmp(hTTPResponse, "302", 3) ||
!strncmp(hTTPResponse, "303", 3) || !strncmp(hTTPResponse, "307", 3) ||
!strncmp(hTTPResponse, "308", 3))
{
m_redirecting = true;
return adRunning;
}
else if (!strncmp(hTTPResponse, "200", 3))
{
// OK
return adRunning;
}
else
{
// unknown error, no special handling
warn("URL %s failed: %s", *m_infoName, response);
return adFailed;
}
}
void WebDownloader::ProcessHeader(const char* line)
{
if (!strncasecmp(line, "Content-Length: ", 16))
{
m_contentLen = atoi(line + 16);
m_confirmedLength = true;
}
else if (!strncasecmp(line, "Content-Encoding: gzip", 22))
{
m_gzip = true;
}
else if (!strncasecmp(line, "Content-Disposition: ", 21))
{
ParseFilename(line);
}
else if (m_redirecting && !strncasecmp(line, "Location: ", 10))
{
ParseRedirect(line + 10);
m_redirected = true;
}
}
void WebDownloader::ParseFilename(const char* contentDisposition)
{
// Examples:
// Content-Disposition: attachment; filename="fname.ext"
// Content-Disposition: attachement;filename=fname.ext
// Content-Disposition: attachement;filename=fname.ext;
const char *p = strstr(contentDisposition, "filename");
if (!p)
{
return;
}
p = strchr(p, '=');
if (!p)
{
return;
}
p++;
while (*p == ' ') p++;
BString<1024> fname = p;
char *pe = fname + strlen(fname) - 1;
while ((*pe == ' ' || *pe == '\n' || *pe == '\r' || *pe == ';') && pe > fname) {
*pe = '\0';
pe--;
}
WebUtil::HttpUnquote(fname);
m_originalFilename = FileSystem::BaseFileName(fname);
debug("OriginalFilename: %s", *m_originalFilename);
}
void WebDownloader::ParseRedirect(const char* location)
{
const char* newLocation = location;
BString<1024> urlBuf;
URL newUrl(newLocation);
if (!newUrl.IsValid())
{
// redirect within host
BString<1024> resource;
URL oldUrl(m_url);
if (*location == '/')
{
// absolute path within host
resource = location;
}
else
{
// relative path within host
resource = oldUrl.GetResource();
char* p = strchr(resource, '?');
if (p)
{
*p = '\0';
}
p = strrchr(resource, '/');
if (p)
{
p[1] = '\0';
}
resource.Append(location);
}
if (oldUrl.GetPort() > 0)
{
urlBuf.Format("%s://%s:%i%s", oldUrl.GetProtocol(), oldUrl.GetHost(), oldUrl.GetPort(), *resource);
}
else
{
urlBuf.Format("%s://%s%s", oldUrl.GetProtocol(), oldUrl.GetHost(), *resource);
}
newLocation = urlBuf;
}
detail("URL %s redirected to %s", *m_url, newLocation);
SetUrl(newLocation);
}
bool WebDownloader::Write(void* buffer, int len)
{
if (!m_outFile.Active() && !PrepareFile())
{
return false;
}
#ifndef DISABLE_GZIP
if (m_gzip)
{
m_gUnzipStream->Write(buffer, len);
const void *outBuf;
int outLen = 1;
while (outLen > 0)
{
GUnzipStream::EStatus gZStatus = m_gUnzipStream->Read(&outBuf, &outLen);
if (gZStatus == GUnzipStream::zlError)
{
error("URL %s: GUnzip failed", *m_infoName);
return false;
}
if (outLen > 0 && m_outFile.Write(outBuf, outLen) <= 0)
{
return false;
}
if (gZStatus == GUnzipStream::zlFinished)
{
m_confirmedLength = true;
return true;
}
}
return true;
}
else
#endif
return m_outFile.Write(buffer, len) > 0;
}
bool WebDownloader::PrepareFile()
{
// prepare file for writing
const char* filename = m_outputFilename;
if (!m_outFile.Open(filename, DiskFile::omWrite))
{
error("Could not %s file %s", "create", filename);
return false;
}
if (g_Options->GetWriteBuffer() > 0)
{
m_outFile.SetWriteBuffer(g_Options->GetWriteBuffer() * 1024);
}
return true;
}
void WebDownloader::LogDebugInfo()
{
info(" Web-Download: status=%i, LastUpdateTime=%s, filename=%s", m_status,
*Util::FormatTime(m_lastUpdateTime), FileSystem::BaseFileName(m_outputFilename));
}
void WebDownloader::Stop()
{
debug("Trying to stop WebDownloader");
Thread::Stop();
Guard guard(m_connectionMutex);
if (m_connection)
{
m_connection->SetSuppressErrors(true);
m_connection->Cancel();
}
debug("WebDownloader stopped successfully");
}
void WebDownloader::FreeConnection()
{
if (m_connection)
{
debug("Releasing connection");
Guard guard(m_connectionMutex);
if (m_connection->GetStatus() == Connection::csCancelled)
{
m_connection->Disconnect();
}
m_connection.reset();
}
}

View File

@@ -1,104 +0,0 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2012-2016 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/>.
*/
#ifndef WEBDOWNLOADER_H
#define WEBDOWNLOADER_H
#include "NString.h"
#include "Observer.h"
#include "Thread.h"
#include "Connection.h"
#include "FileSystem.h"
#include "Util.h"
class WebDownloader : public Thread, public Subject
{
public:
enum EStatus
{
adUndefined,
adRunning,
adFinished,
adFailed,
adRetry,
adNotFound,
adRedirect,
adConnectError,
adFatalError
};
WebDownloader();
EStatus GetStatus() { return m_status; }
virtual void Run();
virtual void Stop();
EStatus Download();
EStatus DownloadWithRedirects(int maxRedirects);
void SetInfoName(const char* infoName) { m_infoName = infoName; }
const char* GetInfoName() { return m_infoName; }
void SetUrl(const char* url);
const char* GetOutputFilename() { return m_outputFilename; }
void SetOutputFilename(const char* outputFilename) { m_outputFilename = outputFilename; }
time_t GetLastUpdateTime() { return m_lastUpdateTime; }
void SetLastUpdateTimeNow();
bool GetConfirmedLength() { return m_confirmedLength; }
const char* GetOriginalFilename() { return m_originalFilename; }
void SetForce(bool force) { m_force = force; }
void SetRetry(bool retry) { m_retry = retry; }
void LogDebugInfo();
protected:
virtual void ProcessHeader(const char* line);
private:
CString m_url;
CString m_outputFilename;
std::unique_ptr<Connection> m_connection;
Mutex m_connectionMutex;
EStatus m_status = adUndefined;
time_t m_lastUpdateTime;
CString m_infoName;
DiskFile m_outFile;
int m_contentLen;
bool m_confirmedLength = false;
CString m_originalFilename;
bool m_force = false;
bool m_redirecting;
bool m_redirected;
bool m_gzip;
bool m_retry = true;
#ifndef DISABLE_GZIP
std::unique_ptr<GUnzipStream> m_gUnzipStream;
#endif
void SetStatus(EStatus status);
bool Write(void* buffer, int len);
bool PrepareFile();
void FreeConnection();
EStatus CheckResponse(const char* response);
EStatus CreateConnection(URL *url);
void ParseFilename(const char* contentDisposition);
void SendHeaders(URL *url);
EStatus DownloadHeaders();
EStatus DownloadBody();
void ParseRedirect(const char* location);
};
#endif

View File

@@ -1,131 +0,0 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2017 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 "CommandScript.h"
#include "Options.h"
#include "Log.h"
#include "Util.h"
#include "FileSystem.h"
static const int COMMANDPROCESS_SUCCESS = 93;
static const int COMMANDPROCESS_ERROR = 94;
bool CommandScriptController::StartScript(const char* scriptName, const char* command,
std::unique_ptr<Options::OptEntries> modifiedOptions)
{
CommandScriptController* scriptController = new CommandScriptController();
scriptController->m_script = scriptName;
scriptController->m_command = command;
scriptController->m_logId = g_CommandScriptLog->Reset();
scriptController->m_modifiedOptions = std::move(modifiedOptions);
scriptController->SetAutoDestroy(true);
scriptController->Start();
for (ScriptConfig::Script& script : g_ScriptConfig->GetScripts())
{
if (FileSystem::SameFilename(scriptName, script.GetName()))
{
return true;
}
}
return false;
}
void CommandScriptController::Run()
{
ExecuteScriptList(m_script);
}
void CommandScriptController::ExecuteScript(ScriptConfig::Script* script)
{
PrintMessage(Message::mkInfo, "Executing script %s with command %s", script->GetName(), *m_command);
SetArgs({script->GetLocation()});
BString<1024> infoName("script %s with command %s", script->GetName(), *m_command);
SetInfoName(infoName);
SetLogPrefix(script->GetDisplayName());
PrepareParams(script->GetName());
int exitCode = Execute();
infoName[0] = 'S'; // uppercase
SetLogPrefix(nullptr);
switch (exitCode)
{
case COMMANDPROCESS_SUCCESS:
PrintMessage(Message::mkInfo, "%s successful", *infoName);
break;
case COMMANDPROCESS_ERROR:
case -1: // Execute() returns -1 if the process could not be started (file not found or other problem)
PrintMessage(Message::mkError, "%s failed", *infoName);
break;
default:
PrintMessage(Message::mkError, "%s failed (terminated with unknown status)", *infoName);
break;
}
}
void CommandScriptController::PrepareParams(const char* scriptName)
{
ResetEnv();
SetEnvVar("NZBCP_COMMAND", m_command);
PrepareEnvScript(nullptr, scriptName);
}
const char* CommandScriptController::GetOptValue(const char* name, const char* value)
{
Options::OptEntry* entry = m_modifiedOptions->FindOption(name);
return entry ? entry->GetValue() : value;
}
void CommandScriptController::AddMessage(Message::EKind kind, const char * text)
{
NzbScriptController::AddMessage(kind, text);
g_CommandScriptLog->AddMessage(m_logId, kind, text);
}
int CommandScriptLog::Reset()
{
Guard guard(m_logMutex);
m_messages.clear();
return ++m_idScriptGen;
}
void CommandScriptLog::AddMessage(int scriptId, Message::EKind kind, const char * text)
{
Guard guard(m_logMutex);
// save only messages from the last started script
if (scriptId == m_idScriptGen)
{
m_messages.emplace_back(++m_idMessageGen, kind, Util::CurrentTime(), text);
}
}

View File

@@ -1,63 +0,0 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2017 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/>.
*/
#ifndef COMMANDSCRIPT_H
#define COMMANDSCRIPT_H
#include "NzbScript.h"
#include "Log.h"
class CommandScriptController : public Thread, public NzbScriptController
{
public:
virtual void Run();
static bool StartScript(const char* scriptName, const char* command, std::unique_ptr<Options::OptEntries> modifiedOptions);
protected:
virtual void ExecuteScript(ScriptConfig::Script* script);
virtual void AddMessage(Message::EKind kind, const char* text);
virtual const char* GetOptValue(const char* name, const char* value);
private:
CString m_script;
CString m_command;
int m_logId;
std::unique_ptr<Options::OptEntries> m_modifiedOptions;
void PrepareParams(const char* scriptName);
};
class CommandScriptLog
{
public:
GuardedMessageList GuardMessages() { return GuardedMessageList(&m_messages, &m_logMutex); }
int Reset();
void AddMessage(int scriptId, Message::EKind kind, const char* text);
private:
MessageList m_messages;
Mutex m_logMutex;
int m_idMessageGen;
int m_idScriptGen;
};
extern CommandScriptLog* g_CommandScriptLog;
#endif

View File

@@ -1,80 +0,0 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2015-2016 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 "FeedScript.h"
#include "Options.h"
#include "Log.h"
#include "Util.h"
static const int FEED_SUCCESS = 93;
void FeedScriptController::ExecuteScripts(const char* feedScript, const char* feedFile, int feedId, bool* success)
{
FeedScriptController scriptController;
scriptController.m_feedFile = feedFile;
scriptController.m_feedId = feedId;
scriptController.ExecuteScriptList(feedScript);
if (success)
{
*success = scriptController.m_success;
}
}
void FeedScriptController::ExecuteScript(ScriptConfig::Script* script)
{
if (!script->GetFeedScript())
{
return;
}
PrintMessage(Message::mkInfo, "Executing feed-script %s for Feed%i", script->GetName(), m_feedId);
SetArgs({script->GetLocation()});
BString<1024> infoName("feed-script %s for Feed%i", script->GetName(), m_feedId);
SetInfoName(infoName);
SetLogPrefix(script->GetDisplayName());
PrepareParams(script->GetName());
int exitCode = Execute();
if (exitCode != FEED_SUCCESS)
{
infoName[0] = 'F'; // uppercase
PrintMessage(Message::mkError, "%s failed", *infoName);
m_success = false;
}
SetLogPrefix(nullptr);
}
void FeedScriptController::PrepareParams(const char* scriptName)
{
ResetEnv();
SetEnvVar("NZBFP_FILENAME", m_feedFile);
SetIntEnvVar("NZBFP_FEEDID", m_feedId);
PrepareEnvScript(nullptr, scriptName);
}

View File

@@ -1,42 +0,0 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2015-2016 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/>.
*/
#ifndef FEEDSCRIPT_H
#define FEEDSCRIPT_H
#include "NzbScript.h"
class FeedScriptController : public NzbScriptController
{
public:
static void ExecuteScripts(const char* feedScript, const char* feedFile, int feedId, bool* success);
protected:
virtual void ExecuteScript(ScriptConfig::Script* script);
private:
const char* m_feedFile;
int m_feedId;
bool m_success = true;
void PrepareParams(const char* scriptName);
};
#endif

View File

@@ -1,86 +0,0 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2016 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 "NzbScript.h"
#include "Options.h"
#include "Log.h"
#include "FileSystem.h"
/**
* If szStripPrefix is not nullptr, only pp-parameters, whose names start with the prefix
* are processed. The prefix is then stripped from the names.
* If szStripPrefix is nullptr, all pp-parameters are processed; without stripping.
*/
void NzbScriptController::PrepareEnvParameters(NzbParameterList* parameters, const char* stripPrefix)
{
int prefixLen = stripPrefix ? strlen(stripPrefix) : 0;
for (NzbParameter& parameter : parameters)
{
const char* value = parameter.GetValue();
if (stripPrefix && !strncmp(parameter.GetName(), stripPrefix, prefixLen) && (int)strlen(parameter.GetName()) > prefixLen)
{
SetEnvVarSpecial("NZBPR", parameter.GetName() + prefixLen, value);
}
else if (!stripPrefix)
{
SetEnvVarSpecial("NZBPR", parameter.GetName(), value);
}
}
}
void NzbScriptController::PrepareEnvScript(NzbParameterList* parameters, const char* scriptName)
{
if (parameters)
{
PrepareEnvParameters(parameters, nullptr);
}
BString<1024> paramPrefix("%s:", scriptName);
if (parameters)
{
PrepareEnvParameters(parameters, paramPrefix);
}
PrepareEnvOptions(paramPrefix);
}
void NzbScriptController::ExecuteScriptList(const char* scriptList)
{
for (ScriptConfig::Script& script : g_ScriptConfig->GetScripts())
{
if (scriptList && *scriptList)
{
// split szScriptList into tokens
Tokenizer tok(scriptList, ",;");
while (const char* scriptName = tok.Next())
{
if (FileSystem::SameFilename(scriptName, script.GetName()))
{
ExecuteScript(&script);
break;
}
}
}
}
}

View File

@@ -1,37 +0,0 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2016 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/>.
*/
#ifndef NZBSCRIPT_H
#define NZBSCRIPT_H
#include "Script.h"
#include "DownloadInfo.h"
#include "ScriptConfig.h"
class NzbScriptController : public ScriptController
{
protected:
void PrepareEnvParameters(NzbParameterList* parameters, const char* stripPrefix);
void PrepareEnvScript(NzbParameterList* parameters, const char* scriptName);
void ExecuteScriptList(const char* scriptList);
virtual void ExecuteScript(ScriptConfig::Script* script) = 0;
};
#endif

View File

@@ -1,309 +0,0 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-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 "PostScript.h"
#include "Log.h"
#include "Util.h"
#include "Options.h"
#include "WorkState.h"
static const int POSTPROCESS_PARCHECK = 92;
static const int POSTPROCESS_SUCCESS = 93;
static const int POSTPROCESS_ERROR = 94;
static const int POSTPROCESS_NONE = 95;
void PostScriptController::StartJob(PostInfo* postInfo)
{
PostScriptController* scriptController = new PostScriptController();
scriptController->m_postInfo = postInfo;
scriptController->SetAutoDestroy(false);
scriptController->m_prefixLen = 0;
postInfo->SetPostThread(scriptController);
scriptController->Start();
}
void PostScriptController::Run()
{
StringBuilder scriptCommaList;
{
GuardedDownloadQueue guard = DownloadQueue::Guard();
for (NzbParameter& parameter : m_postInfo->GetNzbInfo()->GetParameters())
{
const char* varname = parameter.GetName();
if (strlen(varname) > 0 && varname[0] != '*' && varname[strlen(varname) - 1] == ':' &&
(!strcasecmp(parameter.GetValue(), "yes") || !strcasecmp(parameter.GetValue(), "on") || !strcasecmp(parameter.GetValue(), "1")))
{
CString scriptName(varname);
scriptName[strlen(scriptName) - 1] = '\0'; // remove trailing ':'
scriptCommaList.Append(scriptName);
scriptCommaList.Append(",");
}
}
m_postInfo->GetNzbInfo()->GetScriptStatuses()->clear();
}
ExecuteScriptList(scriptCommaList);
m_postInfo->SetStage(PostInfo::ptFinished);
m_postInfo->SetWorking(false);
}
void PostScriptController::ExecuteScript(ScriptConfig::Script* script)
{
// if any script has requested par-check, do not execute other scripts
if (!script->GetPostScript() || m_postInfo->GetRequestParCheck())
{
return;
}
PrintMessage(Message::mkInfo, "Executing post-process-script %s for %s", script->GetName(), m_postInfo->GetNzbInfo()->GetName());
BString<1024> progressLabel("Executing post-process-script %s", script->GetName());
{
GuardedDownloadQueue guard = DownloadQueue::Guard();
m_postInfo->SetProgressLabel(progressLabel);
}
SetArgs({script->GetLocation()});
BString<1024> infoName("post-process-script %s for %s", script->GetName(), m_postInfo->GetNzbInfo()->GetName());
SetInfoName(infoName);
m_script = script;
SetLogPrefix(script->GetDisplayName());
m_prefixLen = strlen(script->GetDisplayName()) + 2; // 2 = strlen(": ");
PrepareParams(script->GetName());
int exitCode = Execute();
infoName[0] = 'P'; // uppercase
SetLogPrefix(nullptr);
ScriptStatus::EStatus status = AnalyseExitCode(exitCode, infoName);
{
GuardedDownloadQueue guard = DownloadQueue::Guard();
m_postInfo->GetNzbInfo()->GetScriptStatuses()->emplace_back(script->GetName(), status);
}
}
void PostScriptController::PrepareParams(const char* scriptName)
{
GuardedDownloadQueue guard = DownloadQueue::Guard();
ResetEnv();
SetIntEnvVar("NZBPP_NZBID", m_postInfo->GetNzbInfo()->GetId());
SetEnvVar("NZBPP_NZBNAME", m_postInfo->GetNzbInfo()->GetName());
SetEnvVar("NZBPP_DIRECTORY", m_postInfo->GetNzbInfo()->GetDestDir());
SetEnvVar("NZBPP_NZBFILENAME", m_postInfo->GetNzbInfo()->GetFilename());
SetEnvVar("NZBPP_QUEUEDFILE", m_postInfo->GetNzbInfo()->GetQueuedFilename());
SetEnvVar("NZBPP_URL", m_postInfo->GetNzbInfo()->GetUrl());
SetEnvVar("NZBPP_FINALDIR", m_postInfo->GetNzbInfo()->GetFinalDir());
SetEnvVar("NZBPP_CATEGORY", m_postInfo->GetNzbInfo()->GetCategory());
SetIntEnvVar("NZBPP_HEALTH", m_postInfo->GetNzbInfo()->CalcHealth());
SetIntEnvVar("NZBPP_CRITICALHEALTH", m_postInfo->GetNzbInfo()->CalcCriticalHealth(false));
SetEnvVar("NZBPP_DUPEKEY", m_postInfo->GetNzbInfo()->GetDupeKey());
SetIntEnvVar("NZBPP_DUPESCORE", m_postInfo->GetNzbInfo()->GetDupeScore());
const char* dupeModeName[] = { "SCORE", "ALL", "FORCE" };
SetEnvVar("NZBPP_DUPEMODE", dupeModeName[m_postInfo->GetNzbInfo()->GetDupeMode()]);
BString<1024> status = m_postInfo->GetNzbInfo()->MakeTextStatus(true);
SetEnvVar("NZBPP_STATUS", status);
char* detail = strchr(status, '/');
if (detail) *detail = '\0';
SetEnvVar("NZBPP_TOTALSTATUS", status);
const char* scriptStatusName[] = { "NONE", "FAILURE", "SUCCESS" };
SetEnvVar("NZBPP_SCRIPTSTATUS", scriptStatusName[m_postInfo->GetNzbInfo()->GetScriptStatuses()->CalcTotalStatus()]);
// deprecated
int parStatusCodes[] = { 0, 0, 1, 2, 3, 4 };
NzbInfo::EParStatus parStatus = m_postInfo->GetNzbInfo()->GetParStatus();
// for downloads marked as bad and for deleted downloads pass par status "Failure"
// for compatibility with older scripts which don't check "NZBPP_TOTALSTATUS"
if (m_postInfo->GetNzbInfo()->GetDeleteStatus() != NzbInfo::dsNone ||
m_postInfo->GetNzbInfo()->GetMarkStatus() == NzbInfo::ksBad)
{
parStatus = NzbInfo::psFailure;
}
SetIntEnvVar("NZBPP_PARSTATUS", parStatusCodes[parStatus]);
// deprecated
int unpackStatus[] = { 0, 0, 1, 2, 3, 4 };
SetIntEnvVar("NZBPP_UNPACKSTATUS", unpackStatus[m_postInfo->GetNzbInfo()->GetUnpackStatus()]);
// deprecated
SetIntEnvVar("NZBPP_HEALTHDELETED", (int)m_postInfo->GetNzbInfo()->GetDeleteStatus() == NzbInfo::dsHealth);
SetIntEnvVar("NZBPP_TOTALARTICLES", (int)m_postInfo->GetNzbInfo()->GetTotalArticles());
SetIntEnvVar("NZBPP_SUCCESSARTICLES", (int)m_postInfo->GetNzbInfo()->GetSuccessArticles());
SetIntEnvVar("NZBPP_FAILEDARTICLES", (int)m_postInfo->GetNzbInfo()->GetFailedArticles());
for (ServerStat& serverStat : m_postInfo->GetNzbInfo()->GetServerStats())
{
SetIntEnvVar(BString<1024>("NZBPP_SERVER%i_SUCCESSARTICLES", serverStat.GetServerId()),
serverStat.GetSuccessArticles());
SetIntEnvVar(BString<1024>("NZBPP_SERVER%i_FAILEDARTICLES", serverStat.GetServerId()),
serverStat.GetFailedArticles());
}
PrepareEnvScript(m_postInfo->GetNzbInfo()->GetParameters(), scriptName);
}
ScriptStatus::EStatus PostScriptController::AnalyseExitCode(int exitCode, const char* upInfoName)
{
// The ScriptStatus is accumulated for all scripts:
// If any script has failed the status is "failure", etc.
switch (exitCode)
{
case POSTPROCESS_SUCCESS:
PrintMessage(Message::mkInfo, "%s successful", upInfoName);
return ScriptStatus::srSuccess;
case POSTPROCESS_ERROR:
case -1: // Execute() returns -1 if the process could not be started (file not found or other problem)
PrintMessage(Message::mkError, "%s failed", upInfoName);
return ScriptStatus::srFailure;
case POSTPROCESS_NONE:
PrintMessage(Message::mkInfo, "%s skipped", upInfoName);
return ScriptStatus::srNone;
#ifndef DISABLE_PARCHECK
case POSTPROCESS_PARCHECK:
if (m_postInfo->GetNzbInfo()->GetParStatus() > NzbInfo::psSkipped)
{
PrintMessage(Message::mkError, "%s requested par-check/repair, but the collection was already checked", upInfoName);
return ScriptStatus::srFailure;
}
else
{
PrintMessage(Message::mkInfo, "%s requested par-check/repair", upInfoName);
m_postInfo->SetRequestParCheck(true);
m_postInfo->SetForceRepair(true);
return ScriptStatus::srSuccess;
}
break;
#endif
default:
PrintMessage(Message::mkError, "%s failed (terminated with unknown status)", upInfoName);
return ScriptStatus::srFailure;
}
}
void PostScriptController::AddMessage(Message::EKind kind, const char* text)
{
const char* msgText = text + m_prefixLen;
if (!strncmp(msgText, "[NZB] ", 6))
{
debug("Command %s detected", msgText + 6);
if (!strncmp(msgText + 6, "FINALDIR=", 9))
{
GuardedDownloadQueue guard = DownloadQueue::Guard();
m_postInfo->GetNzbInfo()->SetFinalDir(msgText + 6 + 9);
}
else if (!strncmp(msgText + 6, "DIRECTORY=", 10))
{
GuardedDownloadQueue guard = DownloadQueue::Guard();
m_postInfo->GetNzbInfo()->SetDestDir(msgText + 6 + 10);
}
else if (!strncmp(msgText + 6, "NZBPR_", 6))
{
CString param = msgText + 6 + 6;
char* value = strchr(param, '=');
if (value)
{
*value = '\0';
GuardedDownloadQueue guard = DownloadQueue::Guard();
m_postInfo->GetNzbInfo()->GetParameters()->SetParameter(param, value + 1);
}
else
{
m_postInfo->GetNzbInfo()->PrintMessage(Message::mkError,
"Invalid command \"%s\" received from %s", msgText, GetInfoName());
}
}
else if (!strncmp(msgText + 6, "MARK=BAD", 8))
{
SetLogPrefix(nullptr);
PrintMessage(Message::mkWarning, "Marking %s as bad", m_postInfo->GetNzbInfo()->GetName());
SetLogPrefix(m_script->GetDisplayName());
m_postInfo->GetNzbInfo()->SetMarkStatus(NzbInfo::ksBad);
}
else
{
m_postInfo->GetNzbInfo()->PrintMessage(Message::mkError,
"Invalid command \"%s\" received from %s", msgText, GetInfoName());
}
}
else
{
m_postInfo->GetNzbInfo()->AddMessage(kind, text);
GuardedDownloadQueue guard = DownloadQueue::Guard();
m_postInfo->SetProgressLabel(text);
}
if (g_WorkState->GetPausePostProcess() && !m_postInfo->GetNzbInfo()->GetForcePriority())
{
time_t stageTime = m_postInfo->GetStageTime();
time_t startTime = m_postInfo->GetStartTime();
time_t waitTime = Util::CurrentTime();
// wait until Post-processor is unpaused
while (g_WorkState->GetPausePostProcess() && !m_postInfo->GetNzbInfo()->GetForcePriority() && !IsStopped())
{
Util::Sleep(100);
// update time stamps
time_t delta = Util::CurrentTime() - waitTime;
if (stageTime > 0)
{
m_postInfo->SetStageTime(stageTime + delta);
}
if (startTime > 0)
{
m_postInfo->SetStartTime(startTime + delta);
}
}
}
}
void PostScriptController::Stop()
{
debug("Stopping post-process-script");
Thread::Stop();
Terminate();
}

View File

@@ -1,47 +0,0 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2016 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/>.
*/
#ifndef POSTSCRIPT_H
#define POSTSCRIPT_H
#include "Thread.h"
#include "NzbScript.h"
class PostScriptController : public Thread, public NzbScriptController
{
public:
virtual void Run();
virtual void Stop();
static void StartJob(PostInfo* postInfo);
protected:
virtual void ExecuteScript(ScriptConfig::Script* script);
virtual void AddMessage(Message::EKind kind, const char* text);
private:
PostInfo* m_postInfo;
int m_prefixLen;
ScriptConfig::Script* m_script;
void PrepareParams(const char* scriptName);
ScriptStatus::EStatus AnalyseExitCode(int exitCode, const char* upInfoName);
};
#endif

View File

@@ -1,495 +0,0 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2017 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 "NString.h"
#include "QueueScript.h"
#include "NzbScript.h"
#include "Options.h"
#include "Log.h"
#include "Util.h"
#include "FileSystem.h"
static const char* QUEUE_EVENT_NAMES[] = {
"FILE_DOWNLOADED",
"URL_COMPLETED",
"NZB_MARKED",
"NZB_ADDED",
"NZB_NAMED",
"NZB_DOWNLOADED",
"NZB_DELETED" };
class QueueScriptController : public Thread, public NzbScriptController
{
public:
virtual void Run();
static void StartScript(NzbInfo* nzbInfo, ScriptConfig::Script* script, QueueScriptCoordinator::EEvent event);
protected:
virtual void ExecuteScript(ScriptConfig::Script* script);
virtual void AddMessage(Message::EKind kind, const char* text);
private:
CString m_nzbName;
CString m_nzbFilename;
CString m_url;
CString m_category;
CString m_destDir;
CString m_queuedFilename;
int m_id;
int m_priority;
CString m_dupeKey;
EDupeMode m_dupeMode;
int m_dupeScore;
NzbParameterList m_parameters;
int m_prefixLen;
ScriptConfig::Script* m_script;
QueueScriptCoordinator::EEvent m_event;
bool m_markBad;
NzbInfo::EDeleteStatus m_deleteStatus;
NzbInfo::EUrlStatus m_urlStatus;
NzbInfo::EMarkStatus m_markStatus;
void PrepareParams(const char* scriptName);
};
void QueueScriptController::StartScript(NzbInfo* nzbInfo, ScriptConfig::Script* script, QueueScriptCoordinator::EEvent event)
{
QueueScriptController* scriptController = new QueueScriptController();
scriptController->m_nzbName = nzbInfo->GetName();
scriptController->m_nzbFilename = nzbInfo->GetFilename();
scriptController->m_url = nzbInfo->GetUrl();
scriptController->m_category = nzbInfo->GetCategory();
scriptController->m_destDir = nzbInfo->GetDestDir();
scriptController->m_queuedFilename = nzbInfo->GetQueuedFilename();
scriptController->m_id = nzbInfo->GetId();
scriptController->m_priority = nzbInfo->GetPriority();
scriptController->m_dupeKey = nzbInfo->GetDupeKey();
scriptController->m_dupeMode = nzbInfo->GetDupeMode();
scriptController->m_dupeScore = nzbInfo->GetDupeScore();
scriptController->m_parameters.CopyFrom(nzbInfo->GetParameters());
scriptController->m_script = script;
scriptController->m_event = event;
scriptController->m_prefixLen = 0;
scriptController->m_markBad = false;
scriptController->m_deleteStatus = nzbInfo->GetDeleteStatus();
scriptController->m_urlStatus = nzbInfo->GetUrlStatus();
scriptController->m_markStatus = nzbInfo->GetMarkStatus();
scriptController->SetAutoDestroy(true);
scriptController->Start();
}
void QueueScriptController::Run()
{
ExecuteScript(m_script);
SetLogPrefix(nullptr);
if (m_markBad)
{
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
NzbInfo* nzbInfo = downloadQueue->GetQueue()->Find(m_id);
if (nzbInfo)
{
nzbInfo->PrintMessage(Message::mkWarning, "Cancelling download and deleting %s", *m_nzbName);
nzbInfo->SetDeleteStatus(NzbInfo::dsBad);
downloadQueue->EditEntry(m_id, DownloadQueue::eaGroupDelete, nullptr);
}
}
g_QueueScriptCoordinator->CheckQueue();
}
void QueueScriptController::ExecuteScript(ScriptConfig::Script* script)
{
PrintMessage(m_event == QueueScriptCoordinator::qeFileDownloaded ? Message::mkDetail : Message::mkInfo,
"Executing queue-script %s for %s", script->GetName(), FileSystem::BaseFileName(m_nzbName));
SetArgs({script->GetLocation()});
BString<1024> infoName("queue-script %s for %s", script->GetName(), FileSystem::BaseFileName(m_nzbName));
SetInfoName(infoName);
SetLogPrefix(script->GetDisplayName());
m_prefixLen = strlen(script->GetDisplayName()) + 2; // 2 = strlen(": ");
PrepareParams(script->GetName());
Execute();
SetLogPrefix(nullptr);
}
void QueueScriptController::PrepareParams(const char* scriptName)
{
ResetEnv();
SetEnvVar("NZBNA_NZBNAME", m_nzbName);
SetIntEnvVar("NZBNA_NZBID", m_id);
SetEnvVar("NZBNA_FILENAME", m_nzbFilename);
SetEnvVar("NZBNA_DIRECTORY", m_destDir);
SetEnvVar("NZBNA_QUEUEDFILE", m_queuedFilename);
SetEnvVar("NZBNA_URL", m_url);
SetEnvVar("NZBNA_CATEGORY", m_category);
SetIntEnvVar("NZBNA_PRIORITY", m_priority);
SetIntEnvVar("NZBNA_LASTID", m_id); // deprecated
SetEnvVar("NZBNA_DUPEKEY", m_dupeKey);
SetIntEnvVar("NZBNA_DUPESCORE", m_dupeScore);
const char* dupeModeName[] = { "SCORE", "ALL", "FORCE" };
SetEnvVar("NZBNA_DUPEMODE", dupeModeName[m_dupeMode]);
SetEnvVar("NZBNA_EVENT", QUEUE_EVENT_NAMES[m_event]);
const char* deleteStatusName[] = { "NONE", "MANUAL", "HEALTH", "DUPE", "BAD", "GOOD", "COPY", "SCAN" };
SetEnvVar("NZBNA_DELETESTATUS", deleteStatusName[m_deleteStatus]);
const char* urlStatusName[] = { "NONE", "UNKNOWN", "SUCCESS", "FAILURE", "UNKNOWN", "SCAN_SKIPPED", "SCAN_FAILURE" };
SetEnvVar("NZBNA_URLSTATUS", urlStatusName[m_urlStatus]);
const char* markStatusName[] = { "NONE", "BAD", "GOOD", "SUCCESS" };
SetEnvVar("NZBNA_MARKSTATUS", markStatusName[m_markStatus]);
PrepareEnvScript(&m_parameters, scriptName);
}
void QueueScriptController::AddMessage(Message::EKind kind, const char* text)
{
const char* msgText = text + m_prefixLen;
if (!strncmp(msgText, "[NZB] ", 6))
{
debug("Command %s detected", msgText + 6);
if (!strncmp(msgText + 6, "NZBPR_", 6))
{
CString param = msgText + 6 + 6;
char* value = strchr(param, '=');
if (value)
{
*value = '\0';
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
NzbInfo* nzbInfo = QueueScriptCoordinator::FindNzbInfo(downloadQueue, m_id);
if (nzbInfo)
{
nzbInfo->GetParameters()->SetParameter(param, value + 1);
}
}
else
{
error("Invalid command \"%s\" received from %s", msgText, GetInfoName());
}
}
else if (!strncmp(msgText + 6, "DIRECTORY=", 10) &&
m_event == QueueScriptCoordinator::qeNzbDownloaded)
{
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
NzbInfo* nzbInfo = QueueScriptCoordinator::FindNzbInfo(downloadQueue, m_id);
if (nzbInfo)
{
nzbInfo->SetFinalDir(msgText + 6 + 10);
}
}
else if (!strncmp(msgText + 6, "MARK=BAD", 8))
{
m_markBad = true;
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
NzbInfo* nzbInfo = QueueScriptCoordinator::FindNzbInfo(downloadQueue, m_id);
if (nzbInfo)
{
nzbInfo->PrintMessage(Message::mkWarning, "Marking %s as bad", *m_nzbName);
nzbInfo->SetMarkStatus(NzbInfo::ksBad);
}
}
else
{
error("Invalid command \"%s\" received from %s", msgText, GetInfoName());
}
}
else
{
NzbInfo* nzbInfo = nullptr;
{
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
nzbInfo = QueueScriptCoordinator::FindNzbInfo(downloadQueue, m_id);
if (nzbInfo)
{
nzbInfo->AddMessage(kind, text);
}
}
if (!nzbInfo)
{
ScriptController::AddMessage(kind, text);
}
}
}
void QueueScriptCoordinator::InitOptions()
{
m_hasQueueScripts = false;
for (ScriptConfig::Script& script : g_ScriptConfig->GetScripts())
{
if (script.GetQueueScript())
{
m_hasQueueScripts = true;
break;
}
}
}
void QueueScriptCoordinator::EnqueueScript(NzbInfo* nzbInfo, EEvent event)
{
if (!m_hasQueueScripts)
{
return;
}
Guard guard(m_queueMutex);
if (event == qeNzbDownloaded)
{
// delete all other queued scripts for this nzb
m_queue.erase(std::remove_if(m_queue.begin(), m_queue.end(),
[nzbInfo](std::unique_ptr<QueueItem>& queueItem)
{
return queueItem->GetNzbId() == nzbInfo->GetId();
}),
m_queue.end());
}
// respect option "EventInterval"
time_t curTime = Util::CurrentTime();
if (event == qeFileDownloaded &&
(g_Options->GetEventInterval() == -1 ||
(g_Options->GetEventInterval() > 0 && curTime - nzbInfo->GetQueueScriptTime() > 0 &&
(int)(curTime - nzbInfo->GetQueueScriptTime()) < g_Options->GetEventInterval())))
{
return;
}
for (ScriptConfig::Script& script : g_ScriptConfig->GetScripts())
{
if (UsableScript(script, nzbInfo, event))
{
bool alreadyQueued = false;
if (event == qeFileDownloaded)
{
// check if this script is already queued for this nzb
for (QueueItem* queueItem : &m_queue)
{
if (queueItem->GetNzbId() == nzbInfo->GetId() && queueItem->GetScript() == &script)
{
alreadyQueued = true;
break;
}
}
}
if (!alreadyQueued)
{
std::unique_ptr<QueueItem> queueItem = std::make_unique<QueueItem>(nzbInfo->GetId(), &script, event);
if (m_curItem)
{
m_queue.push_back(std::move(queueItem));
}
else
{
m_curItem = std::move(queueItem);
QueueScriptController::StartScript(nzbInfo, m_curItem->GetScript(), m_curItem->GetEvent());
}
}
nzbInfo->SetQueueScriptTime(Util::CurrentTime());
}
}
}
bool QueueScriptCoordinator::UsableScript(ScriptConfig::Script& script, NzbInfo* nzbInfo, EEvent event)
{
if (!script.GetQueueScript())
{
return false;
}
if (!Util::EmptyStr(script.GetQueueEvents()) && !strstr(script.GetQueueEvents(), QUEUE_EVENT_NAMES[event]))
{
return false;
}
// check extension scripts assigned for that nzb
for (NzbParameter& parameter : nzbInfo->GetParameters())
{
const char* varname = parameter.GetName();
if (strlen(varname) > 0 && varname[0] != '*' && varname[strlen(varname)-1] == ':' &&
(!strcasecmp(parameter.GetValue(), "yes") ||
!strcasecmp(parameter.GetValue(), "on") ||
!strcasecmp(parameter.GetValue(), "1")))
{
BString<1024> scriptName = varname;
scriptName[strlen(scriptName)-1] = '\0'; // remove trailing ':'
if (FileSystem::SameFilename(scriptName, script.GetName()))
{
return true;
}
}
}
// for URL-events the extension scripts are not assigned yet;
// instead we take the default extension scripts for the category (or global)
if (event == qeUrlCompleted)
{
const char* postScript = g_Options->GetExtensions();
if (!Util::EmptyStr(nzbInfo->GetCategory()))
{
Options::Category* categoryObj = g_Options->FindCategory(nzbInfo->GetCategory(), false);
if (categoryObj && !Util::EmptyStr(categoryObj->GetExtensions()))
{
postScript = categoryObj->GetExtensions();
}
}
if (!Util::EmptyStr(postScript))
{
Tokenizer tok(postScript, ",;");
while (const char* scriptName = tok.Next())
{
if (FileSystem::SameFilename(scriptName, script.GetName()))
{
return true;
}
}
}
}
return false;
}
NzbInfo* QueueScriptCoordinator::FindNzbInfo(DownloadQueue* downloadQueue, int nzbId)
{
NzbInfo* nzbInfo = downloadQueue->GetQueue()->Find(nzbId);
if (nzbInfo)
{
return nzbInfo;
}
HistoryInfo* historyInfo = downloadQueue->GetHistory()->Find(nzbId);
if (historyInfo)
{
return historyInfo->GetNzbInfo();
}
return nullptr;
}
void QueueScriptCoordinator::CheckQueue()
{
if (m_stopped)
{
return;
}
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
Guard guard(m_queueMutex);
m_curItem.reset();
NzbInfo* curNzbInfo = nullptr;
Queue::iterator itCurItem;
for (Queue::iterator it = m_queue.begin(); it != m_queue.end(); )
{
std::unique_ptr<QueueItem>& queueItem = *it;
NzbInfo* nzbInfo = FindNzbInfo(downloadQueue, queueItem->GetNzbId());
// in a case this nzb must not be processed further - delete queue script from queue
EEvent event = queueItem->GetEvent();
bool ignoreEvent = !nzbInfo ||
(nzbInfo->GetDeleteStatus() != NzbInfo::dsNone && event != qeNzbDeleted && event != qeNzbMarked) ||
(nzbInfo->GetMarkStatus() == NzbInfo::ksBad && event != qeNzbMarked);
if (ignoreEvent)
{
it = m_queue.erase(it);
if (curNzbInfo)
{
// process from the beginning, while "erase" invalidated "itCurItem"
curNzbInfo = nullptr;
it = m_queue.begin();
}
continue;
}
if (!m_curItem || queueItem->GetEvent() > m_curItem->GetEvent())
{
itCurItem = it;
curNzbInfo = nzbInfo;
}
it++;
}
if (curNzbInfo)
{
m_curItem = std::move(*itCurItem);
m_queue.erase(itCurItem);
QueueScriptController::StartScript(curNzbInfo, m_curItem->GetScript(), m_curItem->GetEvent());
}
}
bool QueueScriptCoordinator::HasJob(int nzbId, bool* active)
{
Guard guard(m_queueMutex);
bool working = m_curItem && m_curItem->GetNzbId() == nzbId;
if (active)
{
*active = working;
}
if (!working)
{
for (QueueItem* queueItem : &m_queue)
{
working = queueItem->GetNzbId() == nzbId;
if (working)
{
break;
}
}
}
return working;
}
int QueueScriptCoordinator::GetQueueSize()
{
Guard guard(m_queueMutex);
int queuedCount = m_queue.size();
if (m_curItem)
{
queuedCount++;
}
return queuedCount;
}

View File

@@ -1,77 +0,0 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2017 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/>.
*/
#ifndef QUEUESCRIPT_H
#define QUEUESCRIPT_H
#include "DownloadInfo.h"
#include "ScriptConfig.h"
class QueueScriptCoordinator
{
public:
enum EEvent
{
qeFileDownloaded, // lowest priority
qeUrlCompleted,
qeNzbMarked,
qeNzbAdded,
qeNzbNamed,
qeNzbDownloaded,
qeNzbDeleted // highest priority
};
void Stop() { m_stopped = true; }
void InitOptions();
void EnqueueScript(NzbInfo* nzbInfo, EEvent event);
void CheckQueue();
bool HasJob(int nzbId, bool* active);
int GetQueueSize();
static NzbInfo* FindNzbInfo(DownloadQueue* downloadQueue, int nzbId);
private:
class QueueItem
{
public:
QueueItem(int nzbId, ScriptConfig::Script* script, EEvent event) :
m_nzbId(nzbId), m_script(script), m_event(event) {}
int GetNzbId() { return m_nzbId; }
ScriptConfig::Script* GetScript() { return m_script; }
EEvent GetEvent() { return m_event; }
private:
int m_nzbId;
ScriptConfig::Script* m_script;
EEvent m_event;
};
typedef std::deque<std::unique_ptr<QueueItem>> Queue;
Queue m_queue;
Mutex m_queueMutex;
std::unique_ptr<QueueItem> m_curItem;
bool m_hasQueueScripts = false;
bool m_stopped = false;
bool UsableScript(ScriptConfig::Script& script, NzbInfo* nzbInfo, EEvent event);
};
extern QueueScriptCoordinator* g_QueueScriptCoordinator;
#endif

View File

@@ -1,200 +0,0 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2016 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 "ScanScript.h"
#include "Scanner.h"
#include "Options.h"
#include "Log.h"
#include "FileSystem.h"
class ScanScriptCheck : public NzbScriptController
{
protected:
virtual void ExecuteScript(ScriptConfig::Script* script) { has |= script->GetScanScript(); }
bool has = false;
friend class ScanScriptController;
};
bool ScanScriptController::HasScripts()
{
ScanScriptCheck check;
check.ExecuteScriptList(g_Options->GetExtensions());
return check.has;
}
void ScanScriptController::ExecuteScripts(const char* nzbFilename,
const char* url, const char* directory, CString* nzbName, CString* category,
int* priority, NzbParameterList* parameters, bool* addTop, bool* addPaused,
CString* dupeKey, int* dupeScore, EDupeMode* dupeMode)
{
ScanScriptController scriptController;
scriptController.m_nzbFilename = nzbFilename;
scriptController.m_url = url;
scriptController.m_directory = directory;
scriptController.m_nzbName = nzbName;
scriptController.m_category = category;
scriptController.m_parameters = parameters;
scriptController.m_priority = priority;
scriptController.m_addTop = addTop;
scriptController.m_addPaused = addPaused;
scriptController.m_dupeKey = dupeKey;
scriptController.m_dupeScore = dupeScore;
scriptController.m_dupeMode = dupeMode;
scriptController.m_prefixLen = 0;
const char* extensions = g_Options->GetExtensions();
if (!Util::EmptyStr(*category))
{
Options::Category* categoryObj = g_Options->FindCategory(*category, false);
if (categoryObj && !Util::EmptyStr(categoryObj->GetExtensions()))
{
extensions = categoryObj->GetExtensions();
}
}
scriptController.ExecuteScriptList(extensions);
}
void ScanScriptController::ExecuteScript(ScriptConfig::Script* script)
{
if (!script->GetScanScript() || !FileSystem::FileExists(m_nzbFilename))
{
return;
}
PrintMessage(Message::mkInfo, "Executing scan-script %s for %s", script->GetName(), FileSystem::BaseFileName(m_nzbFilename));
SetArgs({script->GetLocation()});
BString<1024> infoName("scan-script %s for %s", script->GetName(), FileSystem::BaseFileName(m_nzbFilename));
SetInfoName(infoName);
SetLogPrefix(script->GetDisplayName());
m_prefixLen = strlen(script->GetDisplayName()) + 2; // 2 = strlen(": ");
PrepareParams(script->GetName());
Execute();
SetLogPrefix(nullptr);
}
void ScanScriptController::PrepareParams(const char* scriptName)
{
ResetEnv();
SetEnvVar("NZBNP_FILENAME", m_nzbFilename);
SetEnvVar("NZBNP_URL", m_url);
SetEnvVar("NZBNP_NZBNAME", strlen(*m_nzbName) > 0 ? **m_nzbName : FileSystem::BaseFileName(m_nzbFilename));
SetEnvVar("NZBNP_CATEGORY", *m_category);
SetIntEnvVar("NZBNP_PRIORITY", *m_priority);
SetIntEnvVar("NZBNP_TOP", *m_addTop ? 1 : 0);
SetIntEnvVar("NZBNP_PAUSED", *m_addPaused ? 1 : 0);
SetEnvVar("NZBNP_DUPEKEY", *m_dupeKey);
SetIntEnvVar("NZBNP_DUPESCORE", *m_dupeScore);
const char* dupeModeName[] = { "SCORE", "ALL", "FORCE" };
SetEnvVar("NZBNP_DUPEMODE", dupeModeName[*m_dupeMode]);
// remove trailing slash
BString<1024> dir = m_directory;
int len = strlen(dir);
if (dir[len-1] == PATH_SEPARATOR)
{
dir[len-1] = '\0';
}
SetEnvVar("NZBNP_DIRECTORY", dir);
PrepareEnvScript(m_parameters, scriptName);
}
void ScanScriptController::AddMessage(Message::EKind kind, const char* text)
{
const char* msgText = text + m_prefixLen;
if (!strncmp(msgText, "[NZB] ", 6))
{
debug("Command %s detected", msgText + 6);
if (!strncmp(msgText + 6, "NZBNAME=", 8))
{
*m_nzbName = msgText + 6 + 8;
}
else if (!strncmp(msgText + 6, "CATEGORY=", 9))
{
*m_category = msgText + 6 + 9;
g_Scanner->InitPPParameters(*m_category, m_parameters, true);
}
else if (!strncmp(msgText + 6, "NZBPR_", 6))
{
CString param = msgText + 6 + 6;
char* value = strchr(param, '=');
if (value)
{
*value = '\0';
m_parameters->SetParameter(param, value + 1);
}
else
{
error("Invalid command \"%s\" received from %s", msgText, GetInfoName());
}
}
else if (!strncmp(msgText + 6, "PRIORITY=", 9))
{
*m_priority = atoi(msgText + 6 + 9);
}
else if (!strncmp(msgText + 6, "TOP=", 4))
{
*m_addTop = atoi(msgText + 6 + 4) != 0;
}
else if (!strncmp(msgText + 6, "PAUSED=", 7))
{
*m_addPaused = atoi(msgText + 6 + 7) != 0;
}
else if (!strncmp(msgText + 6, "DUPEKEY=", 8))
{
*m_dupeKey = msgText + 6 + 8;
}
else if (!strncmp(msgText + 6, "DUPESCORE=", 10))
{
*m_dupeScore = atoi(msgText + 6 + 10);
}
else if (!strncmp(msgText + 6, "DUPEMODE=", 9))
{
const char* dupeMode = msgText + 6 + 9;
if (strcasecmp(dupeMode, "score") && strcasecmp(dupeMode, "all") && strcasecmp(dupeMode, "force"))
{
error("Invalid value \"%s\" for command \"DUPEMODE\" received from %s", dupeMode, GetInfoName());
return;
}
*m_dupeMode = !strcasecmp(dupeMode, "all") ? dmAll :
!strcasecmp(dupeMode, "force") ? dmForce : dmScore;
}
else
{
error("Invalid command \"%s\" received from %s", msgText, GetInfoName());
}
}
else
{
ScriptController::AddMessage(kind, text);
}
}

View File

@@ -1,57 +0,0 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2016 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/>.
*/
#ifndef SCANSCRIPT_H
#define SCANSCRIPT_H
#include "NzbScript.h"
class ScanScriptController : public NzbScriptController
{
public:
static void ExecuteScripts(const char* nzbFilename, const char* url,
const char* directory, CString* nzbName, CString* category, int* priority,
NzbParameterList* parameters, bool* addTop, bool* addPaused,
CString* dupeKey, int* dupeScore, EDupeMode* dupeMode);
static bool HasScripts();
protected:
virtual void ExecuteScript(ScriptConfig::Script* script);
virtual void AddMessage(Message::EKind kind, const char* text);
private:
const char* m_nzbFilename;
const char* m_url;
const char* m_directory;
CString* m_nzbName;
CString* m_category;
int* m_priority;
NzbParameterList* m_parameters;
bool* m_addTop;
bool* m_addPaused;
CString* m_dupeKey;
int* m_dupeScore;
EDupeMode* m_dupeMode;
int m_prefixLen;
void PrepareParams(const char* scriptName);
};
#endif

View File

@@ -1,114 +0,0 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2016 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 "SchedulerScript.h"
#include "Options.h"
#include "Log.h"
#include "Util.h"
#include "FileSystem.h"
void SchedulerScriptController::StartScript(const char* param, bool externalProcess, int taskId)
{
std::vector<CString> argv;
if (externalProcess && (argv = Util::SplitCommandLine(param)).empty())
{
error("Could not execute scheduled process-script, failed to parse command line: %s", param);
return;
}
SchedulerScriptController* scriptController = new SchedulerScriptController();
scriptController->m_externalProcess = externalProcess;
scriptController->m_script = param;
scriptController->m_taskId = taskId;
if (externalProcess)
{
scriptController->SetArgs(std::move(argv));
}
scriptController->SetAutoDestroy(true);
scriptController->Start();
}
void SchedulerScriptController::Run()
{
if (m_externalProcess)
{
ExecuteExternalProcess();
}
else
{
ExecuteScriptList(m_script);
}
}
void SchedulerScriptController::ExecuteScript(ScriptConfig::Script* script)
{
if (!script->GetSchedulerScript())
{
return;
}
BString<1024> taskName(" for Task%i", m_taskId);
if (m_taskId == 0)
{
taskName = "";
}
PrintMessage(Message::mkInfo, "Executing scheduler-script %s%s", script->GetName(), *taskName);
SetArgs({script->GetLocation()});
BString<1024> infoName("scheduler-script %s%s", script->GetName(), *taskName);
SetInfoName(infoName);
SetLogPrefix(script->GetDisplayName());
PrepareParams(script->GetName());
Execute();
SetLogPrefix(nullptr);
}
void SchedulerScriptController::PrepareParams(const char* scriptName)
{
ResetEnv();
SetIntEnvVar("NZBSP_TASKID", m_taskId);
PrepareEnvScript(nullptr, scriptName);
}
void SchedulerScriptController::ExecuteExternalProcess()
{
info("Executing scheduled process-script %s for Task%i", FileSystem::BaseFileName(GetScript()), m_taskId);
BString<1024> infoName("scheduled process-script %s for Task%i", FileSystem::BaseFileName(GetScript()), m_taskId);
SetInfoName(infoName);
BString<1024> logPrefix = FileSystem::BaseFileName(GetScript());
if (char* ext = strrchr(logPrefix, '.')) *ext = '\0'; // strip file extension
SetLogPrefix(logPrefix);
Execute();
}

View File

@@ -1,45 +0,0 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2016 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/>.
*/
#ifndef SCHEDULERSCRIPT_H
#define SCHEDULERSCRIPT_H
#include "NString.h"
#include "NzbScript.h"
class SchedulerScriptController : public Thread, public NzbScriptController
{
public:
virtual void Run();
static void StartScript(const char* param, bool externalProcess, int taskId);
protected:
virtual void ExecuteScript(ScriptConfig::Script* script);
private:
CString m_script;
bool m_externalProcess;
int m_taskId;
void PrepareParams(const char* scriptName);
void ExecuteExternalProcess();
};
#endif

View File

@@ -1,500 +0,0 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2013-2016 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 "Util.h"
#include "FileSystem.h"
#include "Options.h"
#include "Log.h"
#include "ScriptConfig.h"
static const char* BEGIN_SCRIPT_SIGNATURE = "### NZBGET ";
static const char* POST_SCRIPT_SIGNATURE = "POST-PROCESSING";
static const char* SCAN_SCRIPT_SIGNATURE = "SCAN";
static const char* QUEUE_SCRIPT_SIGNATURE = "QUEUE";
static const char* SCHEDULER_SCRIPT_SIGNATURE = "SCHEDULER";
static const char* FEED_SCRIPT_SIGNATURE = "FEED";
static const char* END_SCRIPT_SIGNATURE = " SCRIPT";
static const char* QUEUE_EVENTS_SIGNATURE = "### QUEUE EVENTS:";
static const char* TASK_TIME_SIGNATURE = "### TASK TIME:";
static const char* DEFINITION_SIGNATURE = "###";
void ScriptConfig::InitOptions()
{
InitScripts();
InitConfigTemplates();
CreateTasks();
}
bool ScriptConfig::LoadConfig(Options::OptEntries* optEntries)
{
// read config file
DiskFile infile;
if (!infile.Open(g_Options->GetConfigFilename(), DiskFile::omRead))
{
return false;
}
int fileLen = (int)FileSystem::FileSize(g_Options->GetConfigFilename());
CString buf;
buf.Reserve(fileLen);
while (infile.ReadLine(buf, fileLen + 1))
{
// remove trailing '\n' and '\r' and spaces
Util::TrimRight(buf);
// skip comments and empty lines
if (buf[0] == 0 || buf[0] == '#' || strspn(buf, " ") == strlen(buf))
{
continue;
}
CString optname;
CString optvalue;
if (Options::SplitOptionString(buf, optname, optvalue))
{
optEntries->emplace_back(optname, optvalue);
}
}
infile.Close();
Options::ConvertOldOptions(optEntries);
return true;
}
bool ScriptConfig::SaveConfig(Options::OptEntries* optEntries)
{
// save to config file
DiskFile infile;
if (!infile.Open(g_Options->GetConfigFilename(), DiskFile::omReadWrite))
{
return false;
}
std::vector<CString> config;
std::set<Options::OptEntry*> writtenOptions;
// read config file into memory array
int fileLen = (int)FileSystem::FileSize(g_Options->GetConfigFilename()) + 1;
CString content;
content.Reserve(fileLen);
while (infile.ReadLine(content, fileLen + 1))
{
config.push_back(*content);
}
content.Clear();
// write config file back to disk, replace old values of existing options with new values
infile.Seek(0);
for (CString& buf : config)
{
const char* eq = strchr(buf, '=');
if (eq && buf[0] != '#')
{
// remove trailing '\n' and '\r' and spaces
buf.TrimRight();
CString optname;
CString optvalue;
if (g_Options->SplitOptionString(buf, optname, optvalue))
{
Options::OptEntry* optEntry = optEntries->FindOption(optname);
if (optEntry)
{
infile.Print("%s=%s\n", optEntry->GetName(), optEntry->GetValue());
writtenOptions.insert(optEntry);
}
}
}
else
{
infile.Print("%s", *buf);
}
}
// write new options
for (Options::OptEntry& optEntry : *optEntries)
{
std::set<Options::OptEntry*>::iterator fit = writtenOptions.find(&optEntry);
if (fit == writtenOptions.end())
{
infile.Print("%s=%s\n", optEntry.GetName(), optEntry.GetValue());
}
}
// close and truncate the file
int pos = (int)infile.Position();
infile.Close();
FileSystem::TruncateFile(g_Options->GetConfigFilename(), pos);
return true;
}
bool ScriptConfig::LoadConfigTemplates(ConfigTemplates* configTemplates)
{
CharBuffer buffer;
if (!FileSystem::LoadFileIntoBuffer(g_Options->GetConfigTemplate(), buffer, true))
{
return false;
}
configTemplates->emplace_back(Script("", ""), buffer);
if (!g_Options->GetScriptDir())
{
return true;
}
Scripts scriptList;
LoadScripts(&scriptList);
const int beginSignatureLen = strlen(BEGIN_SCRIPT_SIGNATURE);
const int definitionSignatureLen = strlen(DEFINITION_SIGNATURE);
for (Script& script : scriptList)
{
DiskFile infile;
if (!infile.Open(script.GetLocation(), DiskFile::omRead))
{
configTemplates->emplace_back(std::move(script), "");
continue;
}
StringBuilder templ;
char buf[1024];
bool inConfig = false;
bool inHeader = false;
while (infile.ReadLine(buf, sizeof(buf) - 1))
{
if (!strncmp(buf, BEGIN_SCRIPT_SIGNATURE, beginSignatureLen) &&
strstr(buf, END_SCRIPT_SIGNATURE) &&
(strstr(buf, POST_SCRIPT_SIGNATURE) ||
strstr(buf, SCAN_SCRIPT_SIGNATURE) ||
strstr(buf, QUEUE_SCRIPT_SIGNATURE) ||
strstr(buf, SCHEDULER_SCRIPT_SIGNATURE) ||
strstr(buf, FEED_SCRIPT_SIGNATURE)))
{
if (inConfig)
{
break;
}
inConfig = true;
inHeader = true;
continue;
}
inHeader &= !strncmp(buf, DEFINITION_SIGNATURE, definitionSignatureLen);
if (inConfig && !inHeader)
{
templ.Append(buf);
}
}
infile.Close();
configTemplates->emplace_back(std::move(script), templ);
}
return true;
}
void ScriptConfig::InitConfigTemplates()
{
if (!LoadConfigTemplates(&m_configTemplates))
{
error("Could not read configuration templates");
}
}
void ScriptConfig::InitScripts()
{
LoadScripts(&m_scripts);
}
void ScriptConfig::LoadScripts(Scripts* scripts)
{
if (Util::EmptyStr(g_Options->GetScriptDir()))
{
return;
}
Scripts tmpScripts;
Tokenizer tokDir(g_Options->GetScriptDir(), ",;");
while (const char* scriptDir = tokDir.Next())
{
LoadScriptDir(&tmpScripts, scriptDir, false);
}
tmpScripts.sort(
[](Script& script1, Script& script2)
{
return strcmp(script1.GetName(), script2.GetName()) < 0;
});
// first add all scripts from ScriptOrder
Tokenizer tokOrder(g_Options->GetScriptOrder(), ",;");
while (const char* scriptName = tokOrder.Next())
{
Scripts::iterator pos = std::find_if(tmpScripts.begin(), tmpScripts.end(),
[scriptName](Script& script)
{
return !strcmp(script.GetName(), scriptName);
});
if (pos != tmpScripts.end())
{
scripts->splice(scripts->end(), tmpScripts, pos);
}
}
// then add all other scripts from scripts directory
scripts->splice(scripts->end(), std::move(tmpScripts));
BuildScriptDisplayNames(scripts);
}
void ScriptConfig::LoadScriptDir(Scripts* scripts, const char* directory, bool isSubDir)
{
DirBrowser dir(directory);
while (const char* filename = dir.Next())
{
if (filename[0] != '.' && filename[0] != '_')
{
BString<1024> fullFilename("%s%c%s", directory, PATH_SEPARATOR, filename);
if (!FileSystem::DirectoryExists(fullFilename))
{
BString<1024> scriptName = BuildScriptName(directory, filename, isSubDir);
if (ScriptExists(scripts, scriptName))
{
continue;
}
Script script(scriptName, fullFilename);
if (LoadScriptFile(&script))
{
scripts->push_back(std::move(script));
}
}
else if (!isSubDir)
{
LoadScriptDir(scripts, fullFilename, true);
}
}
}
}
bool ScriptConfig::LoadScriptFile(Script* script)
{
DiskFile infile;
if (!infile.Open(script->GetLocation(), DiskFile::omRead))
{
return false;
}
CharBuffer buffer(1024 * 10 + 1);
const int beginSignatureLen = strlen(BEGIN_SCRIPT_SIGNATURE);
const int queueEventsSignatureLen = strlen(QUEUE_EVENTS_SIGNATURE);
const int taskTimeSignatureLen = strlen(TASK_TIME_SIGNATURE);
const int definitionSignatureLen = strlen(DEFINITION_SIGNATURE);
// check if the file contains pp-script-signature
// read first 10KB of the file and look for signature
int readBytes = (int)infile.Read(buffer, buffer.Size() - 1);
infile.Close();
buffer[readBytes] = '\0';
bool postScript = false;
bool scanScript = false;
bool queueScript = false;
bool schedulerScript = false;
bool feedScript = false;
char* queueEvents = nullptr;
char* taskTime = nullptr;
bool inConfig = false;
bool afterConfig = false;
// Declarations "QUEUE EVENT:" and "TASK TIME:" can be placed:
// - in script definition body (between opening and closing script signatures);
// - immediately before script definition (before opening script signature);
// - immediately after script definition (after closing script signature).
// The last two pissibilities are provided to increase compatibility of scripts with older
// nzbget versions which do not expect the extra declarations in the script defintion body.
Tokenizer tok(buffer, "\n\r", true);
while (char* line = tok.Next())
{
if (!strncmp(line, QUEUE_EVENTS_SIGNATURE, queueEventsSignatureLen))
{
queueEvents = line + queueEventsSignatureLen;
}
else if (!strncmp(line, TASK_TIME_SIGNATURE, taskTimeSignatureLen))
{
taskTime = line + taskTimeSignatureLen;
}
bool header = !strncmp(line, DEFINITION_SIGNATURE, definitionSignatureLen);
if (!header && !inConfig)
{
queueEvents = nullptr;
taskTime = nullptr;
}
if (!header && afterConfig)
{
break;
}
if (!strncmp(line, BEGIN_SCRIPT_SIGNATURE, beginSignatureLen) && strstr(line, END_SCRIPT_SIGNATURE))
{
if (!inConfig)
{
inConfig = true;
postScript = strstr(line, POST_SCRIPT_SIGNATURE);
scanScript = strstr(line, SCAN_SCRIPT_SIGNATURE);
queueScript = strstr(line, QUEUE_SCRIPT_SIGNATURE);
schedulerScript = strstr(line, SCHEDULER_SCRIPT_SIGNATURE);
feedScript = strstr(line, FEED_SCRIPT_SIGNATURE);
}
else
{
afterConfig = true;
}
}
}
if (!(postScript || scanScript || queueScript || schedulerScript || feedScript))
{
return false;
}
// trim decorations
char* p;
while (queueEvents && *queueEvents && *(p = queueEvents + strlen(queueEvents) - 1) == '#') *p = '\0';
if (queueEvents) queueEvents = Util::Trim(queueEvents);
while (taskTime && *taskTime && *(p = taskTime + strlen(taskTime) - 1) == '#') *p = '\0';
if (taskTime) taskTime = Util::Trim(taskTime);
script->SetPostScript(postScript);
script->SetScanScript(scanScript);
script->SetQueueScript(queueScript);
script->SetSchedulerScript(schedulerScript);
script->SetFeedScript(feedScript);
script->SetQueueEvents(queueEvents);
script->SetTaskTime(taskTime);
return true;
}
BString<1024> ScriptConfig::BuildScriptName(const char* directory, const char* filename, bool isSubDir)
{
if (isSubDir)
{
BString<1024> directory2 = directory;
int len = strlen(directory2);
if (directory2[len-1] == PATH_SEPARATOR || directory2[len-1] == ALT_PATH_SEPARATOR)
{
// trim last path-separator
directory2[len-1] = '\0';
}
return BString<1024>("%s%c%s", FileSystem::BaseFileName(directory2), PATH_SEPARATOR, filename);
}
else
{
return filename;
}
}
bool ScriptConfig::ScriptExists(Scripts* scripts, const char* scriptName)
{
return std::find_if(scripts->begin(), scripts->end(),
[scriptName](Script& script)
{
return !strcmp(script.GetName(), scriptName);
}) != scripts->end();
}
void ScriptConfig::BuildScriptDisplayNames(Scripts* scripts)
{
// trying to use short name without path and extension.
// if there are other scripts with the same short name - using a longer name instead (with ot without extension)
for (Script& script : scripts)
{
BString<1024> shortName = script.GetName();
if (char* ext = strrchr(shortName, '.')) *ext = '\0'; // strip file extension
const char* displayName = FileSystem::BaseFileName(shortName);
for (Script& script2 : scripts)
{
BString<1024> shortName2 = script2.GetName();
if (char* ext = strrchr(shortName2, '.')) *ext = '\0'; // strip file extension
const char* displayName2 = FileSystem::BaseFileName(shortName2);
if (!strcmp(displayName, displayName2) && script.GetName() != script2.GetName())
{
if (!strcmp(shortName, shortName2))
{
displayName = script.GetName();
}
else
{
displayName = shortName;
}
break;
}
}
script.SetDisplayName(displayName);
}
}
void ScriptConfig::CreateTasks()
{
for (Script& script : m_scripts)
{
if (script.GetSchedulerScript() && !Util::EmptyStr(script.GetTaskTime()))
{
Tokenizer tok(g_Options->GetExtensions(), ",;");
while (const char* scriptName = tok.Next())
{
if (FileSystem::SameFilename(scriptName, script.GetName()))
{
g_Options->CreateSchedulerTask(0, script.GetTaskTime(),
nullptr, Options::scScript, script.GetName());
break;
}
}
}
}
}

View File

@@ -1,110 +0,0 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2013-2016 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/>.
*/
#ifndef SCRIPTCONFIG_H
#define SCRIPTCONFIG_H
#include "NString.h"
#include "Container.h"
#include "Options.h"
class ScriptConfig
{
public:
class Script
{
public:
Script(const char* name, const char* location) :
m_name(name), m_location(location), m_displayName(name) {};
Script(Script&&) = default;
const char* GetName() { return m_name; }
const char* GetLocation() { return m_location; }
void SetDisplayName(const char* displayName) { m_displayName = displayName; }
const char* GetDisplayName() { return m_displayName; }
bool GetPostScript() { return m_postScript; }
void SetPostScript(bool postScript) { m_postScript = postScript; }
bool GetScanScript() { return m_scanScript; }
void SetScanScript(bool scanScript) { m_scanScript = scanScript; }
bool GetQueueScript() { return m_queueScript; }
void SetQueueScript(bool queueScript) { m_queueScript = queueScript; }
bool GetSchedulerScript() { return m_schedulerScript; }
void SetSchedulerScript(bool schedulerScript) { m_schedulerScript = schedulerScript; }
bool GetFeedScript() { return m_feedScript; }
void SetFeedScript(bool feedScript) { m_feedScript = feedScript; }
void SetQueueEvents(const char* queueEvents) { m_queueEvents = queueEvents; }
const char* GetQueueEvents() { return m_queueEvents; }
void SetTaskTime(const char* taskTime) { m_taskTime = taskTime; }
const char* GetTaskTime() { return m_taskTime; }
private:
CString m_name;
CString m_location;
CString m_displayName;
bool m_postScript = false;
bool m_scanScript = false;
bool m_queueScript = false;
bool m_schedulerScript = false;
bool m_feedScript = false;
CString m_queueEvents;
CString m_taskTime;
};
typedef std::list<Script> Scripts;
class ConfigTemplate
{
public:
ConfigTemplate(Script&& script, const char* templ) :
m_script(std::move(script)), m_template(templ) {}
Script* GetScript() { return &m_script; }
const char* GetTemplate() { return m_template; }
private:
Script m_script;
CString m_template;
};
typedef std::deque<ConfigTemplate> ConfigTemplates;
void InitOptions();
Scripts* GetScripts() { return &m_scripts; }
bool LoadConfig(Options::OptEntries* optEntries);
bool SaveConfig(Options::OptEntries* optEntries);
bool LoadConfigTemplates(ConfigTemplates* configTemplates);
ConfigTemplates* GetConfigTemplates() { return &m_configTemplates; }
private:
Scripts m_scripts;
ConfigTemplates m_configTemplates;
void InitScripts();
void InitConfigTemplates();
void CreateTasks();
void LoadScriptDir(Scripts* scripts, const char* directory, bool isSubDir);
void BuildScriptDisplayNames(Scripts* scripts);
void LoadScripts(Scripts* scripts);
bool LoadScriptFile(Script* script);
BString<1024>BuildScriptName(const char* directory, const char* filename, bool isSubDir);
bool ScriptExists(Scripts* scripts, const char* scriptName);
};
extern ScriptConfig* g_ScriptConfig;
#endif

View File

@@ -1,724 +0,0 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2013-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 "FeedCoordinator.h"
#include "Options.h"
#include "WorkState.h"
#include "WebDownloader.h"
#include "Util.h"
#include "FileSystem.h"
#include "FeedFile.h"
#include "FeedFilter.h"
#include "FeedScript.h"
#include "DiskState.h"
#include "DupeCoordinator.h"
#include "UrlCoordinator.h"
std::unique_ptr<RegEx>& FeedCoordinator::FilterHelper::GetRegEx(int id)
{
m_regExes.resize(id);
return m_regExes[id - 1];
}
void FeedCoordinator::FilterHelper::CalcDupeStatus(const char* title, const char* dupeKey, char* statusBuf, int bufLen)
{
const char* dupeStatusName[] = { "", "QUEUED", "DOWNLOADING", "3", "SUCCESS", "5", "6", "7", "WARNING",
"9", "10", "11", "12", "13", "14", "15", "FAILURE" };
DupeCoordinator::EDupeStatus dupeStatus = g_DupeCoordinator->GetDupeStatus(DownloadQueue::Guard(), title, dupeKey);
BString<1024> statuses;
for (int i = 1; i <= (int)DupeCoordinator::dsFailure; i = i << 1)
{
if (dupeStatus & i)
{
if (!statuses.Empty())
{
statuses.Append(",");
}
statuses.Append(dupeStatusName[i]);
}
}
strncpy(statusBuf, statuses, bufLen);
}
FeedCoordinator::FeedCoordinator()
{
debug("Creating FeedCoordinator");
m_downloadQueueObserver.m_owner = this;
DownloadQueue::Guard()->Attach(&m_downloadQueueObserver);
m_workStateObserver.m_owner = this;
g_WorkState->Attach(&m_workStateObserver);
}
FeedCoordinator::~FeedCoordinator()
{
debug("Destroying FeedCoordinator");
for (FeedDownloader* feedDownloader : m_activeDownloads)
{
delete feedDownloader;
}
m_activeDownloads.clear();
}
void FeedCoordinator::Run()
{
debug("Entering FeedCoordinator-loop");
while (!DownloadQueue::IsLoaded())
{
Util::Sleep(20);
}
if (g_Options->GetServerMode())
{
Guard guard(m_downloadsMutex);
g_DiskState->LoadFeeds(&m_feeds, &m_feedHistory);
}
time_t lastCleanup = 0;
while (!IsStopped())
{
// this code should not be called too often, once per second is OK
if (!g_WorkState->GetPauseDownload() || m_force || g_Options->GetUrlForce())
{
Guard guard(m_downloadsMutex);
time_t current = Util::CurrentTime();
if ((int)m_activeDownloads.size() < g_Options->GetUrlConnections())
{
m_force = false;
// check feed list and update feeds
for (FeedInfo* feedInfo : &m_feeds)
{
if (((feedInfo->GetInterval() > 0 &&
(feedInfo->GetNextUpdate() == 0 ||
current >= feedInfo->GetNextUpdate() ||
current < feedInfo->GetNextUpdate() - feedInfo->GetInterval() * 60)) ||
feedInfo->GetFetch()) &&
feedInfo->GetStatus() != FeedInfo::fsRunning)
{
StartFeedDownload(feedInfo, feedInfo->GetFetch());
}
else if (feedInfo->GetFetch())
{
m_force = true;
}
}
}
}
CheckSaveFeeds();
ResetHangingDownloads();
if (std::abs(Util::CurrentTime() - lastCleanup) >= 60)
{
// clean up feed history once a minute
CleanupHistory();
CleanupCache();
CheckSaveFeeds();
lastCleanup = Util::CurrentTime();
}
Guard guard(m_downloadsMutex);
if (m_force)
{
// don't sleep too long if there active feeds scheduled for redownload
m_waitCond.WaitFor(m_downloadsMutex, 1000, [&]{ return IsStopped(); });
}
else
{
// no active jobs, we can sleep longer:
// - if option "UrlForce" is active or if the feed list is empty we need to wake up
// only when a new feed preview is requested. We could wait indefinitely for that
// but we need to do some job every now and then and therefore we sleep only 60 seconds.
// - if option "UrlForce" is disabled we need also to wake up when state "DownloadPaused"
// is changed. We detect this via notification from 'WorkState'. However such
// notifications are not 100% reliable due to possible race conditions. Therefore
// we sleep for max. 5 seconds.
int waitInterval = g_Options->GetUrlForce() || m_feeds.empty() ? 60000 : 5000;
m_waitCond.WaitFor(m_downloadsMutex, waitInterval, [&]{ return m_force || IsStopped(); });
}
}
// waiting for downloads
debug("FeedCoordinator: waiting for Downloads to complete");
bool completed = false;
while (!completed)
{
{
Guard guard(m_downloadsMutex);
completed = m_activeDownloads.size() == 0;
}
CheckSaveFeeds();
Util::Sleep(100);
ResetHangingDownloads();
}
debug("FeedCoordinator: Downloads are completed");
debug("Exiting FeedCoordinator-loop");
}
void FeedCoordinator::Stop()
{
Thread::Stop();
debug("Stopping UrlDownloads");
Guard guard(m_downloadsMutex);
for (FeedDownloader* feedDownloader : m_activeDownloads)
{
feedDownloader->Stop();
}
debug("UrlDownloads are notified");
// Resume Run() to exit it
m_waitCond.NotifyAll();
}
void FeedCoordinator::WorkStateUpdate(Subject* caller, void* aspect)
{
m_force = true;
m_waitCond.NotifyAll();
}
void FeedCoordinator::ResetHangingDownloads()
{
if (g_Options->GetUrlTimeout() == 0)
{
return;
}
Guard guard(m_downloadsMutex);
time_t tm = Util::CurrentTime();
for (FeedDownloader* feedDownloader: m_activeDownloads)
{
if (tm - feedDownloader->GetLastUpdateTime() > g_Options->GetUrlTimeout() + 10 &&
feedDownloader->GetStatus() == FeedDownloader::adRunning)
{
error("Cancelling hanging feed download %s", feedDownloader->GetInfoName());
feedDownloader->Stop();
}
}
}
void FeedCoordinator::LogDebugInfo()
{
info(" ---------- FeedCoordinator");
Guard guard(m_downloadsMutex);
info(" Active Downloads: %i", (int)m_activeDownloads.size());
for (FeedDownloader* feedDownloader : m_activeDownloads)
{
feedDownloader->LogDebugInfo();
}
}
void FeedCoordinator::StartFeedDownload(FeedInfo* feedInfo, bool force)
{
debug("Starting new FeedDownloader for %s", feedInfo->GetName());
FeedDownloader* feedDownloader = new FeedDownloader();
feedDownloader->SetAutoDestroy(true);
feedDownloader->Attach(this);
feedDownloader->SetFeedInfo(feedInfo);
feedDownloader->SetUrl(feedInfo->GetUrl());
feedDownloader->SetInfoName(feedInfo->GetName());
feedDownloader->SetForce(force || g_Options->GetUrlForce());
BString<1024> outFilename;
if (feedInfo->GetId() > 0)
{
outFilename.Format("%s%cfeed-%i.tmp", g_Options->GetTempDir(), PATH_SEPARATOR, feedInfo->GetId());
}
else
{
outFilename.Format("%s%cfeed-%i-%i.tmp", g_Options->GetTempDir(), PATH_SEPARATOR, (int)Util::CurrentTime(), rand());
}
feedDownloader->SetOutputFilename(outFilename);
feedInfo->SetStatus(FeedInfo::fsRunning);
feedInfo->SetForce(force);
feedInfo->SetFetch(false);
m_activeDownloads.push_back(feedDownloader);
feedDownloader->Start();
}
void FeedCoordinator::Update(Subject* caller, void* aspect)
{
debug("Notification from FeedDownloader received");
FeedDownloader* feedDownloader = (FeedDownloader*) caller;
if ((feedDownloader->GetStatus() == WebDownloader::adFinished) ||
(feedDownloader->GetStatus() == WebDownloader::adFailed) ||
(feedDownloader->GetStatus() == WebDownloader::adRetry))
{
FeedCompleted(feedDownloader);
}
}
void FeedCoordinator::FeedCompleted(FeedDownloader* feedDownloader)
{
debug("Feed downloaded");
FeedInfo* feedInfo = feedDownloader->GetFeedInfo();
bool statusOK = feedDownloader->GetStatus() == WebDownloader::adFinished;
if (statusOK)
{
feedInfo->SetOutputFilename(feedDownloader->GetOutputFilename());
}
// remove downloader from downloader list
{
Guard guard(m_downloadsMutex);
m_activeDownloads.erase(std::find(m_activeDownloads.begin(), m_activeDownloads.end(), feedDownloader));
}
SchedulerNextUpdate(feedInfo, statusOK);
if (statusOK)
{
if (!feedInfo->GetPreview())
{
bool scriptSuccess = true;
FeedScriptController::ExecuteScripts(
!Util::EmptyStr(feedInfo->GetExtensions()) ? feedInfo->GetExtensions(): g_Options->GetExtensions(),
feedInfo->GetOutputFilename(), feedInfo->GetId(), &scriptSuccess);
if (!scriptSuccess)
{
feedInfo->SetStatus(FeedInfo::fsFailed);
return;
}
std::unique_ptr<FeedFile> feedFile = parseFeed(feedInfo);
std::vector<std::unique_ptr<NzbInfo>> addedNzbs;
{
Guard guard(m_downloadsMutex);
if (feedFile)
{
std::unique_ptr<FeedItemList> feedItems = feedFile->DetachFeedItems();
addedNzbs = ProcessFeed(feedInfo, feedItems.get());
feedFile.reset();
}
feedInfo->SetLastUpdate(Util::CurrentTime());
feedInfo->SetForce(false);
m_save = true;
}
for (std::unique_ptr<NzbInfo>& nzbInfo : addedNzbs)
{
g_UrlCoordinator->AddUrlToQueue(std::move(nzbInfo), false);
}
}
feedInfo->SetStatus(FeedInfo::fsFinished);
}
else
{
feedInfo->SetStatus(FeedInfo::fsFailed);
}
}
void FeedCoordinator::SchedulerNextUpdate(FeedInfo* feedInfo, bool success)
{
time_t current = Util::CurrentTime();
int interval;
if (success)
{
interval = feedInfo->GetInterval() * 60;
feedInfo->SetLastInterval(0);
}
else
{
// On failure schedule next update sooner:
// starting with 1 minute and increasing, but not greater than FeedX.Interval
interval = feedInfo->GetLastInterval() * 2;
interval = std::max(interval, 60);
interval = std::min(interval, feedInfo->GetInterval() * 60);
feedInfo->SetLastInterval(interval);
}
detail("Scheduling update for feed %s in %i minute(s)", feedInfo->GetName(), interval / 60);
feedInfo->SetNextUpdate(current + interval);
}
void FeedCoordinator::FilterFeed(FeedInfo* feedInfo, FeedItemList* feedItems)
{
debug("Filtering feed %s", feedInfo->GetName());
FilterHelper filterHelper;
std::unique_ptr<FeedFilter> feedFilter;
if (!Util::EmptyStr(feedInfo->GetFilter()))
{
feedFilter = std::make_unique<FeedFilter>(feedInfo->GetFilter());
}
for (FeedItemInfo& feedItemInfo : feedItems)
{
feedItemInfo.SetMatchStatus(FeedItemInfo::msAccepted);
feedItemInfo.SetMatchRule(0);
feedItemInfo.SetPauseNzb(feedInfo->GetPauseNzb());
feedItemInfo.SetPriority(feedInfo->GetPriority());
feedItemInfo.SetAddCategory(feedInfo->GetCategory());
feedItemInfo.SetDupeScore(0);
feedItemInfo.SetDupeMode(dmScore);
feedItemInfo.SetFeedFilterHelper(&filterHelper);
feedItemInfo.BuildDupeKey(nullptr, nullptr, nullptr, nullptr);
if (feedFilter)
{
feedFilter->Match(feedItemInfo);
}
}
}
std::vector<std::unique_ptr<NzbInfo>> FeedCoordinator::ProcessFeed(FeedInfo* feedInfo, FeedItemList* feedItems)
{
debug("Process feed %s", feedInfo->GetName());
FilterFeed(feedInfo, feedItems);
std::vector<std::unique_ptr<NzbInfo>> addedNzbs;
bool firstFetch = feedInfo->GetLastUpdate() == 0;
int added = 0;
for (FeedItemInfo& feedItemInfo : feedItems)
{
if (feedItemInfo.GetMatchStatus() == FeedItemInfo::msAccepted)
{
FeedHistoryInfo* feedHistoryInfo = m_feedHistory.Find(feedItemInfo.GetUrl());
FeedHistoryInfo::EStatus status = FeedHistoryInfo::hsUnknown;
if (firstFetch && feedInfo->GetBacklog())
{
status = FeedHistoryInfo::hsBacklog;
}
else if (!feedHistoryInfo)
{
addedNzbs.push_back(CreateNzbInfo(feedInfo, feedItemInfo));
status = FeedHistoryInfo::hsFetched;
added++;
}
if (feedHistoryInfo)
{
feedHistoryInfo->SetLastSeen(Util::CurrentTime());
}
else
{
m_feedHistory.emplace_back(feedItemInfo.GetUrl(), status, Util::CurrentTime());
}
}
}
if (added)
{
info("%s has %i new item(s)", feedInfo->GetName(), added);
}
else
{
detail("%s has no new items", feedInfo->GetName());
}
return addedNzbs;
}
std::unique_ptr<NzbInfo> FeedCoordinator::CreateNzbInfo(FeedInfo* feedInfo, FeedItemInfo& feedItemInfo)
{
debug("Download %s from %s", feedItemInfo.GetUrl(), feedInfo->GetName());
std::unique_ptr<NzbInfo> nzbInfo = std::make_unique<NzbInfo>();
nzbInfo->SetKind(NzbInfo::nkUrl);
nzbInfo->SetFeedId(feedInfo->GetId());
nzbInfo->SetUrl(feedItemInfo.GetUrl());
// add .nzb-extension if not present
BString<1024> nzbName = feedItemInfo.GetFilename();
char* ext = strrchr(nzbName, '.');
if (ext && !strcasecmp(ext, ".nzb"))
{
*ext = '\0';
}
if (!nzbName.Empty())
{
BString<1024> nzbName2("%s.nzb", *nzbName);
nzbInfo->SetFilename(FileSystem::MakeValidFilename(nzbName2));
}
nzbInfo->SetCategory(feedItemInfo.GetAddCategory());
nzbInfo->SetPriority(feedItemInfo.GetPriority());
nzbInfo->SetAddUrlPaused(feedItemInfo.GetPauseNzb());
nzbInfo->SetDupeKey(feedItemInfo.GetDupeKey());
nzbInfo->SetDupeScore(feedItemInfo.GetDupeScore());
nzbInfo->SetDupeMode(feedItemInfo.GetDupeMode());
nzbInfo->SetSize(feedItemInfo.GetSize());
nzbInfo->SetMinTime(feedItemInfo.GetTime());
nzbInfo->SetMaxTime(feedItemInfo.GetTime());
return nzbInfo;
}
std::shared_ptr<FeedItemList> FeedCoordinator::ViewFeed(int id)
{
if (id < 1 || id > (int)m_feeds.size())
{
return nullptr;
}
std::unique_ptr<FeedInfo>& feedInfo = m_feeds[id - 1];
return PreviewFeed(feedInfo->GetId(), feedInfo->GetName(), feedInfo->GetUrl(), feedInfo->GetFilter(),
feedInfo->GetBacklog(), feedInfo->GetPauseNzb(), feedInfo->GetCategory(),
feedInfo->GetPriority(), feedInfo->GetInterval(), feedInfo->GetExtensions(), 0, nullptr);
}
std::shared_ptr<FeedItemList> FeedCoordinator::PreviewFeed(int id,
const char* name, const char* url, const char* filter, bool backlog, bool pauseNzb,
const char* category, int priority, int interval, const char* feedScript,
int cacheTimeSec, const char* cacheId)
{
debug("Preview feed %s", name);
std::unique_ptr<FeedInfo> feedInfo = std::make_unique<FeedInfo>(id, name, url, backlog, interval,
filter, pauseNzb, category, priority, feedScript);
feedInfo->SetPreview(true);
std::shared_ptr<FeedItemList> feedItems;
bool hasCache = false;
if (cacheTimeSec > 0 && *cacheId != '\0')
{
Guard guard(m_downloadsMutex);
for (FeedCacheItem& feedCacheItem : m_feedCache)
{
if (!strcmp(feedCacheItem.GetCacheId(), cacheId))
{
feedCacheItem.SetLastUsage(Util::CurrentTime());
feedItems = feedCacheItem.GetFeedItems();
hasCache = true;
break;
}
}
}
if (!hasCache)
{
bool firstFetch = true;
{
Guard guard(m_downloadsMutex);
for (FeedInfo* feedInfo2 : &m_feeds)
{
if (!strcmp(feedInfo2->GetUrl(), feedInfo->GetUrl()) &&
!strcmp(feedInfo2->GetFilter(), feedInfo->GetFilter()) &&
feedInfo2->GetLastUpdate() > 0)
{
firstFetch = false;
break;
}
}
StartFeedDownload(feedInfo.get(), true);
m_force = true;
m_waitCond.NotifyAll();
}
// wait until the download in a separate thread completes
while (feedInfo->GetStatus() == FeedInfo::fsRunning)
{
Util::Sleep(100);
}
// now can process the feed
if (feedInfo->GetStatus() != FeedInfo::fsFinished)
{
return nullptr;
}
FeedScriptController::ExecuteScripts(
!Util::EmptyStr(feedInfo->GetExtensions()) ? feedInfo->GetExtensions(): g_Options->GetExtensions(),
feedInfo->GetOutputFilename(), feedInfo->GetId(), nullptr);
std::unique_ptr<FeedFile> feedFile = parseFeed(feedInfo.get());
if (!feedFile)
{
return nullptr;
}
feedItems = feedFile->DetachFeedItems();
feedFile.reset();
for (FeedItemInfo& feedItemInfo : feedItems.get())
{
feedItemInfo.SetStatus(firstFetch && feedInfo->GetBacklog() ? FeedItemInfo::isBacklog : FeedItemInfo::isNew);
FeedHistoryInfo* feedHistoryInfo = m_feedHistory.Find(feedItemInfo.GetUrl());
if (feedHistoryInfo)
{
feedItemInfo.SetStatus((FeedItemInfo::EStatus)feedHistoryInfo->GetStatus());
}
}
}
FilterFeed(feedInfo.get(), feedItems.get());
feedInfo.reset();
if (cacheTimeSec > 0 && *cacheId != '\0' && !hasCache)
{
Guard guard(m_downloadsMutex);
m_feedCache.emplace_back(url, cacheTimeSec, cacheId, Util::CurrentTime(), feedItems);
}
return feedItems;
}
void FeedCoordinator::FetchFeed(int id)
{
debug("FetchFeeds");
Guard guard(m_downloadsMutex);
for (FeedInfo* feedInfo : &m_feeds)
{
if (feedInfo->GetId() == id || id == 0)
{
feedInfo->SetFetch(true);
m_force = true;
}
}
m_waitCond.NotifyAll();
}
std::unique_ptr<FeedFile> FeedCoordinator::parseFeed(FeedInfo* feedInfo)
{
std::unique_ptr<FeedFile> feedFile = std::make_unique<FeedFile>(feedInfo->GetOutputFilename(), feedInfo->GetName());
if (feedFile->Parse())
{
FileSystem::DeleteFile(feedInfo->GetOutputFilename());
}
else
{
error("Feed file %s kept for troubleshooting (will be deleted on next successful feed fetch)", feedInfo->GetOutputFilename());
feedFile.reset();
}
return feedFile;
}
void FeedCoordinator::DownloadQueueUpdate(Subject* caller, void* aspect)
{
debug("Notification from URL-Coordinator received");
DownloadQueue::Aspect* queueAspect = (DownloadQueue::Aspect*)aspect;
if (queueAspect->action == DownloadQueue::eaUrlCompleted)
{
Guard guard(m_downloadsMutex);
FeedHistoryInfo* feedHistoryInfo = m_feedHistory.Find(queueAspect->nzbInfo->GetUrl());
if (feedHistoryInfo)
{
feedHistoryInfo->SetStatus(FeedHistoryInfo::hsFetched);
}
else
{
m_feedHistory.emplace_back(queueAspect->nzbInfo->GetUrl(), FeedHistoryInfo::hsFetched, Util::CurrentTime());
}
m_save = true;
}
}
bool FeedCoordinator::HasActiveDownloads()
{
Guard guard(m_downloadsMutex);
return !m_activeDownloads.empty();
}
void FeedCoordinator::CheckSaveFeeds()
{
Guard guard(m_downloadsMutex);
if (m_save)
{
debug("CheckSaveFeeds: save");
if (g_Options->GetServerMode())
{
g_DiskState->SaveFeeds(&m_feeds, &m_feedHistory);
}
m_save = false;
}
}
void FeedCoordinator::CleanupHistory()
{
debug("CleanupHistory");
Guard guard(m_downloadsMutex);
time_t oldestUpdate = Util::CurrentTime();
for (FeedInfo* feedInfo : &m_feeds)
{
if (feedInfo->GetLastUpdate() < oldestUpdate)
{
oldestUpdate = feedInfo->GetLastUpdate();
}
}
time_t borderDate = oldestUpdate - g_Options->GetFeedHistory() * 60*60*24;
m_feedHistory.erase(std::remove_if(m_feedHistory.begin(), m_feedHistory.end(),
[borderDate, this](FeedHistoryInfo& feedHistoryInfo)
{
if (feedHistoryInfo.GetLastSeen() < borderDate)
{
detail("Deleting %s from feed history", feedHistoryInfo.GetUrl());
m_save = true;
return true;
}
return false;
}),
m_feedHistory.end());
}
void FeedCoordinator::CleanupCache()
{
debug("CleanupCache");
Guard guard(m_downloadsMutex);
time_t curTime = Util::CurrentTime();
m_feedCache.remove_if(
[curTime](FeedCacheItem& feedCacheItem)
{
if (feedCacheItem.GetLastUsage() + feedCacheItem.GetCacheTimeSec() < curTime ||
feedCacheItem.GetLastUsage() > curTime)
{
debug("Deleting %s from feed cache", feedCacheItem.GetUrl());
return true;
}
return false;
});
}

View File

@@ -1,150 +0,0 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2013-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/>.
*/
#ifndef FEEDCOORDINATOR_H
#define FEEDCOORDINATOR_H
#include "NString.h"
#include "Log.h"
#include "Thread.h"
#include "WebDownloader.h"
#include "DownloadInfo.h"
#include "FeedFile.h"
#include "FeedInfo.h"
#include "Observer.h"
#include "Util.h"
class FeedDownloader;
class FeedCoordinator : public Thread, public Observer, public Subject, public Debuggable
{
public:
FeedCoordinator();
virtual ~FeedCoordinator();
virtual void Run();
virtual void Stop();
void Update(Subject* caller, void* aspect);
void AddFeed(std::unique_ptr<FeedInfo> feedInfo) { m_feeds.push_back(std::move(feedInfo)); }
/* may return empty pointer on error */
std::shared_ptr<FeedItemList> PreviewFeed(int id, const char* name, const char* url,
const char* filter, bool backlog, bool pauseNzb, const char* category, int priority,
int interval, const char* feedScript, int cacheTimeSec, const char* cacheId);
/* may return empty pointer on error */
std::shared_ptr<FeedItemList> ViewFeed(int id);
void FetchFeed(int id);
bool HasActiveDownloads();
Feeds* GetFeeds() { return &m_feeds; }
protected:
virtual void LogDebugInfo();
private:
class DownloadQueueObserver: public Observer
{
public:
FeedCoordinator* m_owner;
virtual void Update(Subject* caller, void* aspect) { m_owner->DownloadQueueUpdate(caller, aspect); }
};
class WorkStateObserver: public Observer
{
public:
FeedCoordinator* m_owner;
virtual void Update(Subject* caller, void* aspect) { m_owner->WorkStateUpdate(caller, aspect); }
};
class FeedCacheItem
{
public:
FeedCacheItem(const char* url, int cacheTimeSec,const char* cacheId,
time_t lastUsage, std::shared_ptr<FeedItemList> feedItems) :
m_url(url), m_cacheTimeSec(cacheTimeSec), m_cacheId(cacheId),
m_lastUsage(lastUsage), m_feedItems(feedItems) {}
const char* GetUrl() { return m_url; }
int GetCacheTimeSec() { return m_cacheTimeSec; }
const char* GetCacheId() { return m_cacheId; }
time_t GetLastUsage() { return m_lastUsage; }
void SetLastUsage(time_t lastUsage) { m_lastUsage = lastUsage; }
std::shared_ptr<FeedItemList> GetFeedItems() { return m_feedItems; }
private:
CString m_url;
int m_cacheTimeSec;
CString m_cacheId;
time_t m_lastUsage;
std::shared_ptr<FeedItemList> m_feedItems;
};
class FilterHelper : public FeedFilterHelper
{
public:
virtual std::unique_ptr<RegEx>& GetRegEx(int id);
virtual void CalcDupeStatus(const char* title, const char* dupeKey, char* statusBuf, int bufLen);
private:
std::vector<std::unique_ptr<RegEx>> m_regExes;
};
typedef std::list<FeedCacheItem> FeedCache;
typedef std::deque<FeedDownloader*> ActiveDownloads;
Feeds m_feeds;
ActiveDownloads m_activeDownloads;
FeedHistory m_feedHistory;
Mutex m_downloadsMutex;
DownloadQueueObserver m_downloadQueueObserver;
WorkStateObserver m_workStateObserver;
bool m_force = false;
bool m_save = false;
FeedCache m_feedCache;
ConditionVar m_waitCond;
bool m_wokenUp = false;
void StartFeedDownload(FeedInfo* feedInfo, bool force);
void FeedCompleted(FeedDownloader* feedDownloader);
void FilterFeed(FeedInfo* feedInfo, FeedItemList* feedItems);
std::vector<std::unique_ptr<NzbInfo>> ProcessFeed(FeedInfo* feedInfo, FeedItemList* feedItems);
std::unique_ptr<NzbInfo> CreateNzbInfo(FeedInfo* feedInfo, FeedItemInfo& feedItemInfo);
void ResetHangingDownloads();
void DownloadQueueUpdate(Subject* caller, void* aspect);
void CleanupHistory();
void CleanupCache();
void CheckSaveFeeds();
std::unique_ptr<FeedFile> parseFeed(FeedInfo* feedInfo);
void SchedulerNextUpdate(FeedInfo* feedInfo, bool success);
void WorkStateUpdate(Subject* caller, void* aspect);
};
extern FeedCoordinator* g_FeedCoordinator;
class FeedDownloader : public WebDownloader
{
public:
void SetFeedInfo(FeedInfo* feedInfo) { m_feedInfo = feedInfo; }
FeedInfo* GetFeedInfo() { return m_feedInfo; }
private:
FeedInfo* m_feedInfo;
};
#endif

View File

@@ -1,607 +0,0 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2013-2016 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 "FeedFile.h"
#include "Log.h"
#include "DownloadInfo.h"
#include "Options.h"
#include "Util.h"
FeedFile::FeedFile(const char* fileName, const char* infoName) :
m_fileName(fileName), m_infoName(infoName)
{
debug("Creating FeedFile");
m_feedItems = std::make_unique<FeedItemList>();
#ifndef WIN32
m_feedItemInfo = nullptr;
m_tagContent.Clear();
#endif
}
void FeedFile::LogDebugInfo()
{
info(" FeedFile %s", *m_fileName);
}
void FeedFile::ParseSubject(FeedItemInfo& feedItemInfo)
{
// if title has quatation marks we use only part within quatation marks
char* p = (char*)feedItemInfo.GetTitle();
char* start = strchr(p, '\"');
if (start)
{
start++;
char* end = strchr(start + 1, '\"');
if (end)
{
int len = (int)(end - start);
char* point = strchr(start + 1, '.');
if (point && point < end)
{
CString filename(start, len);
char* ext = strrchr(filename, '.');
if (ext && !strcasecmp(ext, ".par2"))
{
*ext = '\0';
}
feedItemInfo.SetFilename(filename);
return;
}
}
}
feedItemInfo.SetFilename(feedItemInfo.GetTitle());
}
#ifdef WIN32
bool FeedFile::Parse()
{
CoInitialize(nullptr);
HRESULT hr;
MSXML::IXMLDOMDocumentPtr doc;
hr = doc.CreateInstance(MSXML::CLSID_DOMDocument);
if (FAILED(hr))
{
return false;
}
// Load the XML document file...
doc->put_resolveExternals(VARIANT_FALSE);
doc->put_validateOnParse(VARIANT_FALSE);
doc->put_async(VARIANT_FALSE);
_variant_t vFilename(*WString(m_fileName));
// 1. first trying to load via filename without URL-encoding (certain charaters doesn't work when encoded)
VARIANT_BOOL success = doc->load(vFilename);
if (success == VARIANT_FALSE)
{
// 2. now trying filename encoded as URL
char url[2048];
EncodeUrl(m_fileName, url, 2048);
debug("url=\"%s\"", url);
_variant_t vUrl(url);
success = doc->load(vUrl);
}
if (success == VARIANT_FALSE)
{
_bstr_t r(doc->GetparseError()->reason);
const char* errMsg = r;
error("Error parsing rss feed %s: %s", *m_infoName, errMsg);
return false;
}
bool ok = ParseFeed(doc);
return ok;
}
void FeedFile::EncodeUrl(const char* filename, char* url, int bufLen)
{
WString widefilename(filename);
char* end = url + bufLen;
for (wchar_t* p = widefilename; *p && url < end - 3; p++)
{
wchar_t ch = *p;
if (('0' <= ch && ch <= '9') ||
('a' <= ch && ch <= 'z') ||
('A' <= ch && ch <= 'Z') ||
ch == '-' || ch == '.' || ch == '_' || ch == '~')
{
*url++ = (char)ch;
}
else
{
*url++ = '%';
uint32 a = (uint32)ch >> 4;
*url++ = a > 9 ? a - 10 + 'A' : a + '0';
a = ch & 0xF;
*url++ = a > 9 ? a - 10 + 'A' : a + '0';
}
}
*url = '\0';
}
bool FeedFile::ParseFeed(IUnknown* nzb)
{
MSXML::IXMLDOMDocumentPtr doc = nzb;
MSXML::IXMLDOMNodePtr root = doc->documentElement;
MSXML::IXMLDOMNodeListPtr itemList = root->selectNodes("/rss/channel/item");
for (int i = 0; i < itemList->Getlength(); i++)
{
MSXML::IXMLDOMNodePtr node = itemList->Getitem(i);
m_feedItems->emplace_back();
FeedItemInfo& feedItemInfo = m_feedItems->back();
MSXML::IXMLDOMNodePtr tag;
MSXML::IXMLDOMNodePtr attr;
// <title>Debian 6</title>
tag = node->selectSingleNode("title");
if (!tag)
{
// bad rss feed
return false;
}
_bstr_t title(tag->Gettext());
feedItemInfo.SetTitle(title);
ParseSubject(feedItemInfo);
// <pubDate>Wed, 26 Jun 2013 00:02:54 -0600</pubDate>
tag = node->selectSingleNode("pubDate");
if (tag)
{
_bstr_t time(tag->Gettext());
time_t unixtime = WebUtil::ParseRfc822DateTime(time);
if (unixtime > 0)
{
feedItemInfo.SetTime(unixtime);
}
}
// <category>Movies &gt; HD</category>
tag = node->selectSingleNode("category");
if (tag)
{
_bstr_t category(tag->Gettext());
feedItemInfo.SetCategory(category);
}
// <description>long text</description>
tag = node->selectSingleNode("description");
if (tag)
{
_bstr_t bdescription(tag->Gettext());
// cleanup CDATA
CString description = (const char*)bdescription;
WebUtil::XmlStripTags(description);
WebUtil::XmlDecode(description);
WebUtil::XmlRemoveEntities(description);
feedItemInfo.SetDescription(description);
}
//<enclosure url="http://myindexer.com/fetch/9eeb264aecce961a6e0d" length="150263340" type="application/x-nzb" />
tag = node->selectSingleNode("enclosure");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("url");
if (attr)
{
_bstr_t url(attr->Gettext());
feedItemInfo.SetUrl(url);
}
attr = tag->Getattributes()->getNamedItem("length");
if (attr)
{
_bstr_t bsize(attr->Gettext());
int64 size = atoll(bsize);
feedItemInfo.SetSize(size);
}
}
if (!feedItemInfo.GetUrl())
{
// <link>https://nzb.org/fetch/334534ce/4364564564</link>
tag = node->selectSingleNode("link");
if (!tag)
{
// bad rss feed
return false;
}
_bstr_t link(tag->Gettext());
feedItemInfo.SetUrl(link);
}
// newznab special
//<newznab:attr name="size" value="5423523453534" />
if (feedItemInfo.GetSize() == 0)
{
tag = node->selectSingleNode("newznab:attr[@name='size'] | nZEDb:attr[@name='size']");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("value");
if (attr)
{
_bstr_t bsize(attr->Gettext());
int64 size = atoll(bsize);
feedItemInfo.SetSize(size);
}
}
}
//<newznab:attr name="imdb" value="1588173"/>
tag = node->selectSingleNode("newznab:attr[@name='imdb'] | nZEDb:attr[@name='imdb']");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("value");
if (attr)
{
_bstr_t bval(attr->Gettext());
int val = atoi(bval);
feedItemInfo.SetImdbId(val);
}
}
//<newznab:attr name="rageid" value="33877"/>
tag = node->selectSingleNode("newznab:attr[@name='rageid'] | nZEDb:attr[@name='rageid']");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("value");
if (attr)
{
_bstr_t bval(attr->Gettext());
int val = atoi(bval);
feedItemInfo.SetRageId(val);
}
}
//<newznab:attr name="tdvdbid" value="33877"/>
tag = node->selectSingleNode("newznab:attr[@name='tvdbid'] | nZEDb:attr[@name='tvdbid']");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("value");
if (attr)
{
_bstr_t bval(attr->Gettext());
int val = atoi(bval);
feedItemInfo.SetTvdbId(val);
}
}
//<newznab:attr name="tvmazeid" value="33877"/>
tag = node->selectSingleNode("newznab:attr[@name='tvmazeid'] | nZEDb:attr[@name='tvmazeid']");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("value");
if (attr)
{
_bstr_t bval(attr->Gettext());
int val = atoi(bval);
feedItemInfo.SetTvmazeId(val);
}
}
//<newznab:attr name="episode" value="E09"/>
//<newznab:attr name="episode" value="9"/>
tag = node->selectSingleNode("newznab:attr[@name='episode'] | nZEDb:attr[@name='episode']");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("value");
if (attr)
{
_bstr_t val(attr->Gettext());
feedItemInfo.SetEpisode(val);
}
}
//<newznab:attr name="season" value="S03"/>
//<newznab:attr name="season" value="3"/>
tag = node->selectSingleNode("newznab:attr[@name='season'] | nZEDb:attr[@name='season']");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("value");
if (attr)
{
_bstr_t val(attr->Gettext());
feedItemInfo.SetSeason(val);
}
}
MSXML::IXMLDOMNodeListPtr itemList = node->selectNodes("newznab:attr | nZEDb:attr");
for (int i = 0; i < itemList->Getlength(); i++)
{
MSXML::IXMLDOMNodePtr node = itemList->Getitem(i);
MSXML::IXMLDOMNodePtr name = node->Getattributes()->getNamedItem("name");
MSXML::IXMLDOMNodePtr value = node->Getattributes()->getNamedItem("value");
if (name && value)
{
_bstr_t bname(name->Gettext());
_bstr_t bval(value->Gettext());
feedItemInfo.GetAttributes()->emplace_back(bname, bval);
}
}
}
return true;
}
#else
bool FeedFile::Parse()
{
#ifdef DISABLE_LIBXML2
error("Could not parse rss feed, program was compiled without libxml2 support");
return false;
#else
xmlSAXHandler SAX_handler = {0};
SAX_handler.startElement = reinterpret_cast<startElementSAXFunc>(SAX_StartElement);
SAX_handler.endElement = reinterpret_cast<endElementSAXFunc>(SAX_EndElement);
SAX_handler.characters = reinterpret_cast<charactersSAXFunc>(SAX_characters);
SAX_handler.error = reinterpret_cast<errorSAXFunc>(SAX_error);
SAX_handler.getEntity = reinterpret_cast<getEntitySAXFunc>(SAX_getEntity);
m_ignoreNextError = false;
int ret = xmlSAXUserParseFile(&SAX_handler, this, m_fileName);
if (ret != 0)
{
error("Failed to parse rss feed %s", *m_infoName);
return false;
}
return true;
#endif
}
void FeedFile::Parse_StartElement(const char *name, const char **atts)
{
ResetTagContent();
if (!strcmp("item", name))
{
m_feedItems->emplace_back();
m_feedItemInfo = &m_feedItems->back();
}
else if (!strcmp("enclosure", name) && m_feedItemInfo)
{
//<enclosure url="http://myindexer.com/fetch/9eeb264aecce961a6e0d" length="150263340" type="application/x-nzb" />
for (; *atts; atts+=2)
{
if (!strcmp("url", atts[0]))
{
CString url = atts[1];
WebUtil::XmlDecode(url);
m_feedItemInfo->SetUrl(url);
}
else if (!strcmp("length", atts[0]))
{
int64 size = atoll(atts[1]);
m_feedItemInfo->SetSize(size);
}
}
}
else if (m_feedItemInfo &&
(!strcmp("newznab:attr", name) || !strcmp("nZEDb:attr", name)) &&
atts[0] && atts[1] && atts[2] && atts[3] &&
!strcmp("name", atts[0]) && !strcmp("value", atts[2]))
{
m_feedItemInfo->GetAttributes()->emplace_back(atts[1], atts[3]);
//<newznab:attr name="size" value="5423523453534" />
if (m_feedItemInfo->GetSize() == 0 &&
!strcmp("size", atts[1]))
{
int64 size = atoll(atts[3]);
m_feedItemInfo->SetSize(size);
}
//<newznab:attr name="imdb" value="1588173"/>
else if (!strcmp("imdb", atts[1]))
{
m_feedItemInfo->SetImdbId(atoi(atts[3]));
}
//<newznab:attr name="rageid" value="33877"/>
else if (!strcmp("rageid", atts[1]))
{
m_feedItemInfo->SetRageId(atoi(atts[3]));
}
//<newznab:attr name="tvdbid" value="33877"/>
else if (!strcmp("tvdbid", atts[1]))
{
m_feedItemInfo->SetTvdbId(atoi(atts[3]));
}
//<newznab:attr name="tvmazeid" value="33877"/>
else if (!strcmp("tvmazeid", atts[1]))
{
m_feedItemInfo->SetTvmazeId(atoi(atts[3]));
}
//<newznab:attr name="episode" value="E09"/>
//<newznab:attr name="episode" value="9"/>
else if (!strcmp("episode", atts[1]))
{
m_feedItemInfo->SetEpisode(atts[3]);
}
//<newznab:attr name="season" value="S03"/>
//<newznab:attr name="season" value="3"/>
else if (!strcmp("season", atts[1]))
{
m_feedItemInfo->SetSeason(atts[3]);
}
}
}
void FeedFile::Parse_EndElement(const char *name)
{
if (!strcmp("title", name) && m_feedItemInfo)
{
m_feedItemInfo->SetTitle(m_tagContent);
ResetTagContent();
ParseSubject(*m_feedItemInfo);
}
else if (!strcmp("link", name) && m_feedItemInfo &&
(!m_feedItemInfo->GetUrl() || strlen(m_feedItemInfo->GetUrl()) == 0))
{
m_feedItemInfo->SetUrl(m_tagContent);
ResetTagContent();
}
else if (!strcmp("category", name) && m_feedItemInfo)
{
m_feedItemInfo->SetCategory(m_tagContent);
ResetTagContent();
}
else if (!strcmp("description", name) && m_feedItemInfo)
{
// cleanup CDATA
CString description = *m_tagContent;
WebUtil::XmlStripTags(description);
WebUtil::XmlDecode(description);
WebUtil::XmlRemoveEntities(description);
m_feedItemInfo->SetDescription(description);
ResetTagContent();
}
else if (!strcmp("pubDate", name) && m_feedItemInfo)
{
time_t unixtime = WebUtil::ParseRfc822DateTime(m_tagContent);
if (unixtime > 0)
{
m_feedItemInfo->SetTime(unixtime);
}
ResetTagContent();
}
}
void FeedFile::Parse_Content(const char *buf, int len)
{
m_tagContent.Append(buf, len);
}
void FeedFile::ResetTagContent()
{
m_tagContent.Clear();
}
void FeedFile::SAX_StartElement(FeedFile* file, const char *name, const char **atts)
{
file->Parse_StartElement(name, atts);
}
void FeedFile::SAX_EndElement(FeedFile* file, const char *name)
{
file->Parse_EndElement(name);
}
void FeedFile::SAX_characters(FeedFile* file, const char * xmlstr, int len)
{
char* str = (char*)xmlstr;
// trim starting blanks
int off = 0;
for (int i = 0; i < len; i++)
{
char ch = str[i];
if (ch == ' ' || ch == 10 || ch == 13 || ch == 9)
{
off++;
}
else
{
break;
}
}
int newlen = len - off;
// trim ending blanks
for (int i = len - 1; i >= off; i--)
{
char ch = str[i];
if (ch == ' ' || ch == 10 || ch == 13 || ch == 9)
{
newlen--;
}
else
{
break;
}
}
if (newlen > 0)
{
// interpret tag content
file->Parse_Content(str + off, newlen);
}
}
void* FeedFile::SAX_getEntity(FeedFile* file, const char * name)
{
#ifdef DISABLE_LIBXML2
void* e = nullptr;
#else
xmlEntityPtr e = xmlGetPredefinedEntity((xmlChar* )name);
#endif
if (!e)
{
warn("entity not found");
file->m_ignoreNextError = true;
}
return e;
}
void FeedFile::SAX_error(FeedFile* file, const char *msg, ...)
{
if (file->m_ignoreNextError)
{
file->m_ignoreNextError = false;
return;
}
va_list argp;
va_start(argp, msg);
char errMsg[1024];
vsnprintf(errMsg, sizeof(errMsg), msg, argp);
errMsg[1024-1] = '\0';
va_end(argp);
// remove trailing CRLF
for (char* pend = errMsg + strlen(errMsg) - 1; pend >= errMsg && (*pend == '\n' || *pend == '\r' || *pend == ' '); pend--) *pend = '\0';
error("Error parsing rss feed %s: %s", *file->m_infoName, errMsg);
}
#endif

View File

@@ -1,62 +0,0 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2013-2016 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/>.
*/
#ifndef FEEDFILE_H
#define FEEDFILE_H
#include "NString.h"
#include "FeedInfo.h"
class FeedFile
{
public:
FeedFile(const char* fileName, const char* infoName);
bool Parse();
std::unique_ptr<FeedItemList> DetachFeedItems() { return std::move(m_feedItems); }
void LogDebugInfo();
private:
std::unique_ptr<FeedItemList> m_feedItems;
CString m_fileName;
CString m_infoName;
void ParseSubject(FeedItemInfo& feedItemInfo);
#ifdef WIN32
bool ParseFeed(IUnknown* nzb);
static void EncodeUrl(const char* filename, char* url, int bufLen);
#else
FeedItemInfo* m_feedItemInfo;
StringBuilder m_tagContent;
bool m_ignoreNextError;
static void SAX_StartElement(FeedFile* file, const char *name, const char **atts);
static void SAX_EndElement(FeedFile* file, const char *name);
static void SAX_characters(FeedFile* file, const char * xmlstr, int len);
static void* SAX_getEntity(FeedFile* file, const char * name);
static void SAX_error(FeedFile* file, const char *msg, ...);
void Parse_StartElement(const char *name, const char **atts);
void Parse_EndElement(const char *name);
void Parse_Content(const char *buf, int len);
void ResetTagContent();
#endif
};
#endif

View File

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More