Compare commits

...

84 Commits

Author SHA1 Message Date
Andrey Prygunkov
fee7819f96 version 0.3.1 2008-02-01 17:22:09 +00:00
Andrey Prygunkov
0204d0849d updated libpar2-patch for msvc to fix a memory leak in libpar2 (windows only) 2008-01-31 19:07:30 +00:00
Andrey Prygunkov
179f9e9ed6 fixed a memory leak in nzb-parser on windows 2008-01-31 19:02:44 +00:00
Andrey Prygunkov
d5b0a8dd8d fixed a memory leak in thread-management-code on windows 2008-01-31 18:58:41 +00:00
Andrey Prygunkov
4744559b46 removed <-testing> from version-string (prepare for release 0.3.1) 2008-01-30 17:06:20 +00:00
Andrey Prygunkov
51b6549671 fixed: compilation error after configuring with <--disable-parcheck> 2008-01-30 17:04:40 +00:00
Andrey Prygunkov
ec61b13269 fixed: trying to move the last group in queue down results in run-time error 2008-01-28 22:04:09 +00:00
Andrey Prygunkov
d898fff913 fixed: long lines (by bad articles) result in seg.fault 2008-01-28 18:29:13 +00:00
Andrey Prygunkov
fc19d48538 added the removing of trailing dot-characters from generated directory names to provide better compatibility with windows 2008-01-24 18:56:03 +00:00
Andrey Prygunkov
fe512e830c added options <NzbDirInterval> and <NzbDirFileAge> to adjust interval and delay by monitoring of incoming-directory for new nzb-files 2008-01-23 21:26:54 +00:00
Andrey Prygunkov
21be45e89a added remote-command <-V> (--serverversion) to print the server's version 2008-01-23 18:17:00 +00:00
Andrey Prygunkov
1eddc76630 fixed: by deleting files from queue the already downloaded articles were not deleted from temp-directory 2008-01-22 18:32:08 +00:00
Andrey Prygunkov
c4cc0cb745 added option <WriteBufferSize> to slightly reduce disk-io (by using of few megabytes of memory) 2008-01-22 18:29:28 +00:00
Andrey Prygunkov
1944ea7273 added gathering of statistical data: uptime, download-time, amount of downloaded data and average session download speed; extended communication protocol to transfer these data; extended ncurses-outputmode to print uptime, download-time and average download speed 2008-01-21 16:58:49 +00:00
Andrey Prygunkov
7da406ed4f removed superfluous trailing CR-character from one warning-message and reformatted few other messages 2008-01-16 18:21:11 +00:00
Andrey Prygunkov
6a0c1031fa added check for message-id of article returned from server, this improves error-detection for bad articles 2008-01-14 19:44:56 +00:00
Andrey Prygunkov
15b016ef7b extended response-messages in communication protocol for better error-reporting on client-side 2008-01-14 19:36:43 +00:00
Andrey Prygunkov
63b8c11ab5 imporved error reporting in remote server on binding errors (previously they were reported only with debug-binaries); fixed: remote server was listening to all interfaces regardless of option <serverip>, now it listen only to the specified address, but the old behaviour is also possible with address <0.0.0.0>, which means <all interfaces> 2008-01-13 17:09:53 +00:00
Andrey Prygunkov
5ca2279af8 improved the download speed meter, new implementation is much more accurate, especially on fast connections; this also means better speed throttling 2008-01-12 18:29:47 +00:00
Andrey Prygunkov
d8cf6263de eliminated few compiler warnings under gcc 2008-01-11 17:36:57 +00:00
Andrey Prygunkov
ebdd0fd1b8 fixed: it was generated way too many debug-messages in ServerPool, even in StandBy-mode 2008-01-11 16:42:28 +00:00
Andrey Prygunkov
9fa4cace8e improved duplicate check: it now works even for files, which subjects could not be parsed - though these files are not detected as duplicates by adding to queue, they will be detected after correct filename is read from the first article's body 2008-01-10 22:05:17 +00:00
Andrey Prygunkov
27d5f058eb better handling of cancelled downloads (important for keeped-alive connections) 2008-01-10 20:55:08 +00:00
Andrey Prygunkov
849db2a9dc authorization errors are now handled like connect errors and not counted as retries (this might cause the lost of articles under heavy load of newsserver) 2008-01-10 18:41:23 +00:00
Andrey Prygunkov
71531b0077 fixed: SmartOrder did not work by moving the files to the top of queue; fixed: DupeCheck did not check files on disk 2008-01-10 18:19:42 +00:00
Andrey Prygunkov
b254a6b6c9 added edit-commands for <pause all pars> and <pause extra pars> (works best on groups but also with file ranges); the key <P> in curses-outputmode in group-view now switches between three states: pause extra pars -> pause all files -> unpause all files; updated ChangeLog and README 2008-01-09 17:45:11 +00:00
Andrey Prygunkov
1cbf435468 improved communication with news-servers: connections are now keeped open until all files are downloaded (or server is paused), this eliminates the need for establishing of connections and authorization for each article; increased maximum possible download speed on fast connection via eliminating of pauses by starting of new articles' downloads (improved the synchronisation mechanism); another speed optimisation in internal decoder (up to 8% faster) 2008-01-08 21:21:16 +00:00
Andrey Prygunkov
abb33b763b fixed: if nzb-parser decides to use subject as a filename (fallback option), this new filename is not saved on disk (and will not be restored on next server's start) 2008-01-05 21:54:01 +00:00
Andrey Prygunkov
f79bdab2cd fixed: par-checking should not be started on deleting of collections from queue 2008-01-05 16:20:22 +00:00
Andrey Prygunkov
1d294f5037 fixed: decoder does not work with enabled option <DirectWrite> 2008-01-04 23:14:29 +00:00
Andrey Prygunkov
40b7b335b1 improved decoding speed (up to 20% faster); added new option <crccheck> to bypass crc-calculation on slow CPUs 2008-01-04 17:08:47 +00:00
Andrey Prygunkov
c18a3b1d7c fixed a compatibility issue (compilation error), detected on one of test linux systems 2008-01-03 17:53:56 +00:00
Andrey Prygunkov
3d782c985f improved parsing of artcile's subject for better extracting of filename part from it and implemented a fallback-option if the parsing was incorrect; improved dupe check for files from the same nzb-request to detect reposted files and download only the best from them; fixed error on parsing of nzb-files containing percent and other special characters in their names (bug appeared on windows only) 2008-01-02 18:41:05 +00:00
Andrey Prygunkov
d35a2d2f04 reformated sample configuration file and changed default optionnames from lowercase to MixedCase for better readability 2007-12-31 17:10:17 +00:00
Andrey Prygunkov
b85a36944d implemented decode-on-the-fly-technique to reduce disk-io; intermediate files with articles' source text are not created anymore, but only intermediate files with decoded data; futher, decoder can write decoded data directly to the destination file (without any intermediate files at all), this eliminates the necessity of joining of articles later (option <directwrite>) 2007-12-31 16:30:47 +00:00
Andrey Prygunkov
e15063208a fixed: destination directory for files was not initialized, caused by changes in revision 50 (greatly reduced...) 2007-12-27 19:24:48 +00:00
Andrey Prygunkov
255ebb8ccd fixed a compilation error on linux 2007-12-26 23:43:42 +00:00
Andrey Prygunkov
07d04d0e65 greatly reduced the memory consumption by keeping articles' info on disk until the file download starts 2007-12-26 23:31:49 +00:00
Andrey Prygunkov
b3cebce074 small changes in curses-outputmode 2007-12-26 13:21:52 +00:00
Andrey Prygunkov
c03f79155d added option <threadlimit> to prevent program from crash if it wants to create too many threads (sometimes may occur in special cases) 2007-12-26 12:06:25 +00:00
Andrey Prygunkov
d449a26ae0 removed test-code from NZBFile 2007-12-26 00:30:02 +00:00
Andrey Prygunkov
904227ebfc added translation of printed messages to oem-codepage to correctly print filenames with non-english characters (windows only) 2007-12-25 22:48:40 +00:00
Andrey Prygunkov
bff48cd97e fixed: deletion of currently downloading files was not always successful and second attempt was neccessary 2007-12-25 16:10:38 +00:00
Andrey Prygunkov
c72c8df5e9 added check to detect special synchonisation issues on windows in debug-mode 2007-12-25 14:37:19 +00:00
Andrey Prygunkov
8a33307b6c improved error-reporting (_brokenlog.txt) on crc-errors 2007-12-25 14:20:27 +00:00
Andrey Prygunkov
5f19177902 fixed: edit-group-commands not worked on posix due synchonisation issue 2007-12-25 01:34:55 +00:00
Andrey Prygunkov
2cb33bafc4 added option <retryoncrcerror> 2007-12-24 23:53:27 +00:00
Andrey Prygunkov
78baf30339 added replacing of CR and LF charachters with spaces in log-window in ncurses-outputmode for better formatting; normalized line-endings in few source files 2007-12-24 22:28:53 +00:00
Andrey Prygunkov
ea4a48dd20 fixed exception occuring if invalid offset was passed to edit-group-command 2007-12-24 21:01:12 +00:00
Andrey Prygunkov
1b8d9e9bfa changed initalization of options to fix conflict between suboption [G] and negative offsets in edit-command 2007-12-24 20:55:49 +00:00
Andrey Prygunkov
ad9a66e971 implemented edit-command <move-offset> for groups 2007-12-24 20:27:22 +00:00
Andrey Prygunkov
bb5c660e1f keywords set for QueueEditor.cpp/h (second attempt) 2007-12-24 17:51:32 +00:00
Andrey Prygunkov
4f29ed2e4c optimzed QueueEditor to edit lists of files and to provide transactional changes to all affected files 2007-12-24 17:37:23 +00:00
Andrey Prygunkov
6d5929a611 implemented edits for lists of groups; extended server/client communication protocol for group-commands; added suboption <G> for command-line switch <-E> to edit groups; Note: group-commands are still limited to pause, resume, delete, move-top, move-bottom 2007-12-23 22:54:10 +00:00
Andrey Prygunkov
c34ad991c4 refactored QueueEditor 2007-12-21 17:23:35 +00:00
Andrey Prygunkov
8eceb70626 added editing of queue for group-view in curses-outputmode: pause, resume, move-to-top, move-to-bottom; currently work only in server-console or standalone-app, not in remote-client 2007-12-20 23:45:05 +00:00
Andrey Prygunkov
f3f609f747 refactored: new class QueueEditor to handle all edit-requests; related code moved from QueueCoordinator and RemoteServer 2007-12-20 19:22:45 +00:00
Andrey Prygunkov
c02074e7ec creation of necessary directories on program's start extended with automatic creation of all parent directories and error reporting if it was not possible 2007-12-19 22:55:03 +00:00
Andrey Prygunkov
6697737405 added options <cursesnzbname>, <cursesgroup> and <cursestime> to define initial state of curses-outputmode 2007-12-19 20:41:06 +00:00
Andrey Prygunkov
dd3ca56564 toggle files/nzbs-list in curses-outputmode with G-key 2007-12-19 19:58:39 +00:00
Andrey Prygunkov
71805e11f7 removed accidentally commited debug-code in ParChecker.cpp 2007-12-19 18:27:26 +00:00
Andrey Prygunkov
e480781905 fixed a segfault appeared on Windows and caused by changes in revision 23 (added par-checker's status...) 2007-12-19 17:53:18 +00:00
Andrey Prygunkov
4df72eafd2 updated ChangeLog 2007-12-19 17:23:38 +00:00
Andrey Prygunkov
cdbfd3b96d added par-checker's status to status line and output of list-command 2007-12-18 23:23:56 +00:00
Andrey Prygunkov
cee6aa53f4 fully implemented SmartOrder-parameter for Edit-commands 2007-12-17 20:13:20 +00:00
Andrey Prygunkov
2d9bca1d1b added option <updateinterval> 2007-12-16 12:55:21 +00:00
Andrey Prygunkov
80685270e0 added option <umask> to specify permissions for newly created files and dirs (POSIX only) 2007-12-15 13:42:06 +00:00
Andrey Prygunkov
5695eaf3f4 1) fixed: file-ids in Edit-command were not converted to/from network byte order; 2) fixed few warnings under gcc 2007-12-14 22:36:10 +00:00
Andrey Prygunkov
8d46ee3236 eliminated configure-option --disable-parprogress (not needed anymore) 2007-12-14 22:19:51 +00:00
Andrey Prygunkov
702aad905f refactored RemoteServer and implemented initial support for SmartOrder in Edit-Command (currently only for MoveTop- and MoveBottom-commands) 2007-12-14 22:04:44 +00:00
Andrey Prygunkov
1da4976fea Edit-command now accepts inverse ranges of file-ids (this allows to keep relative order of files by moving items to the beginning of queue, since edit-command processes files in a giving order), e.g.: nzbget -E T 10-1 2007-12-13 23:56:59 +00:00
Andrey Prygunkov
94b81f9ce4 Edit-command now accepts more than one file-id or range of file-ids and does not require switch <I>, for example: nzbget -E P 2,6-10,33-39 2007-12-13 23:40:33 +00:00
Andrey Prygunkov
eec1a797b3 changed various structures in communication protocol 2007-12-13 19:08:55 +00:00
Andrey Prygunkov
ca7a90adab show/hide timestamps in curses-outputmode with T-key 2007-12-13 18:24:54 +00:00
Andrey Prygunkov
adedd66dac added few comments and renamed one field in MessageBase.h 2007-12-13 00:34:00 +00:00
Andrey Prygunkov
0a43829772 fixed and extended parameters for post-process-scripts (<result of par-check> and <state of nzb-job>) 2007-12-13 00:02:24 +00:00
Andrey Prygunkov
a35ff448b5 revised communication protocol to achieve compatibility between hosts with different endianness 2007-12-08 13:51:54 +00:00
Andrey Prygunkov
74db2f7785 updated version number in configure-script to 0.3.1-testing to differentiate between executables built from svn and published releases 2007-12-08 13:28:27 +00:00
Andrey Prygunkov
4760198c23 added missing Makefile.cvs (needed by KDevelop to rebuild configure-scripts) 2007-12-08 13:23:43 +00:00
Andrey Prygunkov
078c6c037e eliminated few compiler warnings 2007-12-07 22:20:45 +00:00
Andrey Prygunkov
56d4fcf045 fixed segfault when gethostbyname_r returns hinfo=NULL 2007-12-07 22:08:21 +00:00
Andrey Prygunkov
24c4cd87d7 added option daemonusername 2007-12-01 14:36:53 +00:00
Andrey Prygunkov
1b80cdd9b4 fixed seg fault on nzb-names starting with msgid 2007-11-29 18:53:17 +00:00
Andrey Prygunkov
b66266cf82 fixed an endless loop on receiving of SIGSEGV-signal in debug-mode 2007-11-29 18:06:49 +00:00
59 changed files with 9405 additions and 6306 deletions

View File

@@ -64,11 +64,10 @@ ArticleDownloader::ArticleDownloader()
m_szTempFilename = NULL;
m_szArticleFilename = NULL;
m_szInfoName = NULL;
m_szOutputFilename = NULL;
m_pConnection = NULL;
m_pDecoder = NULL;
m_eStatus = adUndefined;
m_iBytes = 0;
memset(&m_tStartTime, 0, sizeof(m_tStartTime));
m_bDuplicate = false;
SetLastUpdateTimeNow();
}
@@ -88,9 +87,9 @@ ArticleDownloader::~ArticleDownloader()
{
free(m_szInfoName);
}
if (m_pDecoder)
if (m_szOutputFilename)
{
delete m_pDecoder;
free(m_szOutputFilename);
}
}
@@ -99,6 +98,11 @@ 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);
@@ -125,9 +129,8 @@ void ArticleDownloader::Run()
{
// file exists from previous program's start
info("Article %s already downloaded, skipping", m_szInfoName);
m_semInitialized.Post();
m_semWaited.Wait();
SetStatus(adFinished);
FreeConnection(true);
return;
}
}
@@ -145,11 +148,6 @@ void ArticleDownloader::Run()
}
int level = 0;
m_semInitialized.Post();
m_semWaited.Wait();
//while (true) usleep(10); // DEBUG TEST
while (!IsStopped() && (retry > 0))
{
SetLastUpdateTimeNow();
@@ -158,7 +156,7 @@ void ArticleDownloader::Run()
if (!m_pConnection)
{
m_pConnection = g_pServerPool->GetConnection(level);
m_pConnection = g_pServerPool->GetConnection(level, true);
}
if (IsStopped())
@@ -181,6 +179,8 @@ void ArticleDownloader::Run()
Status = Download();
}
bool bAuthError = m_pConnection && m_pConnection->GetAuthError();
if (connected)
{
// freeing connection allows other threads to start.
@@ -188,10 +188,12 @@ void ArticleDownloader::Run()
// 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();
FreeConnection(Status == adFinished);
}
if ((Status == adFailed) && ((retry > 1) || !connected) && !IsStopped())
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;
@@ -207,8 +209,9 @@ void ArticleDownloader::Run()
Status = adFailed;
break;
}
if ((Status == adFinished) || (Status == adFatalError))
if ((Status == adFinished) || (Status == adFatalError) ||
(Status == adCrcError && !g_pOptions->GetRetryOnCrcError()))
{
break;
}
@@ -228,13 +231,13 @@ void ArticleDownloader::Run()
{
if (iMaxLevel > 0)
{
warn("Aticle %s @ all servers failed: Article not found", m_szInfoName);
warn("Article %s @ all servers failed: Article not found", m_szInfoName);
}
break;
}
// do not count connect-errors, only article- and group-errors
if (connected)
if (connected && !bAuthError)
{
level++;
if (level > iMaxLevel)
@@ -245,10 +248,15 @@ void ArticleDownloader::Run()
}
}
FreeConnection();
FreeConnection(Status == adFinished);
free(LevelStatus);
if (m_bDuplicate)
{
Status = adFinished;
}
if (Status != adFinished)
{
Status = adFailed;
@@ -268,16 +276,16 @@ void ArticleDownloader::Run()
SetStatus(Status);
debug("Existing ArticleDownloader-loop");
debug("Exiting ArticleDownloader-loop");
}
ArticleDownloader::EStatus ArticleDownloader::Download()
{
// at first, change group! dryan's level wants it this way... ;-)
// 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) == 0;
grpchanged = m_pConnection->JoinGroup(*it);
if (grpchanged)
{
break;
@@ -286,7 +294,10 @@ ArticleDownloader::EStatus ArticleDownloader::Download()
if (!grpchanged)
{
warn("Article %s @ %s failed: Could not join group", m_szInfoName, m_pConnection->GetServer()->GetHost());
if (!m_pConnection->GetAuthError() && !IsStopped())
{
warn("Article %s @ %s failed: Could not join group", m_szInfoName, m_pConnection->GetServer()->GetHost());
}
return adFailed;
}
@@ -300,7 +311,7 @@ ArticleDownloader::EStatus ArticleDownloader::Download()
for (int retry = 3; retry > 0; retry--)
{
answer = m_pConnection->Request(tmp);
if (answer && (!strncmp(answer, "2", 1)))
if (answer && !strncmp(answer, "2", 1))
{
break;
}
@@ -308,30 +319,31 @@ ArticleDownloader::EStatus ArticleDownloader::Download()
if (!answer)
{
warn("Article %s @ %s failed: Connection closed by remote host", m_szInfoName, m_pConnection->GetServer()->GetHost());
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 adNotFound;
return (!strncmp(answer, "41", 2) || !strncmp(answer, "42", 2)) ? adNotFound : adFailed;
}
// positive answer!
const char* dnfilename = m_szTempFilename;
FILE* outfile = fopen(dnfilename, "w");
if (!outfile)
if (g_pOptions->GetDecoder() == Options::dcYenc)
{
error("Could not create file %s", dnfilename);
return adFatalError;
m_YDecoder.Clear();
m_YDecoder.SetAutoSeek(g_pOptions->GetDirectWrite());
m_YDecoder.SetCrcCheck(g_pOptions->GetCrcCheck());
}
gettimeofday(&m_tStartTime, 0);
m_iBytes = 0;
m_pOutFile = NULL;
EStatus Status = adRunning;
bool bBody = false;
const int LineBufSize = 1024*10;
char* szLineBuf = (char*)malloc(LineBufSize);
@@ -347,14 +359,14 @@ ArticleDownloader::EStatus ArticleDownloader::Download()
usleep(200 * 1000);
}
struct _timeval tSpeedReadingStartTime;
gettimeofday(&tSpeedReadingStartTime, 0);
char* line = m_pConnection->ReadLine(szLineBuf, LineBufSize);
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;
}
@@ -365,114 +377,269 @@ ArticleDownloader::EStatus ArticleDownloader::Download()
break;
}
// Did we meet an unexpected need for authorization?
if (!strncmp(line, "480", 3))
{
m_pConnection->Authenticate();
}
//detect lines starting with "." (marked as "..")
if (!strncmp(line, "..", 2))
{
line++;
}
int wrcnt = (int)fwrite(line, 1, strlen(line), outfile);
if (wrcnt > 0)
// check id of returned article
if (!bBody)
{
m_iBytes += wrcnt;
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);
fflush(outfile);
fclose(outfile);
if (m_pOutFile)
{
fclose(m_pOutFile);
}
if (IsStopped())
{
remove(dnfilename);
remove(m_szTempFilename);
return adFailed;
}
if (Status == adFailed)
{
warn("Unexpected end of %s", m_szInfoName);
remove(dnfilename);
remove(m_szTempFilename);
return adFailed;
}
FreeConnection();
if ((g_pOptions->GetDecoder() == Options::dcUulib) ||
(g_pOptions->GetDecoder() == Options::dcYenc))
if (Status == adRunning)
{
// Give time to other threads. Help to avoid hangs on Asus WL500g router.
usleep(10 * 1000);
FreeConnection(true);
return Decode();
}
else
{
return Status;
}
}
SetStatus(adDecoding);
struct _timeval StartTime, EndTime;
gettimeofday(&StartTime, 0);
bool OK = false;
if (g_pOptions->GetDecoder() == Options::dcUulib)
{
m_pDecoder = new Decoder();
m_pDecoder->SetKind(Decoder::dcUulib);
}
else if (g_pOptions->GetDecoder() == Options::dcYenc)
{
m_pDecoder = new Decoder();
m_pDecoder->SetKind(Decoder::dcYenc);
}
if (m_pDecoder)
{
m_pDecoder->SetSrcFilename(dnfilename);
bool ArticleDownloader::Write(char* szLine, int iLen)
{
if (!m_pOutFile && !PrepareFile(szLine))
{
return false;
}
char tmpdestfile[1024];
snprintf(tmpdestfile, 1024, "%s.dec", m_szResultFilename);
tmpdestfile[1024-1] = '\0';
m_pDecoder->SetDestFilename(tmpdestfile);
OK = m_pDecoder->Execute();
if (OK)
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())
{
rename(tmpdestfile, m_szResultFilename);
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
{
remove(tmpdestfile);
bOpen = true;
}
if (m_pDecoder->GetArticleFilename())
{
m_szArticleFilename = strdup(m_pDecoder->GetArticleFilename());
}
delete m_pDecoder;
m_pDecoder = NULL;
}
}
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);
}
gettimeofday(&EndTime, 0);
remove(dnfilename);
#ifdef WIN32
float fDeltaTime = (float)((EndTime.time - StartTime.time) * 1000 + (EndTime.millitm - StartTime.millitm));
#else
float fDeltaTime = ((EndTime.tv_sec - StartTime.tv_sec) * 1000000 + (EndTime.tv_usec - StartTime.tv_usec)) / 1000.0;
#endif
if (OK)
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);
debug("Decode time %.1f ms", fDeltaTime);
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
{
warn("Decoding %s failed", m_szInfoName);
remove(m_szResultFilename);
return adFailed;
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(dnfilename, m_szResultFilename);
rename(m_szTempFilename, m_szResultFilename);
info("Article %s successfully downloaded", m_szInfoName);
return adFinished;
}
@@ -494,10 +661,6 @@ void ArticleDownloader::LogDebugInfo()
#endif
debug(" Download: status=%s, LastUpdateTime=%s, filename=%s", GetStatusText(), szTime, BaseFileName(GetTempFilename()));
if (m_pDecoder)
{
m_pDecoder->LogDebugInfo();
}
}
void ArticleDownloader::Stop()
@@ -510,17 +673,34 @@ void ArticleDownloader::Stop()
m_pConnection->Cancel();
}
m_mutexConnection.Unlock();
debug("ArticleDownloader stopped successfuly");
debug("ArticleDownloader stopped successfully");
}
void ArticleDownloader::FreeConnection()
bool ArticleDownloader::Terminate()
{
if (m_pConnection)
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();
m_pConnection->Disconnect();
g_pServerPool->FreeConnection(m_pConnection);
if (!bKeepConnected || m_pConnection->GetStatus() == Connection::csCancelled)
{
m_pConnection->Disconnect();
}
g_pServerPool->FreeConnection(m_pConnection, true);
m_pConnection = NULL;
m_mutexConnection.Unlock();
}
@@ -530,11 +710,8 @@ void ArticleDownloader::CompleteFileParts()
{
debug("Completing file parts");
debug("ArticleFilename: %s", m_pFileInfo->GetFilename());
SetStatus(adJoining);
char ofn[1024];
snprintf(ofn, 1024, "%s%c%s", m_pFileInfo->GetDestDir(), (int)PATH_SEPARATOR, m_pFileInfo->GetFilename());
ofn[1024-1] = '\0';
SetStatus(adJoining);
char szNZBNiceName[1024];
m_pFileInfo->GetNiceNZBName(szNZBNiceName, 1024);
@@ -543,18 +720,26 @@ void ArticleDownloader::CompleteFileParts()
snprintf(InfoFilename, 1024, "%s%c%s", szNZBNiceName, (int)PATH_SEPARATOR, m_pFileInfo->GetFilename());
InfoFilename[1024-1] = '\0';
// Ensure the DstDir is created
mkdir(m_pFileInfo->GetDestDir(), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
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;
@@ -566,15 +751,15 @@ void ArticleDownloader::CompleteFileParts()
}
FILE* outfile = NULL;
char tmpdestfile[1024];
snprintf(tmpdestfile, 1024, "%s.tmp", ofn);
tmpdestfile[1024-1] = '\0';
remove(tmpdestfile);
if ((g_pOptions->GetDecoder() == Options::dcUulib) ||
(g_pOptions->GetDecoder() == Options::dcYenc))
if (((g_pOptions->GetDecoder() == Options::dcUulib) ||
(g_pOptions->GetDecoder() == Options::dcYenc)) &&
!g_pOptions->GetDirectWrite())
{
remove(tmpdestfile);
outfile = fopen(tmpdestfile, "w+");
if (!outfile)
{
@@ -582,16 +767,32 @@ void ArticleDownloader::CompleteFileParts()
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)
{
mkdir(ofn, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
remove(tmpdestfile);
mkdir(ofn, S_DIRMODE);
}
bool complete = true;
int iBrokenCount = 0;
static const int BUFFER_SIZE = 1024 * 50;
char* buffer = (char*)malloc(BUFFER_SIZE);
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++)
{
@@ -601,8 +802,9 @@ void ArticleDownloader::CompleteFileParts()
iBrokenCount++;
complete = false;
}
else if ((g_pOptions->GetDecoder() == Options::dcUulib) ||
(g_pOptions->GetDecoder() == Options::dcYenc))
else if (((g_pOptions->GetDecoder() == Options::dcUulib) ||
(g_pOptions->GetDecoder() == Options::dcYenc)) &&
!g_pOptions->GetDirectWrite())
{
FILE* infile;
const char* fn = pa->GetResultFilename();
@@ -617,7 +819,6 @@ void ArticleDownloader::CompleteFileParts()
cnt = (int)fread(buffer, 1, BUFFER_SIZE, infile);
fwrite(buffer, 1, cnt, outfile);
SetLastUpdateTimeNow();
usleep(10); // give time to other threads
}
fclose(infile);
@@ -638,19 +839,30 @@ void ArticleDownloader::CompleteFileParts()
rename(fn, dstFileName);
}
}
free(buffer);
if ((g_pOptions->GetDecoder() == Options::dcUulib) ||
(g_pOptions->GetDecoder() == Options::dcYenc))
if (buffer)
{
free(buffer);
}
if (outfile)
{
fclose(outfile);
rename(tmpdestfile, ofn);
}
for (FileInfo::Articles::iterator it = m_pFileInfo->GetArticles()->begin(); it != m_pFileInfo->GetArticles()->end(); it++)
if (g_pOptions->GetDirectWrite())
{
ArticleInfo* pa = *it;
remove(pa->GetResultFilename());
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)
@@ -696,24 +908,3 @@ void ArticleDownloader::CompleteFileParts()
SetStatus(adFinished);
}
bool ArticleDownloader::Terminate()
{
NNTPConnection* pConnection = m_pConnection;
bool terminated = Kill();
if (terminated && pConnection)
{
debug("Terminating connection");
pConnection->Cancel();
g_pServerPool->FreeConnection(pConnection);
}
return terminated;
}
void ArticleDownloader::WaitInit()
{
// waiting until the download becomes ready to catch connection,
// but no longer then 30 seconds
m_semInitialized.TimedWait(30000);
m_semWaited.Post();
}

View File

@@ -47,6 +47,8 @@ public:
adRunning,
adFinished,
adFailed,
adDecodeError,
adCrcError,
adDecoding,
adJoining,
adNotFound,
@@ -63,20 +65,18 @@ private:
char* m_szTempFilename;
char* m_szArticleFilename;
char* m_szInfoName;
char* m_szOutputFilename;
time_t m_tLastUpdateTime;
Decoder* m_pDecoder;
Semaphore m_semInitialized;
Semaphore m_semWaited;
static const char* m_szJobStatus[];
#ifdef WIN32
struct _timeb m_tStartTime;
#else
struct timeval m_tStartTime;
#endif
int m_iBytes;
YDecoder m_YDecoder;
FILE* m_pOutFile;
bool m_bDuplicate;
EStatus Download();
void FreeConnection();
bool Write(char* szLine, int iLen);
bool PrepareFile(char* szLine);
EStatus Decode();
void FreeConnection(bool bKeepConnected);
public:
ArticleDownloader();
@@ -95,17 +95,12 @@ public:
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 WaitInit();
#ifdef WIN32
struct _timeb* GetStartTime() { return &m_tStartTime; }
#else
struct timeval* GetStartTime() { return &m_tStartTime; }
#endif
int GetBytes() { return m_iBytes; }
void SetConnection(NNTPConnection* pConnection) { m_pConnection = pConnection; }
void LogDebugInfo();
};
@@ -115,6 +110,7 @@ class DownloadSpeedMeter
public:
virtual ~DownloadSpeedMeter() {};
virtual float CalcCurrentDownloadSpeed() = 0;
virtual void AddSpeedReading(int iBytes) = 0;
};
#endif

View File

@@ -1,3 +1,77 @@
nzbget-0.3.1:
- Greatly reduced the memory consumption by keeping articles' info on disk
until the file download starts;
- Implemented decode-on-the-fly-technique to reduce disk-io; downloaded
and decoded data can also be saved directly to the destination file
(without any intermediate files at all); this eliminates the necessity
of joining of articles later (option "DirectWrite");
- Improved communication with news-servers: connections are now keeped open
until all files are downloaded (or server paused); this eliminates the
need for establishing of connections and authorizations for each
article and improves overal download speed;
- Significantly better download speed is now possible on fast connection;
it was limited by delays on starting of new articles' downloads;
the synchronisation mechanism was reworked to fix this issue;
- Download speed meter is much more accurate, especially on fast connections;
this also means better speed throttling;
- Speed optimisations in internal decoder (up to 25% faster);
- CRC-calculation can be bypassed to increase performance on slow CPUs
(option "CrcCheck");
- Improved parsing of artcile's subject for better extracting of filename
part from it and implemented a fallback-option if the parsing was incorrect;
- Improved dupe check for files from the same nzb-request to detect reposted
files and download only the best from them (if option "DupeCheck" is on);
- Articles with incorrect CRC can be treated as "possibly recoverable errors"
and relaunched for download (option "RetryOnCrcError"), it is useful if
multiple servers are available;
- Improved error-check for downloaded articles (crc-check and check for
received message-id) decrease the number of broken files;
- Extensions in curses-outputmode: added group-view-mode (key "G") to show
items in download queue as groups, where one group represents all files
from the same nzb-file; the editing of queue works also in group-mode
(for all files in this group): pause/unpause/delete/move of groups;
- Other extensions in curses-outputmode: key "T" toggles timestamps in log;
added output of statistical data: uptime, download-time, average session
download speed;
- Edit-command accepts more than one ID or range of IDs.
E.g: "nzbget -E P 2,6-10,33-39"; The switch "-I" is not used anymore;
- Move-actions in Edit-command affect files in a smart order to guarantee
that the relative order of files in queue is not changed after the moving;
- Extended syntax of edit-command to edit groups (pause/unpause/delete/move
of groups). E.g: "nzbget -E G P 2";
- Added option "DaemonUserName" to set the user that the daemon (POSIX only)
normally runs at. This allows nzbget daemon to be launched in rc.local
(at boot), and download items as a specific user id; Thanks to Thierry
MERLE <merlum@users.sourceforge.net> for the patch;
- Added option "UMask" to specify permissions for newly created files and dirs
(POSIX only);
- Communication protocol used between server and client was revised to define
the byte order for transferred data. This allows hosts with different
endianness to communicate with each other;
- Added options "CursesNzbName", "CursesGroup" and "CursesTime" to define
initial state of curses-outputmode;
- Added option "UpdateInterval" to adjust update interval for Frontend-output
(useful in remote-mode to reduce network usage);
- Added option "WriteBufferSize" to reduce disk-io (but it could slightly
increase memory usage and therefore disabled by default);
- List-command prints additional statistical info: uptime, download-time,
total amount of downloaded data and average session download speed;
- The creation of necessary directories on program's start was extended
with automatic creation of all parent directories or error reporting
if it was not possible;
- Printed messages are now translated to oem-codepage to correctly print
filenames with non-english characters (windows only);
- Added remote-command "-V (--serverversion)" to print the server's version;
- Added option "ThreadLimit" to prevent program from crash if it wants to
create too many threads (sometimes could occur in special cases);
- Added options "NzbDirInterval" and "NzbDirFileAge" to adjust interval and
delay by monitoring of incoming-directory for new nzb-files;
- Fixed error on parsing of nzb-files containing percent and other special
characters in their names (bug appeared on windows only);
- Reformated sample configuration file and changed default optionnames
from lowercase to MixedCase for better readability;
- Few bugs (seg faults) were fixed.
nzbget-0.3.0:
- The download queue now contains newsgroup-files to be downloaded instead of
nzb-jobs. By adding a new job, the nzb-file is immediately parsed and each
@@ -136,7 +210,7 @@ nzbget-0.3.0:
- Solaris 10 on x86;
- Many memory-leaks and thread issues were fixed;
- The program was thoroughly worked over. Almost every line of code was
revised;
revised.
nzbget-0.2.3
- Fixed problem with losing connection to newsserver after too long idle time

View File

@@ -73,52 +73,47 @@ void ColoredFrontend::PrintStatus()
char tmp[1024];
char timeString[100];
timeString[0] = '\0';
float fCurrentDownloadSpeed = m_bStandBy ? 0 : m_fCurrentDownloadSpeed;
if (m_fCurrentDownloadSpeed > 0.0f)
if (fCurrentDownloadSpeed > 0.0 && !m_bPause)
{
long long remain_sec = m_lRemainingSize / ((long long int)(m_fCurrentDownloadSpeed * 1024));
int h = 0;
int m = 0;
int s = 0;
while (remain_sec > 3600)
{
h++;
remain_sec -= 3600;
}
while (remain_sec > 60)
{
m++;
remain_sec -= 60;
}
s = remain_sec;
sprintf(timeString, "(~ %.2d:%.2d:%.2d)", h, m, s);
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);
}
const char* szPause[] = { "Paused", "" };
int iPauseIdx = m_bPause ? 0 : 1;
char szDownloadLimit[128];
if (m_fDownloadLimit > 0.0f)
{
sprintf(szDownloadLimit, "Limit %.0f KB/S", m_fDownloadLimit);
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 running, %.0f KB/s, %.2f MB remaining %s %s %s%s\n",
m_iThreadCount, m_fCurrentDownloadSpeed, (float)(m_lRemainingSize / 1024.0 / 1024.0),
timeString, szPause[iPauseIdx], szDownloadLimit, szControlSeq);
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;
@@ -126,7 +121,6 @@ void ColoredFrontend::PrintStatus()
void ColoredFrontend::PrintMessage(Message * pMessage)
{
const char* msg = pMessage->GetText();
#ifdef WIN32
switch (pMessage->GetKind())
{
@@ -148,8 +142,12 @@ void ColoredFrontend::PrintMessage(Message * pMessage)
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:

View File

@@ -47,6 +47,8 @@
#include "Connection.h"
#include "Log.h"
static const int CONNECTION_READBUFFER_SIZE = 1024;
void Connection::Init()
{
debug("Intiializing global connection data");
@@ -86,8 +88,9 @@ Connection::Connection(NetAddress* pNetAddress)
m_eStatus = csDisconnected;
m_iSocket = INVALID_SOCKET;
m_iBufAvail = 0;
m_bCanceling = false;
m_iTimeout = 60;
m_bSuppressErrors = true;
m_szReadBuf = (char*)malloc(CONNECTION_READBUFFER_SIZE + 1);
}
Connection::~Connection()
@@ -98,6 +101,7 @@ Connection::~Connection()
{
Disconnect();
}
free(m_szReadBuf);
}
int Connection::Connect()
@@ -127,6 +131,8 @@ int Connection::Disconnect()
int iRes = DoDisconnect();
m_eStatus = csDisconnected;
m_iSocket = INVALID_SOCKET;
m_iBufAvail = 0;
return iRes;
}
@@ -142,7 +148,10 @@ int Connection::Bind()
int iRes = DoBind();
m_eStatus = csListening;
if (iRes == 0)
{
m_eStatus = csListening;
}
return iRes;
}
@@ -158,9 +167,6 @@ int Connection::WriteLine(char* line)
int iRes = DoWriteLine(line);
if (iRes == EOF)
Connection::DoDisconnect();
return iRes;
}
@@ -178,17 +184,14 @@ int Connection::Send(char* pBuffer, int iSize)
return iRes;
}
char* Connection::ReadLine(char* pBuffer, int iSize)
char* Connection::ReadLine(char* pBuffer, int iSize, int* pBytesRead)
{
if (m_eStatus != csConnected)
{
return NULL;
}
char* res = DoReadLine(pBuffer, iSize);
if (res == NULL)
Connection::DoDisconnect();
char* res = DoReadLine(pBuffer, iSize, pBytesRead);
return res;
}
@@ -301,7 +304,7 @@ unsigned int Connection::ResolveHostAddr(const char* szHost)
{
struct hostent* hinfo;
bool err = false;
int h_errnop;
int h_errnop = 0;
#ifdef WIN32
hinfo = gethostbyname(szHost);
err = hinfo == NULL;
@@ -312,6 +315,7 @@ unsigned int Connection::ResolveHostAddr(const char* szHost)
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;
@@ -354,18 +358,19 @@ int Connection::DoWriteLine(char* szText)
return send(m_iSocket, szText, strlen(szText), 0);
}
char* Connection::DoReadLine(char* pBuffer, int iSize)
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, ReadBufLen, 0);
iBufAvail = recv(m_iSocket, m_szReadBuf, CONNECTION_READBUFFER_SIZE, 0);
if (iBufAvail < 0)
{
ReportError("Could not receive data on socket", NULL, 0);
@@ -399,18 +404,24 @@ char* Connection::DoReadLine(char* pBuffer, int iSize)
pBufPtr += len;
szBufPtr += len;
iBufAvail -= len;
iBytesRead += len;
iSize -= len;
if (p)
{
break;
}
iSize--;
}
*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;
@@ -432,7 +443,18 @@ int Connection::DoBind()
struct sockaddr_in sSocketAddress;
memset(&sSocketAddress, '\0', sizeof(sSocketAddress));
sSocketAddress.sin_family = AF_INET;
sSocketAddress.sin_addr.s_addr = htonl(INADDR_ANY);
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));
@@ -461,7 +483,7 @@ SOCKET Connection::DoAccept()
SOCKET iSocket = accept(GetSocket(), (struct sockaddr *) & ClientAddress, &SockLen);
if (iSocket == INVALID_SOCKET && !m_bCanceling)
if (iSocket == INVALID_SOCKET && m_eStatus != csCancelled)
{
ReportError("Could not accept connection", NULL, 0);
}
@@ -472,15 +494,14 @@ SOCKET Connection::DoAccept()
void Connection::Cancel()
{
debug("Cancelling connection");
m_bCanceling = true;
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);
}
m_eStatus = csCancelled;
}
}
@@ -499,9 +520,23 @@ void Connection::ReportError(const char* szMsgPrefix, const char* szMsgArg, int
snprintf(szErrPrefix, 1024, szMsgPrefix, szMsgArg);
szErrPrefix[1024-1] = '\0';
#ifdef WIN32
debug("%s: ErrNo %i", szErrPrefix, ErrCode);
if (m_bSuppressErrors)
{
debug("%s: ErrNo %i", szErrPrefix, ErrCode);
}
else
{
error("%s: ErrNo %i", szErrPrefix, ErrCode);
}
#else
const char* szErrMsg = hstrerror(ErrCode);
debug("%s: ErrNo %i, %s", szErrPrefix, ErrCode, szErrMsg);
if (m_bSuppressErrors)
{
debug("%s: ErrNo %i, %s", szErrPrefix, ErrCode, szErrMsg);
}
else
{
error("%s: ErrNo %i, %s", szErrPrefix, ErrCode, szErrMsg);
}
#endif
}

View File

@@ -43,15 +43,21 @@ public:
protected:
NetAddress* m_pNetAddress;
SOCKET m_iSocket;
static const int ReadBufLen = 1024;
char m_szReadBuf[ReadBufLen + 1];
char* m_szReadBuf;
int m_iBufAvail;
char* m_szBufPtr;
EStatus m_eStatus;
bool m_bCanceling;
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);
@@ -64,21 +70,16 @@ public:
int Send(char* pBuffer, int iSize);
int Recv(char* pBuffer, int iSize);
bool RecvAll(char* pBuffer, int iSize);
char* ReadLine(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; }
protected:
virtual int DoConnect();
virtual int DoDisconnect();
int DoBind();
int DoWriteLine(char* text);
char* DoReadLine(char* pBuffer, int iSize);
SOCKET DoAccept();
EStatus GetStatus() { return m_eStatus; }
void SetSuppressErrors(bool bSuppressErrors) { m_bSuppressErrors = bSuppressErrors; }
bool GetSuppressErrors() { return m_bSuppressErrors; }
};
#endif

View File

@@ -48,31 +48,12 @@
#include <uudeview.h>
#endif
//#define USEEXTERNALDECODER // not working
//#define DEBUGDECODER
#include "Decoder.h"
#include "Log.h"
#include "Options.h"
#include "Util.h"
#ifdef DEBUGDECODER
int g_iDecoderID = 0;
#endif
Mutex Decoder::m_mutexDecoder;
unsigned int Decoder::crc_tab[256];
void Decoder::Init()
{
debug("Initializing global decoder");
crc32gentab();
}
void Decoder::Final()
{
debug("Finalizing global Decoder");
}
Mutex UULibDecoder::m_mutexDecoder;
unsigned int YDecoder::crc_tab[256];
Decoder::Decoder()
{
@@ -81,9 +62,7 @@ Decoder::Decoder()
m_szSrcFilename = NULL;
m_szDestFilename = NULL;
m_szArticleFilename = NULL;
m_eKind = dcYenc;
m_iDebugStatus = 0;
m_iDebugLines = 0;
m_bCrcError = false;
}
Decoder::~ Decoder()
@@ -96,19 +75,12 @@ Decoder::~ Decoder()
}
}
bool Decoder::Execute()
{
if (m_eKind == dcUulib)
{
return DecodeUulib();
}
else
{
return DecodeYenc();
}
}
bool Decoder::DecodeUulib()
/*
* UULibDecoder
*/
bool UULibDecoder::Execute()
{
bool res = false;
@@ -118,11 +90,6 @@ bool Decoder::DecodeUulib()
m_mutexDecoder.Lock();
#ifdef DEBUGDECODER
debug("Decoding ID %i (%s)", g_iDecoderID, szSrcFilename);
#endif
#ifndef USEEXTERNALDECODER
UUInitialize();
UUSetOption(UUOPT_DESPERATE, 1, NULL);
@@ -184,13 +151,6 @@ bool Decoder::DecodeUulib()
}
UUCleanUp();
#else
execl("/usr/local/bin", "uudeview", szSrcFilename, szDestFilename);
#endif
#ifdef DEBUGDECODER
debug("Finished decoding ID %i (%s)", g_iDecoderID++, szDestFilename);
#endif
m_mutexDecoder.Unlock();
@@ -200,123 +160,42 @@ bool Decoder::DecodeUulib()
}
/**
* YDecoder
* Very primitive (but fast) implementation of yEnc-Decoder
*/
bool Decoder::DecodeYenc()
void YDecoder::Init()
{
FILE* infile = fopen(m_szSrcFilename, "r");
if (!infile)
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)
{
error("Could not open file \"%s\"", m_szSrcFilename);
return false;
free(m_szArticleFilename);
}
FILE* outfile = fopen(m_szDestFilename, "w");
if (!outfile)
{
error("Could not create file \"%s\"", m_szDestFilename);
fclose(infile);
return false;
}
static const int MAX_LINE_LEN = 1024;
char buffer[MAX_LINE_LEN];
bool body = false;
bool end = false;
unsigned long expectedCRC = 0;
unsigned long calculatedCRC = 0xFFFFFFFF;
m_iDebugStatus = 1;
bool eof = !fgets(buffer, sizeof(buffer), infile);
m_iDebugLines++;
m_iDebugStatus = 2;
while (!eof)
{
if (body)
{
if (strstr(buffer, "=yend size="))
{
end = true;
m_iDebugStatus = 3;
char* pc = strstr(buffer, "pcrc32=");
if (pc)
{
pc += 7; //=strlen("pcrc32=")
expectedCRC = strtoul(pc, NULL, 16);
}
break;
}
m_iDebugStatus = 4;
char* iptr = buffer;
char* optr = buffer;
while (*iptr)
{
switch (*iptr)
{
case '=': //escape-sequence
iptr++;
*optr = *iptr - 64 - 42;
*optr++;
break;
case '\n': // ignored char
case '\r': // ignored char
break;
default: // normal char
*optr = *iptr - 42;
*optr++;
break;
}
iptr++;
}
m_iDebugStatus = 5;
calculatedCRC = crc32m(calculatedCRC, (unsigned char *)buffer, optr - buffer);
fwrite(buffer, 1, optr - buffer, outfile);
m_iDebugStatus = 6;
}
else
{
if (strstr(buffer, "=ypart begin="))
{
m_iDebugStatus = 7;
body = true;
}
else if (strstr(buffer, "=ybegin part="))
{
m_iDebugStatus = 8;
char* pb = strstr(buffer, "name=");
if (pb)
{
m_iDebugStatus = 9;
pb += 5; //=strlen("name=")
char* pe;
for (pe = pb; *pe != '\0' && *pe != '\n' && *pe != '\r'; pe++) ;
m_szArticleFilename = (char*)malloc(pe - pb + 1);
strncpy(m_szArticleFilename, pb, pe - pb);
m_szArticleFilename[pe - pb] = '\0';
m_iDebugStatus = 10;
}
m_iDebugStatus = 11;
}
}
m_iDebugStatus = 12;
eof = !fgets(buffer, sizeof(buffer), infile);
m_iDebugStatus = 13;
m_iDebugLines++;
}
m_iDebugStatus = 14;
calculatedCRC ^= 0xFFFFFFFF;
debug("Expected pcrc32=%x", expectedCRC);
debug("Calculated pcrc32=%x", calculatedCRC);
if (expectedCRC != calculatedCRC)
{
warn("CRC-Error for \"%s\"", m_szDestFilename);
}
fclose(infile);
fclose(outfile);
return body && end;
m_szArticleFilename = NULL;
}
/* from crc32.c (http://www.koders.com/c/fid699AFE0A656F0022C9D6B9D1743E697B69CE5815.aspx)
@@ -329,7 +208,7 @@ bool Decoder::DecodeYenc()
* calculate the crcTable for crc32-checksums.
* it is generated to the polynom [..]
*/
void Decoder::crc32gentab()
void YDecoder::crc32gentab()
{
unsigned long crc, poly;
int i, j;
@@ -363,21 +242,132 @@ void Decoder::crc32gentab()
* reached. the crc32-checksum will be
* the result.
*/
unsigned long Decoder::crc32m(unsigned long startCrc, unsigned char *block, unsigned int length)
unsigned long YDecoder::crc32m(unsigned long startCrc, unsigned char *block, unsigned int length)
{
register unsigned long crc;
unsigned long i;
crc = startCrc;
for (i = 0; i < length; i++)
register unsigned long crc = startCrc;
for (unsigned long i = 0; i < length; i++)
{
crc = ((crc >> 8) & 0x00FFFFFF) ^ crc_tab[(crc ^ *block++) & 0xFF];
}
return crc;
}
void Decoder::LogDebugInfo()
unsigned int YDecoder::DecodeBuffer(char* buffer)
{
debug(" Decoder: status=%i, lines=%i, filename=%s, ArticleFileName=%s",
m_iDebugStatus, m_iDebugLines, BaseFileName(m_szSrcFilename), m_szArticleFilename);
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;
}

View File

@@ -29,44 +29,61 @@
#include "Thread.h"
//#define DECODER_INTERNAL_FGETS
class Decoder
{
public:
enum EKind
{
dcUulib,
dcYenc
};
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;
static unsigned int crc_tab[256];
EKind m_eKind;
const char* m_szSrcFilename;
const char* m_szDestFilename;
char* m_szArticleFilename;
int m_iDebugStatus;
int m_iDebugLines;
bool DecodeUulib();
bool DecodeYenc();
static void crc32gentab();
unsigned long crc32m(unsigned long startCrc, unsigned char *block, unsigned int length);
static Mutex m_mutexDecoder;
public:
Decoder();
~Decoder();
bool Execute();
void SetKind(EKind eKind) { m_eKind = eKind; }
void SetSrcFilename(const char* szSrcFilename) { m_szSrcFilename = szSrcFilename; }
void SetDestFilename(const char* szDestFilename) { m_szDestFilename = szDestFilename; }
const char* GetArticleFilename() { return m_szArticleFilename; }
void LogDebugInfo();
virtual bool Execute();
};
static void Init();
static void Final();
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

View File

@@ -34,6 +34,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <stdio.h>
#include "nzbget.h"
#include "DiskState.h"
@@ -46,10 +47,9 @@ 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.
* If parameter "OnlyOrder" is set to true, only the file "queue" will
* be written to disk (It useful, if only the order of files in queue was changed).
* This function saves only file "queue".
*/
bool DiskState::Save(DownloadQueue* pDownloadQueue, bool OnlyOrder)
bool DiskState::Save(DownloadQueue* pDownloadQueue)
{
debug("Saving queue to disk");
@@ -74,14 +74,7 @@ bool DiskState::Save(DownloadQueue* pDownloadQueue, bool OnlyOrder)
FileInfo* pFileInfo = *it;
if (!pFileInfo->GetDeleted())
{
char fileName[1024];
snprintf(fileName, 1024, "%s%i", g_pOptions->GetQueueDir(), pFileInfo->GetID());
fileName[1024-1] = '\0';
fprintf(outfile, "%i,%i\n", pFileInfo->GetID(), (int)pFileInfo->GetPaused());
if (!OnlyOrder)
{
SaveFileInfo(pFileInfo, fileName);
}
cnt++;
}
}
@@ -95,6 +88,14 @@ bool DiskState::Save(DownloadQueue* pDownloadQueue, bool OnlyOrder)
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");
@@ -123,7 +124,7 @@ bool DiskState::Load(DownloadQueue* pDownloadQueue)
snprintf(fileName, 1024, "%s%i", g_pOptions->GetQueueDir(), id);
fileName[1024-1] = '\0';
FileInfo* pFileInfo = new FileInfo();
bool res = LoadFileInfo(pFileInfo, fileName);
bool res = LoadFileInfo(pFileInfo, fileName, true, false);
if (res)
{
pFileInfo->SetID(id);
@@ -149,6 +150,14 @@ bool DiskState::Load(DownloadQueue* pDownloadQueue)
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");
@@ -187,7 +196,7 @@ bool DiskState::SaveFileInfo(FileInfo* pFileInfo, const char* szFilename)
return true;
}
bool DiskState::LoadFileInfo(FileInfo* pFileInfo, const char * szFilename)
bool DiskState::LoadFileInfo(FileInfo* pFileInfo, const char * szFilename, bool bFileSummary, bool bArticles)
{
debug("Loading FileInfo from disk");
@@ -203,28 +212,28 @@ bool DiskState::LoadFileInfo(FileInfo* pFileInfo, const char * szFilename)
if (!fgets(buf, sizeof(buf), infile)) goto error;
if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n'
pFileInfo->SetNZBFilename(buf);
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'
pFileInfo->SetSubject(buf);
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'
pFileInfo->SetDestDir(buf);
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'
pFileInfo->SetFilename(buf);
if (bFileSummary) pFileInfo->SetFilename(buf);
int iFilenameConfirmed;
if (fscanf(infile, "%i\n", &iFilenameConfirmed) != 1) goto error;
pFileInfo->SetFilenameConfirmed(iFilenameConfirmed);
if (bFileSummary) pFileInfo->SetFilenameConfirmed(iFilenameConfirmed);
unsigned long High, Low;
if (fscanf(infile, "%lu,%lu\n", &High, &Low) != 2) goto error;
pFileInfo->SetSize((((unsigned long long)High) << 32) + Low);
pFileInfo->SetRemainingSize(pFileInfo->GetSize());
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;
@@ -232,23 +241,26 @@ bool DiskState::LoadFileInfo(FileInfo* pFileInfo, const char * szFilename)
{
if (!fgets(buf, sizeof(buf), infile)) goto error;
if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n'
pFileInfo->GetGroups()->push_back(strdup(buf));
if (bFileSummary) pFileInfo->GetGroups()->push_back(strdup(buf));
}
if (fscanf(infile, "%i\n", &size) != 1) goto error;
for (int i = 0; i < size; i++)
if (bArticles)
{
int PartNumber, PartSize;
if (fscanf(infile, "%i,%i\n", &PartNumber, &PartSize) != 2) goto error;
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'
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);
ArticleInfo* pArticleInfo = new ArticleInfo();
pArticleInfo->SetPartNumber(PartNumber);
pArticleInfo->SetSize(PartSize);
pArticleInfo->SetMessageID(buf);
pFileInfo->GetArticles()->push_back(pArticleInfo);
}
}
fclose(infile);
@@ -322,7 +334,7 @@ bool DiskState::Exists()
return fileExists;
}
bool DiskState::DiscardFileInfo(DownloadQueue* pDownloadQueue, FileInfo * pFileInfo)
bool DiskState::DiscardFile(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo)
{
// delete diskstate-file
char fileName[1024];
@@ -330,7 +342,7 @@ bool DiskState::DiscardFileInfo(DownloadQueue* pDownloadQueue, FileInfo * pFileI
fileName[1024-1] = '\0';
remove(fileName);
return Save(pDownloadQueue, true);
return !pDownloadQueue || Save(pDownloadQueue);
}
void DiskState::CleanupTempDir(DownloadQueue* pDownloadQueue)
@@ -349,11 +361,14 @@ void DiskState::CleanupTempDir(DownloadQueue* pDownloadQueue)
DirBrowser dir(g_pOptions->GetTempDir());
while (const char* filename = dir.Next())
{
bool del = strstr(filename, ".tmp") || strstr(filename, ".dec");
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)
{
int id, part;
if (sscanf(filename, "%i.%i", &id, &part) == 2)
if ((sscanf(filename, "%i.%i", &id, &part) == 2) ||
(sscanf(filename, "%i.out", &id) == 1))
{
del = true;
ptr = ids;

View File

@@ -32,14 +32,16 @@ class DiskState
{
private:
bool SaveFileInfo(FileInfo* pFileInfo, const char* szFilename);
bool LoadFileInfo(FileInfo* pFileInfo, const char* szFilename);
bool LoadFileInfo(FileInfo* pFileInfo, const char* szFilename, bool bFileSummary, bool bArticles);
public:
bool Exists();
bool Save(DownloadQueue* pDownloadQueue, bool OnlyOrder);
bool Save(DownloadQueue* pDownloadQueue);
bool Load(DownloadQueue* pDownloadQueue);
bool SaveFile(FileInfo* pFileInfo);
bool LoadArticles(FileInfo* pFileInfo);
bool Discard();
bool DiscardFileInfo(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo);
bool DiscardFile(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo);
void CleanupTempDir(DownloadQueue* pDownloadQueue);
};

View File

@@ -35,18 +35,12 @@
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include "nzbget.h"
#include "DownloadInfo.h"
#include "Options.h"
#include "Log.h"
#include "Util.h"
extern Options* g_pOptions;
int FileInfo::m_iIDGen = 0;
ArticleInfo::ArticleInfo()
@@ -99,6 +93,7 @@ FileInfo::FileInfo()
m_bPaused = false;
m_bDeleted = false;
m_iCompleted = 0;
m_bOutputInitialized = false;
m_iIDGen++;
m_iID = m_iIDGen;
}
@@ -124,17 +119,22 @@ FileInfo::~ FileInfo()
free(m_szNZBFilename);
}
for (Articles::iterator it = m_Articles.begin(); it != m_Articles.end() ;it++)
{
delete *it;
}
m_Articles.clear();
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)
@@ -168,43 +168,47 @@ void FileInfo::GetNiceNZBName(char* szBuffer, int iSize)
void FileInfo::MakeNiceNZBName(const char * szNZBFilename, char * szBuffer, int iSize)
{
strncpy(szBuffer, BaseFileName(szNZBFilename), iSize);
szBuffer[iSize-1] = '\0';
if (char* p = strrchr(szBuffer, '.')) *p = '\0';
}
char postname[1024];
const char* szBaseName = BaseFileName(szNZBFilename);
void FileInfo::ParseSubject()
{
char* fnstart = strstr(m_szSubject, "\"");
char* fnend = NULL;
if (fnstart)
// if .nzb file has a certain structure, try to strip out certain elements
if (sscanf(szBaseName, "msgid_%*d_%1023s", postname) == 1)
{
fnstart++;
fnend = strstr(fnstart, "\"");
}
if (fnend)
{
char fn[1024];
strncpy(fn, fnstart, fnend - fnstart);
fn[fnend - fnstart] = '\0';
m_szFilename = strdup(fn);
// OK, using stripped name
}
else
{
debug("Could not extract Filename from Subject: %s. Using Subject as Filename", m_szSubject);
m_szFilename = strdup(m_szSubject);
// using complete filename
strncpy(postname, szBaseName, 1024);
postname[1024-1] = '\0';
}
//replace bad chars in filename
char* p = m_szFilename;
while (*p)
// 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)
{
if (strchr("\\/:*?\"><'\n\r\t", *p))
// 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)
{
*p = '_';
strncpy(postname, "noname", 1024);
}
p++;
}
strncpy(szBuffer, postname, iSize);
szBuffer[iSize-1] = '\0';
}
void FileInfo::SetFilename(const char* szFilename)
@@ -216,57 +220,39 @@ void FileInfo::SetFilename(const char* szFilename)
m_szFilename = strdup(szFilename);
}
void FileInfo::BuildDestDirName(const char* szNZBFilename)
void FileInfo::MakeValidFilename()
{
char szBuffer[1024];
if (g_pOptions->GetAppendNZBDir())
{
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)
{
// wipe out certain structure
memset(strrchr(postname, '.'), 0, postname + strlen(postname) - strrchr(postname, '.'));
}
else
{
strncpy(postname, szBaseName, 1024);
postname[1024-1] = '\0';
}
// wipe out ".nzb"
memset(strrchr(postname, '.'), 0, postname + strlen(postname) - strrchr(postname, '.'));
snprintf(szBuffer, 1024, "%s%s", g_pOptions->GetDestDir(), postname);
szBuffer[1024-1] = '\0';
}
else
{
strncpy(szBuffer, g_pOptions->GetDestDir(), 1024);
szBuffer[1024-1] = '\0'; // trim the last slash, always returned by GetDestDir()
}
m_szDestDir = strdup(szBuffer);
::MakeValidFilename(m_szFilename, '_');
}
bool FileInfo::IsDupe()
void FileInfo::LockOutputFile()
{
debug("Checking if the file was already downloaded or queued");
m_mutexOutputFile.Lock();
}
void FileInfo::UnlockOutputFile()
{
m_mutexOutputFile.Unlock();
}
bool FileInfo::IsDupe(const char* szFilename)
{
struct stat buffer;
char fileName[1024];
bool exists = false;
snprintf(fileName, 1024, "%s%c%s", GetDestDir(), (int)PATH_SEPARATOR, GetFilename());
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)
if (exists)
{
snprintf(fileName, 1024, "%s%c%s_broken", GetDestDir(), (int)PATH_SEPARATOR, GetFilename());
fileName[1024-1] = '\0';
exists = !stat(fileName, &buffer);
return true;
}
return exists;
return false;
}

View File

@@ -30,6 +30,8 @@
#include <vector>
#include <deque>
#include "Thread.h"
class ArticleInfo
{
public:
@@ -83,6 +85,8 @@ private:
bool m_bDeleted;
bool m_bFilenameConfirmed;
int m_iCompleted;
bool m_bOutputInitialized;
Mutex m_mutexOutputFile;
static int m_iIDGen;
@@ -101,6 +105,7 @@ public:
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; }
@@ -111,13 +116,16 @@ public:
void SetPaused(bool Paused) { m_bPaused = Paused; }
bool GetDeleted() { return m_bDeleted; }
void SetDeleted(bool Deleted) { m_bDeleted = Deleted; }
void BuildDestDirName(const char* szNZBFilename);
const char* GetDestDir() { return m_szDestDir; }
void SetDestDir(const char* szDestDir);
int GetCompleted() { return m_iCompleted; }
void SetCompleted(int s) { m_iCompleted = s; }
void ParseSubject();
bool IsDupe();
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;

View File

@@ -36,6 +36,8 @@
#include <string.h>
#ifndef WIN32
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#endif
#include "nzbget.h"
@@ -45,9 +47,12 @@
#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()
@@ -63,8 +68,14 @@ Frontend::Frontend()
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()
@@ -77,7 +88,7 @@ bool Frontend::PrepareData()
}
if (!RequestMessages() || ((m_bSummary || m_bFileList) && !RequestFileList()))
{
printf("Unable to send request to nzbserver at %s (port %i) \n", g_pOptions->GetServerIP(), g_pOptions->GetServerPort());
printf("\nUnable to send request to nzbget-server at %s (port %i) \n", g_pOptions->GetServerIP(), g_pOptions->GetServerPort());
Stop();
return false;
}
@@ -91,6 +102,10 @@ bool Frontend::PrepareData()
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;
@@ -195,68 +210,24 @@ void Frontend::ServerDumpDebug()
}
}
bool Frontend::ServerEditQueue(EEditAction eAction, int iEntry)
bool Frontend::ServerEditQueue(QueueEditor::EEditAction eAction, int iOffset, int iID)
{
DownloadQueue* pDownloadQueue = LockQueue();
int ID = 0;
bool bPause = false;
if (iEntry >= 0 && iEntry < (int)pDownloadQueue->size())
{
FileInfo* pFileInfo = (*pDownloadQueue)[iEntry];
ID = pFileInfo->GetID();
bPause = !pFileInfo->GetPaused();
}
UnlockQueue();
if (ID == 0)
{
return false;
}
if (IsRemoteMode())
{
switch (eAction)
{
case eaPauseUnpause:
return RequestEditQueue(bPause ? NZBMessageRequest::eActionPause : NZBMessageRequest::eActionResume, 0, ID, ID);
case eaDelete:
return RequestEditQueue(NZBMessageRequest::eActionDelete, 0, ID, ID);
case eaMoveUp:
return RequestEditQueue(NZBMessageRequest::eActionMoveOffset, -1, ID, ID);
case eaMoveDown:
return RequestEditQueue(NZBMessageRequest::eActionMoveOffset, +1, ID, ID);
case eaMoveTop:
return RequestEditQueue(NZBMessageRequest::eActionMoveTop, 0, ID, ID);
case eaMoveBottom:
return RequestEditQueue(NZBMessageRequest::eActionMoveBottom, 0, ID, ID);
}
return RequestEditQueue(eAction, iOffset, iID);
}
else
{
switch (eAction)
{
case eaPauseUnpause:
return g_pQueueCoordinator->EditQueuePauseUnpauseEntry(ID, bPause);
case eaDelete:
return g_pQueueCoordinator->EditQueueDeleteEntry(ID);
case eaMoveUp:
return g_pQueueCoordinator->EditQueueMoveEntry(ID, -1, false);
case eaMoveDown:
return g_pQueueCoordinator->EditQueueMoveEntry(ID, +1, false);
case eaMoveTop:
return g_pQueueCoordinator->EditQueueMoveEntry(ID, -1000000, true);
case eaMoveBottom:
return g_pQueueCoordinator->EditQueueMoveEntry(ID, +1000000, true);
}
return g_pQueueCoordinator->GetQueueEditor()->EditEntry(iID, true, eAction, iOffset);
}
return false;
}
void Frontend::InitMessageBase(SNZBMessageBase* pMessageBase, int iRequest, int iSize)
void Frontend::InitMessageBase(SNZBRequestBase* pMessageBase, int iRequest, int iSize)
{
pMessageBase->m_iId = NZBMESSAGE_SIGNATURE;
pMessageBase->m_iType = iRequest;
pMessageBase->m_iSize = 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';
}
@@ -273,11 +244,11 @@ bool Frontend::RequestMessages()
}
SNZBLogRequest LogRequest;
InitMessageBase(&LogRequest.m_MessageBase, NZBMessageRequest::eRequestLog, sizeof(LogRequest));
LogRequest.m_iLines = m_iNeededLogEntries;
InitMessageBase(&LogRequest.m_MessageBase, eRemoteRequestLog, sizeof(LogRequest));
LogRequest.m_iLines = htonl(m_iNeededLogEntries);
if (m_iNeededLogEntries == 0)
{
LogRequest.m_iIDFrom = m_iNeededLogFirstID > 0 ? m_iNeededLogFirstID : 1;
LogRequest.m_iIDFrom = htonl(m_iNeededLogFirstID > 0 ? m_iNeededLogFirstID : 1);
}
else
{
@@ -290,17 +261,20 @@ bool Frontend::RequestMessages()
}
// Now listen for the returned log
SNZBLogRequestAnswer LogRequestAnswer;
if (connection.Recv((char*) &LogRequestAnswer, sizeof(LogRequestAnswer)) < 0)
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 (LogRequestAnswer.m_iTrailingDataLength > 0)
if (ntohl(LogResponse.m_iTrailingDataLength) > 0)
{
pBuf = (char*)malloc(LogRequestAnswer.m_iTrailingDataLength);
if (!connection.RecvAll(pBuf, LogRequestAnswer.m_iTrailingDataLength))
pBuf = (char*)malloc(ntohl(LogResponse.m_iTrailingDataLength));
if (!connection.RecvAll(pBuf, ntohl(LogResponse.m_iTrailingDataLength)))
{
free(pBuf);
return false;
@@ -309,19 +283,19 @@ bool Frontend::RequestMessages()
connection.Disconnect();
if (LogRequestAnswer.m_iTrailingDataLength > 0)
if (ntohl(LogResponse.m_iTrailingDataLength) > 0)
{
char* pBufPtr = (char*)pBuf;
for (int i = 0; i < LogRequestAnswer.m_iNrTrailingEntries; i++)
for (unsigned int i = 0; i < ntohl(LogResponse.m_iNrTrailingEntries); i++)
{
SNZBLogRequestAnswerEntry* pLogAnswer = (SNZBLogRequestAnswerEntry*) pBufPtr;
SNZBLogResponseEntry* pLogAnswer = (SNZBLogResponseEntry*) pBufPtr;
char* szText = pBufPtr + sizeof(SNZBLogRequestAnswerEntry);
char* szText = pBufPtr + sizeof(SNZBLogResponseEntry);
Message* pMessage = new Message(pLogAnswer->m_iID, (Message::EKind)pLogAnswer->m_iKind, pLogAnswer->m_tTime, szText);
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(SNZBLogRequestAnswerEntry) + pLogAnswer->m_iTextLen;
pBufPtr += sizeof(SNZBLogResponseEntry) + ntohl(pLogAnswer->m_iTextLen);
}
free(pBuf);
@@ -342,9 +316,9 @@ bool Frontend::RequestFileList()
}
SNZBListRequest ListRequest;
InitMessageBase(&ListRequest.m_MessageBase, NZBMessageRequest::eRequestList, sizeof(ListRequest));
ListRequest.m_bFileList = m_bFileList;
ListRequest.m_bServerState = m_bSummary;
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)
{
@@ -352,17 +326,20 @@ bool Frontend::RequestFileList()
}
// Now listen for the returned list
SNZBListRequestAnswer ListRequestAnswer;
if (connection.Recv((char*) &ListRequestAnswer, sizeof(ListRequestAnswer)) < 0)
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 (ListRequestAnswer.m_iTrailingDataLength > 0)
if (ntohl(ListResponse.m_iTrailingDataLength) > 0)
{
pBuf = (char*)malloc(ListRequestAnswer.m_iTrailingDataLength);
if (!connection.RecvAll(pBuf, ListRequestAnswer.m_iTrailingDataLength))
pBuf = (char*)malloc(ntohl(ListResponse.m_iTrailingDataLength));
if (!connection.RecvAll(pBuf, ntohl(ListResponse.m_iTrailingDataLength)))
{
free(pBuf);
return false;
@@ -373,40 +350,45 @@ bool Frontend::RequestFileList()
if (m_bSummary)
{
m_bPause = ListRequestAnswer.m_bServerPaused;
m_lRemainingSize = ListRequestAnswer.m_lRemainingSize;
m_fCurrentDownloadSpeed = ListRequestAnswer.m_fDownloadRate;
m_fDownloadLimit = ListRequestAnswer.m_fDownloadLimit;
m_iThreadCount = ListRequestAnswer.m_iThreadCount;
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 && ListRequestAnswer.m_iTrailingDataLength > 0)
if (m_bFileList && ntohl(ListResponse.m_iTrailingDataLength) > 0)
{
char* pBufPtr = (char*)pBuf;
for (int i = 0; i < ListRequestAnswer.m_iNrTrailingEntries; i++)
for (unsigned int i = 0; i < ntohl(ListResponse.m_iNrTrailingEntries); i++)
{
SNZBListRequestAnswerEntry* pListAnswer = (SNZBListRequestAnswerEntry*) pBufPtr;
SNZBListResponseEntry* pListAnswer = (SNZBListResponseEntry*) pBufPtr;
char* szNZBFilename = pBufPtr + sizeof(SNZBListRequestAnswerEntry);
char* szSubject = pBufPtr + sizeof(SNZBListRequestAnswerEntry) + pListAnswer->m_iNZBFilenameLen;
char* szFileName = pBufPtr + sizeof(SNZBListRequestAnswerEntry) + pListAnswer->m_iNZBFilenameLen + pListAnswer->m_iSubjectLen;
char* szDestDir = pBufPtr + sizeof(SNZBListRequestAnswerEntry) + pListAnswer->m_iNZBFilenameLen + pListAnswer->m_iSubjectLen + pListAnswer->m_iFilenameLen;
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(pListAnswer->m_iID);
pFileInfo->SetSize(pListAnswer->m_iFileSize);
pFileInfo->SetRemainingSize(pListAnswer->m_iRemainingSize);
pFileInfo->SetPaused(pListAnswer->m_bPaused);
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(pListAnswer->m_bFilenameConfirmed);
pFileInfo->SetFilenameConfirmed(ntohl(pListAnswer->m_bFilenameConfirmed));
pFileInfo->SetDestDir(szDestDir);
m_RemoteQueue.push_back(pFileInfo);
pBufPtr += sizeof(SNZBListRequestAnswerEntry) + pListAnswer->m_iNZBFilenameLen + pListAnswer->m_iSubjectLen +
pListAnswer->m_iFilenameLen + pListAnswer->m_iDestDirLen;
pBufPtr += sizeof(SNZBListResponseEntry) + ntohl(pListAnswer->m_iNZBFilenameLen) +
ntohl(pListAnswer->m_iSubjectLen) + ntohl(pListAnswer->m_iFilenameLen) + ntohl(pListAnswer->m_iDestDirLen);
}
}
if (pBuf)
@@ -438,9 +420,9 @@ bool Frontend::RequestDumpDebug()
return client.RequestServerDumpDebug();
}
bool Frontend::RequestEditQueue(int iAction, int iOffset, int iIDFrom, int iIDTo)
bool Frontend::RequestEditQueue(int iAction, int iOffset, int iID)
{
RemoteClient client;
client.SetVerbose(false);
return client.RequestServerEditQueue(iAction, iOffset, iIDFrom, iIDTo);
return client.RequestServerEditQueue(iAction, iOffset, &iID, 1, false);
}

View File

@@ -31,20 +31,10 @@
#include "Log.h"
#include "DownloadInfo.h"
#include "MessageBase.h"
#include "QueueEditor.h"
class Frontend : public Thread
{
public:
enum EEditAction
{
eaPauseUnpause,
eaDelete,
eaMoveUp,
eaMoveDown,
eaMoveTop,
eaMoveBottom
};
private:
Log::Messages m_RemoteMessages;
DownloadQueue m_RemoteQueue;
@@ -55,8 +45,9 @@ private:
protected:
bool m_bSummary;
bool m_bFileList;
unsigned int m_iNeededLogEntries;
unsigned int m_iNeededLogFirstID;
unsigned int m_iNeededLogEntries;
unsigned int m_iNeededLogFirstID;
int m_iUpdateInterval;
// summary
float m_fCurrentDownloadSpeed;
@@ -64,6 +55,11 @@ protected:
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();
@@ -72,15 +68,15 @@ protected:
DownloadQueue* LockQueue();
void UnlockQueue();
bool IsRemoteMode();
void InitMessageBase(SNZBMessageBase* pMessageBase, int iRequest, int iSize);
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(EEditAction eAction, int iEntry);
bool RequestEditQueue(int iAction, int iOffset, int iIDFrom, int iIDTo);
bool ServerEditQueue(QueueEditor::EEditAction eAction, int iOffset, int iEntry);
bool RequestEditQueue(int iAction, int iOffset, int iID);
public:
Frontend();

View File

@@ -269,7 +269,7 @@ void abort(const char* msg, ...)
g_pLog->m_mutexLog.Lock();
printf("%s", tmp2);
printf("\n%s", tmp2);
g_pLog->Filelog(tmp2);

View File

@@ -59,7 +59,7 @@ void LoggableFrontend::Run()
while (!IsStopped())
{
Update();
usleep(200 * 1000);
usleep(m_iUpdateInterval * 1000);
}
// Printing the last messages
Update();
@@ -106,25 +106,33 @@ void LoggableFrontend::Update()
void LoggableFrontend::PrintMessage(Message * pMessage)
{
#ifdef WIN32
char* msg = strdup(pMessage->GetText());
CharToOem(msg, msg);
#else
const char* msg = pMessage->GetText();
#endif
switch (pMessage->GetKind())
{
case Message::mkDebug:
fprintf(stdout, "[DEBUG] %s\n", msg);
printf("[DEBUG] %s\n", msg);
break;
case Message::mkError:
fprintf(stdout, "[ERROR] %s\n", msg);
printf("[ERROR] %s\n", msg);
break;
case Message::mkWarning:
fprintf(stdout, "[WARNING] %s\n", msg);
printf("[WARNING] %s\n", msg);
break;
case Message::mkInfo:
fprintf(stdout, "[INFO] %s\n", msg);
printf("[INFO] %s\n", msg);
break;
}
#ifdef WIN32
free(msg);
#endif
}
void LoggableFrontend::PrintSkip()
{
fprintf(stdout, ".....\n");
printf(".....\n");
}

View File

@@ -2,17 +2,17 @@ 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 RemoteServer.cpp RemoteServer.h \
NCursesFrontend.cpp NCursesFrontend.h NNTPConnection.cpp NNTPConnection.h RemoteClient.cpp \
RemoteClient.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 ServerPool.cpp ServerPool.h Thread.cpp Thread.h Util.cpp Util.h \
nzbget.cpp nzbget.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
EXTRA_DIST = nzbget.conf.example \
win32.h NTService.cpp NTService.h \
libpar2-0.2-MSVC8.patch libsigc++-2.0.18-MSVC8.patch \
nzbget.kdevelop nzbget.sln nzbget.vcproj
Makefile.cvs nzbget.kdevelop nzbget.sln nzbget.vcproj
clean-bak: rm *~

8
Makefile.cvs Normal file
View File

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

View File

@@ -1,8 +1,8 @@
# Makefile.in generated by automake 1.9.5 from Makefile.am.
# Makefile.in generated by automake 1.10 from Makefile.am.
# @configure_input@
# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
# 2003, 2004, 2005 Free Software Foundation, Inc.
# 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
# This Makefile.in is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
@@ -14,17 +14,11 @@
@SET_MAKE@
SOURCES = $(nzbget_SOURCES)
srcdir = @srcdir@
top_srcdir = @top_srcdir@
VPATH = @srcdir@
pkgdatadir = $(datadir)/@PACKAGE@
pkglibdir = $(libdir)/@PACKAGE@
pkgincludedir = $(includedir)/@PACKAGE@
top_builddir = .
am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
INSTALL = @INSTALL@
install_sh_DATA = $(install_sh) -c -m 644
install_sh_PROGRAM = $(install_sh) -c
install_sh_SCRIPT = $(install_sh) -c
@@ -39,17 +33,17 @@ POST_UNINSTALL = :
build_triplet = @build@
host_triplet = @host@
bin_PROGRAMS = nzbget$(EXEEXT)
subdir = .
DIST_COMMON = README $(am__configure_deps) $(srcdir)/Makefile.am \
$(srcdir)/Makefile.in $(srcdir)/config.h.in \
$(top_srcdir)/configure AUTHORS COPYING ChangeLog INSTALL NEWS \
config.guess config.sub depcomp install-sh missing
subdir = .
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
am__aclocal_m4_deps = $(top_srcdir)/configure.ac
am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
$(ACLOCAL_M4)
am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \
configure.lineno configure.status.lineno
configure.lineno config.status.lineno
mkinstalldirs = $(install_sh) -d
CONFIG_HEADER = config.h
CONFIG_CLEAN_FILES =
@@ -60,16 +54,16 @@ am_nzbget_OBJECTS = ArticleDownloader.$(OBJEXT) \
ColoredFrontend.$(OBJEXT) Connection.$(OBJEXT) \
Decoder.$(OBJEXT) DiskState.$(OBJEXT) DownloadInfo.$(OBJEXT) \
Frontend.$(OBJEXT) Log.$(OBJEXT) LoggableFrontend.$(OBJEXT) \
RemoteServer.$(OBJEXT) NCursesFrontend.$(OBJEXT) \
NNTPConnection.$(OBJEXT) RemoteClient.$(OBJEXT) \
NCursesFrontend.$(OBJEXT) NNTPConnection.$(OBJEXT) \
NZBFile.$(OBJEXT) NetAddress.$(OBJEXT) NewsServer.$(OBJEXT) \
Observer.$(OBJEXT) Options.$(OBJEXT) ParChecker.$(OBJEXT) \
PrePostProcessor.$(OBJEXT) QueueCoordinator.$(OBJEXT) \
ServerPool.$(OBJEXT) Thread.$(OBJEXT) Util.$(OBJEXT) \
nzbget.$(OBJEXT)
QueueEditor.$(OBJEXT) RemoteClient.$(OBJEXT) \
RemoteServer.$(OBJEXT) ServerPool.$(OBJEXT) Thread.$(OBJEXT) \
Util.$(OBJEXT) nzbget.$(OBJEXT)
nzbget_OBJECTS = $(am_nzbget_OBJECTS)
nzbget_LDADD = $(LDADD)
DEFAULT_INCLUDES = -I. -I$(srcdir) -I.
DEFAULT_INCLUDES = -I.@am__isrc@
depcomp = $(SHELL) $(top_srcdir)/depcomp
am__depfiles_maybe = depfiles
CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
@@ -98,8 +92,6 @@ distuninstallcheck_listfiles = find . -type f -print
distcleancheck_listfiles = find . -type f -print
ACLOCAL = @ACLOCAL@
ADDSRCS = @ADDSRCS@
AMDEP_FALSE = @AMDEP_FALSE@
AMDEP_TRUE = @AMDEP_TRUE@
AMTAR = @AMTAR@
AR = @AR@
AUTOCONF = @AUTOCONF@
@@ -125,6 +117,8 @@ ECHO_T = @ECHO_T@
EGREP = @EGREP@
EXEEXT = @EXEEXT@
FALSE = @FALSE@
GREP = @GREP@
INSTALL = @INSTALL@
INSTALL_DATA = @INSTALL_DATA@
INSTALL_PROGRAM = @INSTALL_PROGRAM@
INSTALL_SCRIPT = @INSTALL_SCRIPT@
@@ -137,6 +131,7 @@ LTLIBOBJS = @LTLIBOBJS@
MAKE = @MAKE@
MAKEINFO = @MAKEINFO@
MKDIR = @MKDIR@
MKDIR_P = @MKDIR_P@
MV = @MV@
OBJEXT = @OBJEXT@
PACKAGE = @PACKAGE@
@@ -154,14 +149,12 @@ STRIP = @STRIP@
TAR = @TAR@
TRUE = @TRUE@
VERSION = @VERSION@
abs_builddir = @abs_builddir@
abs_srcdir = @abs_srcdir@
abs_top_builddir = @abs_top_builddir@
abs_top_srcdir = @abs_top_srcdir@
ac_ct_CC = @ac_ct_CC@
ac_ct_CXX = @ac_ct_CXX@
ac_ct_RANLIB = @ac_ct_RANLIB@
ac_ct_STRIP = @ac_ct_STRIP@
am__fastdepCC_FALSE = @am__fastdepCC_FALSE@
am__fastdepCC_TRUE = @am__fastdepCC_TRUE@
am__fastdepCXX_FALSE = @am__fastdepCXX_FALSE@
am__fastdepCXX_TRUE = @am__fastdepCXX_TRUE@
am__include = @am__include@
am__leading_dot = @am__leading_dot@
am__quote = @am__quote@
@@ -173,43 +166,54 @@ build_alias = @build_alias@
build_cpu = @build_cpu@
build_os = @build_os@
build_vendor = @build_vendor@
builddir = @builddir@
datadir = @datadir@
datarootdir = @datarootdir@
docdir = @docdir@
dvidir = @dvidir@
exec_prefix = @exec_prefix@
host = @host@
host_alias = @host_alias@
host_cpu = @host_cpu@
host_os = @host_os@
host_vendor = @host_vendor@
htmldir = @htmldir@
includedir = @includedir@
infodir = @infodir@
install_sh = @install_sh@
libdir = @libdir@
libexecdir = @libexecdir@
localedir = @localedir@
localstatedir = @localstatedir@
mandir = @mandir@
mkdir_p = @mkdir_p@
oldincludedir = @oldincludedir@
pdfdir = @pdfdir@
prefix = @prefix@
program_transform_name = @program_transform_name@
psdir = @psdir@
sbindir = @sbindir@
sharedstatedir = @sharedstatedir@
srcdir = @srcdir@
sysconfdir = @sysconfdir@
target_alias = @target_alias@
top_builddir = @top_builddir@
top_srcdir = @top_srcdir@
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 RemoteServer.cpp RemoteServer.h \
NCursesFrontend.cpp NCursesFrontend.h NNTPConnection.cpp NNTPConnection.h RemoteClient.cpp \
RemoteClient.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 ServerPool.cpp ServerPool.h Thread.cpp Thread.h Util.cpp Util.h \
nzbget.cpp nzbget.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
EXTRA_DIST = nzbget.conf.example \
win32.h NTService.cpp NTService.h \
libpar2-0.2-MSVC8.patch libsigc++-2.0.18-MSVC8.patch \
nzbget.kdevelop nzbget.sln nzbget.vcproj
Makefile.cvs nzbget.kdevelop nzbget.sln nzbget.vcproj
all: config.h
$(MAKE) $(AM_MAKEFLAGS) all-am
@@ -253,7 +257,7 @@ $(ACLOCAL_M4): $(am__aclocal_m4_deps)
config.h: stamp-h1
@if test ! -f $@; then \
rm -f stamp-h1; \
$(MAKE) stamp-h1; \
$(MAKE) $(AM_MAKEFLAGS) stamp-h1; \
else :; fi
stamp-h1: $(srcdir)/config.h.in $(top_builddir)/config.status
@@ -268,7 +272,7 @@ distclean-hdr:
-rm -f config.h stamp-h1
install-binPROGRAMS: $(bin_PROGRAMS)
@$(NORMAL_INSTALL)
test -z "$(bindir)" || $(mkdir_p) "$(DESTDIR)$(bindir)"
test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)"
@list='$(bin_PROGRAMS)'; for p in $$list; do \
p1=`echo $$p|sed 's/$(EXEEXT)$$//'`; \
if test -f $$p \
@@ -291,7 +295,7 @@ clean-binPROGRAMS:
-test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS)
nzbget$(EXEEXT): $(nzbget_OBJECTS) $(nzbget_DEPENDENCIES)
@rm -f nzbget$(EXEEXT)
$(CXXLINK) $(nzbget_LDFLAGS) $(nzbget_OBJECTS) $(nzbget_LDADD) $(LIBS)
$(CXXLINK) $(nzbget_OBJECTS) $(nzbget_LDADD) $(LIBS)
mostlyclean-compile:
-rm -f *.$(OBJEXT)
@@ -318,6 +322,7 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ParChecker.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PrePostProcessor.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/QueueCoordinator.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/QueueEditor.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/RemoteClient.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/RemoteServer.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ServerPool.Po@am__quote@
@@ -326,19 +331,18 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nzbget.Po@am__quote@
.cpp.o:
@am__fastdepCXX_TRUE@ if $(CXXCOMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ $<; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi
@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXXCOMPILE) -c -o $@ $<
.cpp.obj:
@am__fastdepCXX_TRUE@ if $(CXXCOMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ `$(CYGPATH_W) '$<'`; \
@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi
@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
@am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCXX_FALSE@ $(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
uninstall-info-am:
ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
@@ -390,23 +394,22 @@ distclean-tags:
distdir: $(DISTFILES)
$(am__remove_distdir)
mkdir $(distdir)
@srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \
topsrcdirstrip=`echo "$(top_srcdir)" | sed 's|.|.|g'`; \
list='$(DISTFILES)'; for file in $$list; do \
case $$file in \
$(srcdir)/*) file=`echo "$$file" | sed "s|^$$srcdirstrip/||"`;; \
$(top_srcdir)/*) file=`echo "$$file" | sed "s|^$$topsrcdirstrip/|$(top_builddir)/|"`;; \
esac; \
test -d $(distdir) || mkdir $(distdir)
@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
list='$(DISTFILES)'; \
dist_files=`for file in $$list; do echo $$file; done | \
sed -e "s|^$$srcdirstrip/||;t" \
-e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
case $$dist_files in \
*/*) $(MKDIR_P) `echo "$$dist_files" | \
sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
sort -u` ;; \
esac; \
for file in $$dist_files; do \
if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
dir=`echo "$$file" | sed -e 's,/[^/]*$$,,'`; \
if test "$$dir" != "$$file" && test "$$dir" != "."; then \
dir="/$$dir"; \
$(mkdir_p) "$(distdir)$$dir"; \
else \
dir=''; \
fi; \
if test -d $$d/$$file; then \
dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \
fi; \
@@ -420,7 +423,7 @@ distdir: $(DISTFILES)
-find $(distdir) -type d ! -perm -777 -exec chmod a+rwx {} \; -o \
! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \
! -type d ! -perm -400 -exec chmod a+r {} \; -o \
! -type d ! -perm -444 -exec $(SHELL) $(install_sh) -c -m a+r {} {} \; \
! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \
|| chmod -R a+r $(distdir)
dist-gzip: distdir
tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz
@@ -495,7 +498,7 @@ distcheck: dist
$(am__remove_distdir)
@(echo "$(distdir) archives ready for distribution: "; \
list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \
sed -e '1{h;s/./=/g;p;x;}' -e '$${p;x;}'
sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x'
distuninstallcheck:
@cd $(distuninstallcheck_dir) \
&& test `$(distuninstallcheck_listfiles) | wc -l` -le 1 \
@@ -519,7 +522,7 @@ check: check-am
all-am: Makefile $(PROGRAMS) config.h
installdirs:
for dir in "$(DESTDIR)$(bindir)"; do \
test -z "$$dir" || $(mkdir_p) "$$dir"; \
test -z "$$dir" || $(MKDIR_P) "$$dir"; \
done
install: install-am
install-exec: install-exec-am
@@ -568,12 +571,20 @@ info-am:
install-data-am:
install-dvi: install-dvi-am
install-exec-am: install-binPROGRAMS
install-html: install-html-am
install-info: install-info-am
install-man:
install-pdf: install-pdf-am
install-ps: install-ps-am
installcheck-am:
maintainer-clean: maintainer-clean-am
@@ -595,7 +606,9 @@ ps: ps-am
ps-am:
uninstall-am: uninstall-binPROGRAMS uninstall-info-am
uninstall-am: uninstall-binPROGRAMS
.MAKE: install-am install-strip
.PHONY: CTAGS GTAGS all all-am am--refresh check check-am clean \
clean-binPROGRAMS clean-generic ctags dist dist-all dist-bzip2 \
@@ -603,13 +616,14 @@ uninstall-am: uninstall-binPROGRAMS uninstall-info-am
distclean-compile distclean-generic distclean-hdr \
distclean-tags distcleancheck distdir distuninstallcheck dvi \
dvi-am html html-am info info-am install install-am \
install-binPROGRAMS install-data install-data-am install-exec \
install-exec-am install-info install-info-am install-man \
install-binPROGRAMS install-data install-data-am install-dvi \
install-dvi-am install-exec install-exec-am install-html \
install-html-am install-info install-info-am install-man \
install-pdf install-pdf-am install-ps install-ps-am \
install-strip installcheck installcheck-am installdirs \
maintainer-clean maintainer-clean-generic mostlyclean \
mostlyclean-compile mostlyclean-generic pdf pdf-am ps ps-am \
tags uninstall uninstall-am uninstall-binPROGRAMS \
uninstall-info-am
tags uninstall uninstall-am uninstall-binPROGRAMS
clean-bak: rm *~

View File

@@ -27,10 +27,18 @@
#ifndef MESSAGEBASE_H
#define MESSAGEBASE_H
static const int32_t NZBMESSAGE_SIGNATURE = 0x6E7A6201; // = "nzb"-version-1
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
@@ -38,146 +46,253 @@ static const int NZBREQUESTPASSWORDSIZE = 32;
#pragma pack(1)
#endif
namespace NZBMessageRequest
// Possible values for field "m_iType" of struct "SNZBRequestBase":
enum eRemoteRequest
{
enum
{
eRequestDownload = 1,
eRequestPauseUnpause,
eRequestList,
eRequestSetDownloadRate,
eRequestDumpDebug,
eRequestEditQueue,
eRequestLog,
eRequestShutdown
eRemoteRequestDownload = 1,
eRemoteRequestPauseUnpause,
eRemoteRequestList,
eRemoteRequestSetDownloadRate,
eRemoteRequestDumpDebug,
eRemoteRequestEditQueue,
eRemoteRequestLog,
eRemoteRequestShutdown,
eRemoteRequestVersion
};
// Possible values for field "m_iAction" of struct "SNZBEditQueueRequest":
enum
// 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
{
eActionMoveOffset = 1, // move to m_iOffset relative to the current position in queue
eActionMoveTop, // move to top of queue
eActionMoveBottom, // move to bottom of queue
eActionPause, // pause
eActionResume, // resume (unpause)
eActionDelete // delete
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 NZBMessageBase struct
struct SNZBMessageBase
// The basic SNZBRequestBase struct, used in all requests
struct SNZBRequestBase
{
int32_t m_iId; // Id must be 'nzbg' in integer-value
int32_t m_iType; // message type, must be > 0
int32_t m_iSize; // Size of the entire struct
char m_szPassword[ NZBREQUESTPASSWORDSIZE ]; // Password needs to be in every request
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
{
SNZBMessageBase m_MessageBase; // Must be the first in the struct
char m_szFilename[ NZBREQUESTFILENAMESIZE ];
int32_t m_bAddFirst;
int32_t m_iTrailingDataLength;
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 list request
// 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
{
SNZBMessageBase m_MessageBase; // Must be the first in the struct
int32_t m_bFileList;
int32_t m_bServerState;
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 request-answer
struct SNZBListRequestAnswer
// A list response
struct SNZBListResponse
{
int32_t m_iSize; // Size of the entire struct
int32_t m_iEntrySize; // Size of the SNZBListRequestAnswerEntry-struct
long long m_lRemainingSize;
float m_fDownloadRate;
float m_fDownloadLimit;
int32_t m_bServerPaused;
int32_t m_iThreadCount;
int32_t m_iNrTrailingEntries;
int32_t m_iTrailingDataLength;
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 request-answer entry
struct SNZBListRequestAnswerEntry
// A list response entry
struct SNZBListResponseEntry
{
int32_t m_iNZBFilenameLen;
int32_t m_iSubjectLen;
int32_t m_iFilenameLen;
int32_t m_iDestDirLen;
int32_t m_iFileSize;
int32_t m_bFilenameConfirmed;
int32_t m_iRemainingSize;
int32_t m_iID;
int32_t m_bPaused;
//char m_szNZBFilename[0]; // variable sized
//char m_szSubject[0]; // variable sized
//char m_szFilename[0]; // variable sized
//char m_szDestDir[0]; // variable sized
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
{
SNZBMessageBase 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".
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 request-answer
struct SNZBLogRequestAnswer
// A log response
struct SNZBLogResponse
{
int32_t m_iSize; // Size of the entire struct
int32_t m_iEntrySize; // Size of the SNZBLogRequestAnswerEntry-struct
int32_t m_iNrTrailingEntries;
int32_t m_iTrailingDataLength;
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 request-answer entry
struct SNZBLogRequestAnswerEntry
// A log response entry
struct SNZBLogResponseEntry
{
int32_t m_iTextLen;
int32_t m_iID;
int32_t m_iKind; // see Message::Kind in "Log.h"
time_t m_tTime;
//char m_szText[0]; // variable sized
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
{
SNZBMessageBase m_MessageBase; // Must be the first in the struct
int32_t m_bPause; // The value g_bPause should be set to
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
{
SNZBMessageBase m_MessageBase; // Must be the first in the struct
float m_fDownloadRate;
SNZBRequestBase m_MessageBase; // Must be the first in the struct
int32_t m_iDownloadRate; // Speed limit, in Bytes pro Second
};
// A download request
// 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
{
SNZBMessageBase m_MessageBase; // Must be the first in the struct
int32_t m_iIDFrom; // ID of the first file in the range
int32_t m_iIDTo; // ID of the last file in the range, not used yet, must be same as m_iIDFrom
int32_t m_iAction; // action to be done, see later
int32_t m_iOffset; // Offset to move (for m_iAction = 0)
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
{
SNZBMessageBase m_MessageBase; // Must be the first in the struct
int32_t m_iLevel; // Future use
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

View File

@@ -49,6 +49,7 @@
#include "nzbget.h"
#include "NCursesFrontend.h"
#include "Options.h"
#ifdef HAVE_CURSES_H
// curses.h header must be included last to avoid problems on Solaris
@@ -58,6 +59,7 @@
#undef erase
#endif
extern Options* g_pOptions;
extern void ExitProc();
static const int NCURSES_COLORPAIR_TEXT = 1;
@@ -99,6 +101,25 @@ static const int READKEY_EMPTY = ERR;
#endif
NCursesFrontend::GroupInfo::GroupInfo(int iID, const char* szNZBFilename)
{
m_iID = iID;
m_szNZBFilename = strdup(szNZBFilename);
m_iFileCount = 0;
m_lSize = 0;
m_lRemainingSize = 0;
m_lPausedSize = 0;
m_iParCount = 0;
}
NCursesFrontend::GroupInfo::~GroupInfo()
{
if (m_szNZBFilename)
{
free(m_szNZBFilename);
}
}
NCursesFrontend::NCursesFrontend()
{
m_iScreenHeight = 0;
@@ -108,7 +129,6 @@ NCursesFrontend::NCursesFrontend()
m_bSummary = true;
m_bFileList = true;
m_iNeededLogEntries = 0;
m_iSkipUpdateData = 0;
m_iQueueWinTop = 0;
m_iQueueWinHeight = 0;
m_iQueueWinClientHeight = 0;
@@ -117,8 +137,15 @@ NCursesFrontend::NCursesFrontend()
m_iMessagesWinClientHeight = 0;
m_iSelectedQueueEntry = 0;
m_iQueueScrollOffset = 0;
m_bShowNZBname = true;
m_bShowNZBname = g_pOptions->GetCursesNZBName();
m_bShowTimestamp = g_pOptions->GetCursesTime();
m_bGroupFiles = g_pOptions->GetCursesGroup();
m_QueueWindowPercentage = 0.5f;
m_iDataUpdatePos = 0;
m_iLastEditEntry = -1;
m_bLastPausePars = false;
m_groupQueue.clear();
// Setup curses
#ifdef WIN32
@@ -205,39 +232,67 @@ void NCursesFrontend::Run()
{
debug("Entering NCursesFrontend-loop");
int iScreenUpdateInterval = 25;
int iScreenUpdatePos = 0;
m_iDataUpdatePos = 0;
while (!IsStopped())
{
// The data (queue and log) is updated each 200 msec,
// The data (queue and log) is updated each m_iUpdateInterval msec,
// but the window is updated more often for better reaction on user's input
Update();
usleep(25 * 1000);
m_iSkipUpdateData -= 25;
if (m_iSkipUpdateData < 0)
{
m_iSkipUpdateData = 200;
}
if (iScreenUpdatePos <= 0)
{
iScreenUpdatePos = iScreenUpdateInterval;
Update();
if (m_iDataUpdatePos <= 0)
{
m_iDataUpdatePos = m_iUpdateInterval;
}
}
usleep(10 * 1000);
iScreenUpdatePos -= 10;
m_iDataUpdatePos -= 10;
}
FreeData();
ClearGroupQueue();
debug("Exiting NCursesFrontend-loop");
}
void NCursesFrontend::NeedUpdateData()
{
m_iDataUpdatePos = 10;
}
void NCursesFrontend::Update()
{
// Figure out how big the screen is
CalcWindowSizes();
if (!m_iSkipUpdateData)
if (m_iDataUpdatePos <= 0)
{
FreeData();
ClearGroupQueue();
m_iNeededLogEntries = m_iMessagesWinClientHeight;
if (!PrepareData())
{
return;
}
PrepareGroupQueue();
}
if (m_eInputMode == eEditQueue)
{
int iQueueSize = CalcQueueSize();
if (iQueueSize == 0)
{
m_iSelectedQueueEntry = 0;
m_eInputMode = eNormal;
}
}
//------------------------------------------
// Print Current NZBQueue
//------------------------------------------
@@ -291,9 +346,7 @@ void NCursesFrontend::CalcWindowSizes()
m_iScreenWidth = iNrColumns;
}
DownloadQueue* pDownloadQueue = LockQueue();
int iQueueSize = pDownloadQueue->size();
UnlockQueue();
int iQueueSize = CalcQueueSize();
m_iQueueWinTop = 0;
m_iQueueWinHeight = (int)((float) (m_iScreenHeight - 2) * m_QueueWindowPercentage);
@@ -316,6 +369,21 @@ void NCursesFrontend::CalcWindowSizes()
}
}
int NCursesFrontend::CalcQueueSize()
{
if (m_bGroupFiles)
{
return m_groupQueue.size();
}
else
{
DownloadQueue* pDownloadQueue = LockQueue();
int iQueueSize = pDownloadQueue->size();
UnlockQueue();
return iQueueSize;
}
}
void NCursesFrontend::PlotLine(const char * szString, int iRow, int iPos, int iColorPair)
{
char szBuffer[MAX_SCREEN_WIDTH];
@@ -337,7 +405,9 @@ void NCursesFrontend::PlotText(const char * szString, int iRow, int iPos, int iC
int len = strlen(szString);
for (int i = 0; i < len; i++)
{
m_pScreenBuffer[iBufPos + i].Char.AsciiChar = szString[i];
char c = szString[i];
CharToOemBuff(&c, &c, 1);
m_pScreenBuffer[iBufPos + i].Char.AsciiChar = c;
m_pScreenBuffer[iBufPos + i].Attributes = m_ColorAttr[iColorPair];
}
#else
@@ -451,7 +521,40 @@ int NCursesFrontend::PrintMessage(Message* Msg, int iRow, int iMaxLines)
const int iMessageTypeColor[] = { NCURSES_COLORPAIR_INFO, NCURSES_COLORPAIR_WARNING,
NCURSES_COLORPAIR_ERROR, NCURSES_COLORPAIR_DEBUG };
const char* szText = Msg->GetText();
char* szText = (char*)Msg->GetText();
if (m_bShowTimestamp)
{
int iLen = strlen(szText) + 50;
szText = (char*)malloc(iLen);
time_t rawtime = Msg->GetTime();
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
snprintf(szText, iLen, "%s - %s", szTime, Msg->GetText());
szText[iLen - 1] = '\0';
}
else
{
szText = strdup(szText);
}
// replace CR and LF characters with spaces
for (char* p = szText; *p; p++)
{
if (*p == '\n' || *p == '\r')
{
*p = ' ';
}
}
int iLen = strlen(szText);
int iWinWidth = m_iScreenWidth - 8;
int iMsgLines = iLen / iWinWidth;
@@ -475,7 +578,9 @@ int NCursesFrontend::PrintMessage(Message* Msg, int iRow, int iMaxLines)
}
iLines++;
}
free(szText);
return iLines;
}
@@ -487,76 +592,83 @@ void NCursesFrontend::PrintStatus()
char timeString[100];
timeString[0] = '\0';
if (m_fCurrentDownloadSpeed > 0.0)
float fCurrentDownloadSpeed = m_bStandBy ? 0 : m_fCurrentDownloadSpeed;
if (fCurrentDownloadSpeed > 0.0 && !m_bPause)
{
long long remain_sec = (long long)(m_lRemainingSize / (m_fCurrentDownloadSpeed * 1024));
int h = 0;
int m = 0;
int s = 0;
while (remain_sec > 3600)
{
h++;
remain_sec -= 3600;
}
while (remain_sec > 60)
{
m++;
remain_sec -= 60;
}
s = remain_sec;
sprintf(timeString, "(~ %.2d:%.2d:%.2d)", h, m, s);
long long remain_sec = (long long)(m_lRemainingSize / (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);
sprintf(szDownloadLimit, ", Limit %.0f KB/s", m_fDownloadLimit);
}
else
{
szDownloadLimit[0] = 0;
}
snprintf(tmp, MAX_SCREEN_WIDTH, " %d threads running, %.0f KB/s, %.2f MB remaining %s %s %s", m_iThreadCount, m_fCurrentDownloadSpeed, (float)(m_lRemainingSize / 1024.0 / 1024.0), timeString, m_bPause ? "Paused" : "", szDownloadLimit);
char szParStatus[128];
if (m_iParJobCount > 0)
{
sprintf(szParStatus, ", %i par%s", m_iParJobCount, m_iParJobCount > 1 ? "s" : "");
}
else
{
szParStatus[0] = 0;
}
float fAverageSpeed = m_iDnTimeSec > 0 ? m_iAllBytes / m_iDnTimeSec / 1024 : 0;
snprintf(tmp, MAX_SCREEN_WIDTH, " %d threads, %.0f KB/s, %.2f MB remaining%s%s%s%s, Avg. %.0f KB/s",
m_iThreadCount, fCurrentDownloadSpeed, (float)(m_lRemainingSize / 1024.0 / 1024.0), timeString,
szParStatus, m_bPause ? (m_bStandBy ? ", Paused" : ", Pausing") : "", szDownloadLimit, fAverageSpeed);
tmp[MAX_SCREEN_WIDTH - 1] = '\0';
PlotLine(tmp, iStatusRow, 0, NCURSES_COLORPAIR_STATUS);
}
void NCursesFrontend::PrintKeyInputBar()
{
DownloadQueue* pDownloadQueue = LockQueue();
int iQueueSize = pDownloadQueue->size();
UnlockQueue();
int iQueueSize = CalcQueueSize();
int iInputBarRow = m_iScreenHeight - 1;
switch (m_eInputMode)
{
case eNormal:
PlotLine("(Q)uit | (E)dit | (P)ause | (R)ate | n(Z)b | (W)indow", iInputBarRow, 0, NCURSES_COLORPAIR_KEYBAR);
if (m_bGroupFiles)
{
PlotLine("(Q)uit | (E)dit | (P)ause | (R)ate | (W)indow | (G)roup | (T)ime", iInputBarRow, 0, NCURSES_COLORPAIR_KEYBAR);
}
else
{
PlotLine("(Q)uit | (E)dit | (P)ause | (R)ate | (W)indow | (G)roup | (T)ime | n(Z)b", iInputBarRow, 0, NCURSES_COLORPAIR_KEYBAR);
}
break;
case eEditQueue:
{
char* szStatus = NULL;
if (m_iSelectedQueueEntry > 0 && iQueueSize > 1 && m_iSelectedQueueEntry == iQueueSize - 1)
{
szStatus = "(Q)uit | (E)xit | (P)ause | (D)elete | (U)p/(T)op";
}
else if (iQueueSize > 1 && m_iSelectedQueueEntry == 0)
{
szStatus = "(Q)uit | (E)xit | (P)ause | (D)elete | dow(N)/(B)ottom";
}
else if (iQueueSize > 1)
{
szStatus = "(Q)uit | (E)xit | (P)ause | (D)elete | (U)p/dow(N)/(T)op/(B)ottom";
}
else
{
szStatus = "(Q)uit";
}
char* szStatus = NULL;
if (m_iSelectedQueueEntry > 0 && iQueueSize > 1 && m_iSelectedQueueEntry == iQueueSize - 1)
{
szStatus = "(Q)uit | (E)xit | (P)ause | (D)elete | (U)p/(T)op";
}
else if (iQueueSize > 1 && m_iSelectedQueueEntry == 0)
{
szStatus = "(Q)uit | (E)xit | (P)ause | (D)elete | dow(N)/(B)ottom";
}
else if (iQueueSize > 1)
{
szStatus = "(Q)uit | (E)xit | (P)ause | (D)elete | (U)p/dow(N)/(T)op/(B)ottom";
}
else
{
szStatus = "(Q)uit | (E)xit | (P)ause | (D)elete";
}
PlotLine(szStatus, iInputBarRow, 0, NCURSES_COLORPAIR_KEYBAR);
PlotLine(szStatus, iInputBarRow, 0, NCURSES_COLORPAIR_KEYBAR);
break;
}
case eDownloadRate:
@@ -571,6 +683,18 @@ void NCursesFrontend::PrintKeyInputBar()
}
void NCursesFrontend::PrintQueue()
{
if (m_bGroupFiles)
{
PrintGroupQueue();
}
else
{
PrintFileQueue();
}
}
void NCursesFrontend::PrintFileQueue()
{
int iLineNr = m_iQueueWinTop;
@@ -580,7 +704,7 @@ void NCursesFrontend::PrintQueue()
char szBuffer[MAX_SCREEN_WIDTH];
snprintf(szBuffer, sizeof(szBuffer), "%s Files for downloading", m_bUseColor ? "" : "*** ");
szBuffer[MAX_SCREEN_WIDTH - 1] = '\0';
PlotLine(szBuffer, iLineNr++, 0, NCURSES_COLORPAIR_INFOLINE);
PrintTopHeader(szBuffer, iLineNr++, true);
PlotLine("Ready to receive nzb-job", iLineNr++, 0, NCURSES_COLORPAIR_TEXT);
}
else
@@ -617,7 +741,7 @@ void NCursesFrontend::PrintQueue()
snprintf(szBuffer, sizeof(szBuffer), " %sFiles for downloading - %i / %i files in queue - %s / %s",
m_bUseColor ? "" : "*** ", pDownloadQueue->size(), pDownloadQueue->size() - iPausedFiles, szRemaining, szUnpaused);
szBuffer[MAX_SCREEN_WIDTH - 1] = '\0';
PlotLine(szBuffer, m_iQueueWinTop, 0, NCURSES_COLORPAIR_INFOLINE);
PrintTopHeader(szBuffer, m_iQueueWinTop, true);
}
UnlockQueue();
}
@@ -689,11 +813,281 @@ void NCursesFrontend::FormatFileSize(char * szBuffer, int iBufLen, long long lFi
szBuffer[iBufLen - 1] = '\0';
}
void NCursesFrontend::PrintTopHeader(char* szHeader, int iLineNr, bool bUpTime)
{
char szBuffer[MAX_SCREEN_WIDTH];
snprintf(szBuffer, sizeof(szBuffer), "%-*s", m_iScreenWidth, szHeader);
szBuffer[MAX_SCREEN_WIDTH - 1] = '\0';
int iHeaderLen = strlen(szHeader);
int iCharsLeft = m_iScreenWidth - iHeaderLen - 2;
int iTime = bUpTime ? m_iUpTimeSec : m_iDnTimeSec;
int d = iTime / 3600 / 24;
int h = (iTime % (3600 * 24)) / 3600;
int m = (iTime % 3600) / 60;
int s = iTime % 60;
char szTime[30];
if (d == 0)
{
snprintf(szTime, 30, "%.2d:%.2d:%.2d", h, m, s);
if ((int)strlen(szTime) > iCharsLeft)
{
snprintf(szTime, 30, "%.2d:%.2d", h, m);
}
}
else
{
snprintf(szTime, 30, "%i %s %.2d:%.2d:%.2d", d, (d == 1 ? "day" : "days"), h, m, s);
if ((int)strlen(szTime) > iCharsLeft)
{
snprintf(szTime, 30, "%id %.2d:%.2d:%.2d", d, h, m, s);
}
if ((int)strlen(szTime) > iCharsLeft)
{
snprintf(szTime, 30, "%id %.2d:%.2d", d, h, m);
}
}
szTime[29] = '\0';
const char* szShortCap = bUpTime ? " Up " : "Dn ";
const char* szLongCap = bUpTime ? " Uptime " : " Download-time ";
int iTimeLen = strlen(szTime);
int iShortCapLen = strlen(szShortCap);
int iLongCapLen = strlen(szLongCap);
if (iCharsLeft - iTimeLen - iLongCapLen >= 0)
{
snprintf(szBuffer + m_iScreenWidth - iTimeLen - iLongCapLen, MAX_SCREEN_WIDTH - (m_iScreenWidth - iTimeLen - iLongCapLen), "%s%s", szLongCap, szTime);
}
else if (iCharsLeft - iTimeLen - iShortCapLen >= 0)
{
snprintf(szBuffer + m_iScreenWidth - iTimeLen - iShortCapLen, MAX_SCREEN_WIDTH - (m_iScreenWidth - iTimeLen - iShortCapLen), "%s%s", szShortCap, szTime);
}
else if (iCharsLeft - iTimeLen >= 0)
{
snprintf(szBuffer + m_iScreenWidth - iTimeLen, MAX_SCREEN_WIDTH - (m_iScreenWidth - iTimeLen), "%s", szTime);
}
PlotLine(szBuffer, iLineNr, 0, NCURSES_COLORPAIR_INFOLINE);
}
void NCursesFrontend::PrintGroupQueue()
{
int iLineNr = m_iQueueWinTop;
GroupQueue* pGroupQueue = &m_groupQueue;
if (pGroupQueue->empty())
{
char szBuffer[MAX_SCREEN_WIDTH];
snprintf(szBuffer, sizeof(szBuffer), "%s NZBs for downloading", m_bUseColor ? "" : "*** ");
szBuffer[MAX_SCREEN_WIDTH - 1] = '\0';
PrintTopHeader(szBuffer, iLineNr++, false);
PlotLine("Ready to receive nzb-job", iLineNr++, 0, NCURSES_COLORPAIR_TEXT);
}
else
{
iLineNr++;
long long lRemaining = 0;
long long lPaused = 0;
int i = 0;
for (GroupQueue::iterator it = pGroupQueue->begin(); it != pGroupQueue->end(); it++, i++)
{
GroupInfo* pGroupInfo = *it;
if (i >= m_iQueueScrollOffset && i < m_iQueueScrollOffset + m_iQueueWinHeight -1)
{
PrintGroupname(pGroupInfo, iLineNr++, i == m_iSelectedQueueEntry);
}
lRemaining += pGroupInfo->GetRemainingSize();
lPaused += pGroupInfo->GetPausedSize();
}
char szRemaining[20];
FormatFileSize(szRemaining, sizeof(szRemaining), lRemaining);
char szUnpaused[20];
FormatFileSize(szUnpaused, sizeof(szUnpaused), lRemaining - lPaused);
char szBuffer[MAX_SCREEN_WIDTH];
snprintf(szBuffer, sizeof(szBuffer), " %sNZBs for downloading - %i NZBs in queue - %s / %s",
m_bUseColor ? "" : "*** ", pGroupQueue->size(), szRemaining, szUnpaused);
szBuffer[MAX_SCREEN_WIDTH - 1] = '\0';
PrintTopHeader(szBuffer, m_iQueueWinTop, false);
}
}
void NCursesFrontend::PrintGroupname(GroupInfo * pGroupInfo, int iRow, bool bSelected)
{
int color = 0;
const char* Brace1 = "[";
const char* Brace2 = "]";
if (m_eInputMode == eEditQueue && bSelected)
{
color = NCURSES_COLORPAIR_TEXTHIGHL;
if (!m_bUseColor)
{
Brace1 = "<";
Brace2 = ">";
}
}
else
{
color = NCURSES_COLORPAIR_TEXT;
}
long long lUnpausedRemainingSize = pGroupInfo->GetRemainingSize() - pGroupInfo->GetPausedSize();
char szRemaining[20];
FormatFileSize(szRemaining, sizeof(szRemaining), lUnpausedRemainingSize);
char szPaused[20];
szPaused[0] = '\0';
if (pGroupInfo->GetPausedSize() > 0)
{
char szPausedSize[20];
FormatFileSize(szPausedSize, sizeof(szPausedSize), pGroupInfo->GetPausedSize());
sprintf(szPaused, " + %s paused", szPausedSize);
}
char szNZBNiceName[1024];
FileInfo::MakeNiceNZBName(pGroupInfo->GetNZBFilename(), szNZBNiceName, 1023);
char szBuffer[MAX_SCREEN_WIDTH];
snprintf(szBuffer, MAX_SCREEN_WIDTH, "%s%i%s %s (%i file%s, %s%s)", Brace1, pGroupInfo->GetID(), Brace2, szNZBNiceName,
pGroupInfo->m_iFileCount, pGroupInfo->m_iFileCount > 1 ? "s" : "", szRemaining, szPaused);
szBuffer[MAX_SCREEN_WIDTH - 1] = '\0';
PlotLine(szBuffer, iRow, 0, color);
}
void NCursesFrontend::PrepareGroupQueue()
{
m_groupQueue.clear();
DownloadQueue* pDownloadQueue = LockQueue();
for (DownloadQueue::iterator it = pDownloadQueue->begin(); it != pDownloadQueue->end(); it++)
{
FileInfo* pFileInfo = *it;
GroupInfo* pGroupInfo = NULL;
for (GroupQueue::iterator itg = m_groupQueue.begin(); itg != m_groupQueue.end(); itg++)
{
GroupInfo* pGroupInfo1 = *itg;
if (!strcmp(pGroupInfo1->GetNZBFilename(), pFileInfo->GetNZBFilename()))
{
pGroupInfo = pGroupInfo1;
break;
}
}
if (!pGroupInfo)
{
pGroupInfo = new GroupInfo(pFileInfo->GetID(), pFileInfo->GetNZBFilename());
m_groupQueue.push_back(pGroupInfo);
}
pGroupInfo->m_iFileCount++;
pGroupInfo->m_lSize += pFileInfo->GetSize();
pGroupInfo->m_lRemainingSize += pFileInfo->GetRemainingSize();
if (pFileInfo->GetPaused())
{
pGroupInfo->m_lPausedSize += pFileInfo->GetRemainingSize();
}
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"))
{
pGroupInfo->m_iParCount++;
}
}
UnlockQueue();
}
void NCursesFrontend::ClearGroupQueue()
{
for (GroupQueue::iterator it = m_groupQueue.begin(); it != m_groupQueue.end(); it++)
{
delete *it;
}
m_groupQueue.clear();
}
bool NCursesFrontend::EditQueue(QueueEditor::EEditAction eAction, int iOffset)
{
int ID = 0;
if (m_bGroupFiles)
{
if (m_iSelectedQueueEntry >= 0 && m_iSelectedQueueEntry < (int)m_groupQueue.size())
{
GroupInfo* pGroupInfo = m_groupQueue[m_iSelectedQueueEntry];
ID = pGroupInfo->GetID();
if (eAction == QueueEditor::eaFilePause)
{
if (pGroupInfo->GetRemainingSize() == pGroupInfo->GetPausedSize())
{
eAction = QueueEditor::eaFileResume;
}
else if (pGroupInfo->GetPausedSize() == 0 && (pGroupInfo->m_iParCount > 0) &&
!(m_bLastPausePars && m_iLastEditEntry == m_iSelectedQueueEntry))
{
eAction = QueueEditor::eaFilePauseExtraPars;
m_bLastPausePars = true;
}
else
{
eAction = QueueEditor::eaFilePause;
m_bLastPausePars = false;
}
}
}
// map file-edit-actions to group-edit-actions
QueueEditor::EEditAction FileToGroupMap[] = {
(QueueEditor::EEditAction)0,
QueueEditor::eaGroupMoveOffset,
QueueEditor::eaGroupMoveTop,
QueueEditor::eaGroupMoveBottom,
QueueEditor::eaGroupPause,
QueueEditor::eaGroupResume,
QueueEditor::eaGroupDelete,
QueueEditor::eaGroupPauseAllPars,
QueueEditor::eaGroupPauseExtraPars };
eAction = FileToGroupMap[eAction];
}
else
{
DownloadQueue* pDownloadQueue = LockQueue();
if (m_iSelectedQueueEntry >= 0 && m_iSelectedQueueEntry < (int)pDownloadQueue->size())
{
FileInfo* pFileInfo = (*pDownloadQueue)[m_iSelectedQueueEntry];
ID = pFileInfo->GetID();
if (eAction == QueueEditor::eaFilePause)
{
eAction = !pFileInfo->GetPaused() ? QueueEditor::eaFilePause : QueueEditor::eaFileResume;
}
}
UnlockQueue();
}
m_iLastEditEntry = m_iSelectedQueueEntry;
if (ID != 0)
{
return ServerEditQueue(eAction, iOffset, ID);
}
else
{
return false;
}
}
void NCursesFrontend::SetCurrentQueueEntry(int iEntry)
{
DownloadQueue* pDownloadQueue = LockQueue();
int iQueueSize = pDownloadQueue->size();
UnlockQueue();
int iQueueSize = CalcQueueSize();
if (iEntry < 0)
{
@@ -735,25 +1129,23 @@ void NCursesFrontend::UpdateInput()
int iKey;
while ((iKey = ReadConsoleKey()) != READKEY_EMPTY)
{
DownloadQueue* pDownloadQueue = LockQueue();
int iQueueSize = pDownloadQueue->size();
UnlockQueue();
int iQueueSize = CalcQueueSize();
// Normal or edit queue mode
if (m_eInputMode == eNormal || m_eInputMode == eEditQueue)
{
switch (iKey)
{
// Key 'q' for quit
case 'q':
// Key 'q' for quit
ExitProc();
break;
// show/hide NZBFilename
case 'z':
// show/hide NZBFilename
m_bShowNZBname = !m_bShowNZBname;
break;
// swicth window sizes
case 'w':
// swicth window sizes
if (m_QueueWindowPercentage == 0.5)
{
m_QueueWindowPercentage = 1;
@@ -769,6 +1161,12 @@ void NCursesFrontend::UpdateInput()
CalcWindowSizes();
SetCurrentQueueEntry(m_iSelectedQueueEntry);
break;
case 'g':
// group/ungroup files
m_bGroupFiles = !m_bGroupFiles;
SetCurrentQueueEntry(m_iSelectedQueueEntry);
NeedUpdateData();
break;
}
}
@@ -777,8 +1175,8 @@ void NCursesFrontend::UpdateInput()
{
switch (iKey)
{
// Key 'p' for pause
case 'p':
// Key 'p' for pause
if (!IsRemoteMode())
{
info(m_bPause ? "Unpausing download" : "Pausing download");
@@ -798,70 +1196,32 @@ void NCursesFrontend::UpdateInput()
{
m_QueueWindowPercentage = 0.5;
}
return;
}
break;
// Download rate
case 'r':
// Download rate
m_eInputMode = eDownloadRate;
m_iInputNumberIndex = 0;
m_iInputValue = 0;
return;
case 't':
// show/hide Timestamps
m_bShowTimestamp = !m_bShowTimestamp;
break;
}
}
// Edit Queue mode
else if (m_eInputMode == eEditQueue)
if (m_eInputMode == eEditQueue)
{
switch (iKey)
{
// Key 'p' for pause
case 'p':
ServerEditQueue(eaPauseUnpause, m_iSelectedQueueEntry);
break;
// Delete entry
case 'd':
if (ServerEditQueue(eaDelete, m_iSelectedQueueEntry))
{
if (iQueueSize == 0)
{
m_iSelectedQueueEntry = 0;
m_eInputMode = eNormal;
}
else
{
SetCurrentQueueEntry(m_iSelectedQueueEntry);
}
}
break;
case 'u':
if (ServerEditQueue(eaMoveUp, m_iSelectedQueueEntry))
{
SetCurrentQueueEntry(m_iSelectedQueueEntry - 1);
}
break;
case 'n':
if (ServerEditQueue(eaMoveDown, m_iSelectedQueueEntry))
{
SetCurrentQueueEntry(m_iSelectedQueueEntry + 1);
}
break;
case 't':
if (ServerEditQueue(eaMoveTop, m_iSelectedQueueEntry))
{
SetCurrentQueueEntry(0);
}
break;
case 'b':
if (ServerEditQueue(eaMoveBottom, m_iSelectedQueueEntry))
{
SetCurrentQueueEntry(iQueueSize > 0 ? iQueueSize - 1 : 0);
}
break;
case 'e':
case 10: // return
case 13: // enter
m_eInputMode = eNormal;
break;
return;
case KEY_DOWN:
if (m_iSelectedQueueEntry < iQueueSize - 1)
{
@@ -908,11 +1268,46 @@ void NCursesFrontend::UpdateInput()
case KEY_END:
SetCurrentQueueEntry(iQueueSize > 0 ? iQueueSize - 1 : 0);
break;
case 'p':
// Key 'p' for pause
EditQueue(QueueEditor::eaFilePause, 0);
break;
case 'd':
// Delete entry
if (EditQueue(QueueEditor::eaFileDelete, 0))
{
SetCurrentQueueEntry(m_iSelectedQueueEntry);
}
break;
case 'u':
if (EditQueue(QueueEditor::eaFileMoveOffset, -1))
{
SetCurrentQueueEntry(m_iSelectedQueueEntry - 1);
}
break;
case 'n':
if (EditQueue(QueueEditor::eaFileMoveOffset, +1))
{
SetCurrentQueueEntry(m_iSelectedQueueEntry + 1);
}
break;
case 't':
if (EditQueue(QueueEditor::eaFileMoveTop, 0))
{
SetCurrentQueueEntry(0);
}
break;
case 'b':
if (EditQueue(QueueEditor::eaFileMoveBottom, 0))
{
SetCurrentQueueEntry(iQueueSize > 0 ? iQueueSize - 1 : 0);
}
break;
}
}
// Edit download rate input mode
else if (m_eInputMode == eDownloadRate)
if (m_eInputMode == eDownloadRate)
{
// Numbers
if (m_iInputNumberIndex < 5 && iKey >= '0' && iKey <= '9')

View File

@@ -46,22 +46,50 @@ private:
eDownloadRate
};
bool m_bUseColor;
int m_iSkipUpdateData;
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_iQueueScrollOffset;
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;
int m_iInputNumberIndex;
int m_iInputValue;
#ifdef WIN32
CHAR_INFO* m_pScreenBuffer;
@@ -69,12 +97,14 @@ private:
int m_iScreenBufferSize;
std::vector<WORD> m_ColorAttr;
#else
void* m_pWindow; // WINDOW*
void* m_pWindow; // WINDOW*
#endif
EInputMode m_eInputMode;
bool m_bShowNZBname;
float m_QueueWindowPercentage;
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);
@@ -83,7 +113,13 @@ private:
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();
@@ -94,6 +130,9 @@ private:
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();

View File

@@ -41,30 +41,23 @@
#include "Connection.h"
#include "NewsServer.h"
static const int CONNECTION_LINEBUFFER_SIZE = 1024*10;
NNTPConnection::NNTPConnection(NewsServer* server) : Connection(server)
{
m_UnavailableGroups.clear();
m_szActiveGroup = NULL;
m_szLineBuf = (char*)malloc(LineBufSize);
m_szLineBuf = (char*)malloc(CONNECTION_LINEBUFFER_SIZE);
m_bAuthError = false;
}
NNTPConnection::~NNTPConnection()
{
for (unsigned int i = 0; i < m_UnavailableGroups.size(); i++)
{
free(m_UnavailableGroups[i]);
m_UnavailableGroups[i] = NULL;
}
m_UnavailableGroups.clear();
if (m_szActiveGroup)
{
free(m_szActiveGroup);
m_szActiveGroup = NULL;
}
if (m_szLineBuf)
{
free(m_szLineBuf);
}
free(m_szLineBuf);
}
char* NNTPConnection::Request(char* req)
@@ -74,9 +67,11 @@ char* NNTPConnection::Request(char* req)
return NULL;
}
m_bAuthError = false;
WriteLine(req);
char* answer = ReadLine(m_szLineBuf, LineBufSize);
char* answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL);
if (!answer)
{
@@ -88,56 +83,56 @@ char* NNTPConnection::Request(char* req)
debug("%s requested authorization", m_pNetAddress->GetHost());
//authentication required!
if (Authenticate() < 0)
if (!Authenticate())
{
m_bAuthError = true;
return NULL;
}
//try again
WriteLine(req);
answer = ReadLine(m_szLineBuf, LineBufSize);
answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL);
return answer;
}
return answer;
}
int NNTPConnection::Authenticate()
bool NNTPConnection::Authenticate()
{
if ((!((NewsServer*)m_pNetAddress)->GetUser()) ||
(!((NewsServer*)m_pNetAddress)->GetPassword()))
if (!((NewsServer*)m_pNetAddress)->GetUser() ||
!((NewsServer*)m_pNetAddress)->GetPassword())
{
return -1;
return false;
}
return AuthInfoUser();
}
int NNTPConnection::AuthInfoUser(int iRecur)
bool NNTPConnection::AuthInfoUser(int iRecur)
{
if (iRecur > 10)
{
return -1;
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, LineBufSize);
char* answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL);
if (!answer)
{
return -1;
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 0;
return true;
}
else if (!strncmp(answer, "381", 3))
{
@@ -145,44 +140,101 @@ int NNTPConnection::AuthInfoUser(int iRecur)
}
else if (!strncmp(answer, "480", 3))
{
return AuthInfoUser();
return AuthInfoUser(++iRecur);
}
return -1;
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;
}
int NNTPConnection::AuthInfoPass(int iRecur)
bool NNTPConnection::AuthInfoPass(int iRecur)
{
if (iRecur > 10)
{
return -1;
return false;
}
char tmp[1024];
snprintf(tmp, 1024, "AUTHINFO PASS %s\r\n", ((NewsServer*)m_pNetAddress)->GetPassword());
tmp[1024-1] = '\0';
WriteLine(tmp);
char* szAnswer = ReadLine(m_szLineBuf, LineBufSize);
if (!szAnswer)
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 -1;
return false;
}
else if (!strncmp(szAnswer, "2", 1))
else if (!strncmp(answer, "2", 1))
{
debug("authorization for %s successful", m_pNetAddress->GetHost());
return 0;
return true;
}
else if (!strncmp(szAnswer, "381", 3))
else if (!strncmp(answer, "381", 3))
{
return AuthInfoPass(++iRecur);
}
error("authorization for %s failed (Answer: %s)", m_pNetAddress->GetHost(), szAnswer);
return -1;
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()
@@ -190,8 +242,11 @@ int NNTPConnection::DoConnect()
debug("Opening connection to %s", GetServer()->GetHost());
int res = Connection::DoConnect();
if (res < 0)
{
return res;
char* answer = DoReadLine(m_szLineBuf, LineBufSize);
}
char* answer = DoReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL);
if (!answer)
{
@@ -215,60 +270,11 @@ int NNTPConnection::DoDisconnect()
if (m_eStatus == csConnected)
{
Request("quit\r\n");
if (m_szActiveGroup)
{
free(m_szActiveGroup);
m_szActiveGroup = NULL;
}
}
return Connection::DoDisconnect();
}
int NNTPConnection::JoinGroup(char* grp)
{
if (!grp)
{
debug("joinGroup called with NULL-pointer!!");
return -1;
}
if ((m_szActiveGroup) && (!strcmp(m_szActiveGroup, grp)))
return 0;
for (unsigned int i = 0; i < m_UnavailableGroups.size(); i++)
{
if (!strcmp(grp, m_UnavailableGroups[i]))
{
debug("Group %s unavailable on %s.", grp, this->GetServer()->GetHost());
return -1;
}
}
char tmp[1024];
snprintf(tmp, 1024, "GROUP %s\r\n", grp);
tmp[1024-1] = '\0';
char* answer = Request(tmp);
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 0;
}
if (!answer)
{
warn("Error changing group on %s: Connection closed by remote host.",
GetServer()->GetHost());
return -1;
}
else
{
warn("Error changing group on %s to %s: Answer was \"%s\".",
GetServer()->GetHost(), grp, answer);
m_UnavailableGroups.push_back(strdup(grp));
}
return -1;
}

View File

@@ -27,32 +27,30 @@
#ifndef NNTPCONNECTION_H
#define NNTPCONNECTION_H
#include <stdio.h>
#include <vector>
#include "NewsServer.h"
#include "Connection.h"
class NNTPConnection : public Connection
{
private:
std::vector <char*> m_UnavailableGroups;
char* m_szActiveGroup;
static const int LineBufSize = 1024*10;
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);
int Authenticate();
int AuthInfoUser(int iRecur = 0);
int AuthInfoPass(int iRecur = 0);
int JoinGroup(char* grp);
bool Authenticate();
bool AuthInfoUser(int iRecur = 0);
bool AuthInfoPass(int iRecur = 0);
bool JoinGroup(char* grp);
bool GetAuthError() { return m_bAuthError; }
};

View File

@@ -38,148 +38,148 @@
extern void ExitProc();
RunProc Run = NULL;
char* strServiceName = "NZBGet";
SERVICE_STATUS_HANDLE nServiceStatusHandle;
DWORD nServiceCurrentStatus;
char* strServiceName = "NZBGet";
SERVICE_STATUS_HANDLE nServiceStatusHandle;
DWORD nServiceCurrentStatus;
BOOL UpdateServiceStatus(DWORD dwCurrentState, DWORD dwWaitHint)
{
BOOL success;
SERVICE_STATUS nServiceStatus;
nServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
nServiceStatus.dwCurrentState = dwCurrentState;
if (dwCurrentState == SERVICE_START_PENDING)
{
nServiceStatus.dwControlsAccepted = 0;
}
else
{
nServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
}
nServiceStatus.dwWin32ExitCode = NO_ERROR;
nServiceStatus.dwServiceSpecificExitCode = 0;
nServiceStatus.dwCheckPoint = 0;
nServiceStatus.dwWaitHint = dwWaitHint;
success = SetServiceStatus(nServiceStatusHandle, &nServiceStatus);
return success;
}
BOOL UpdateServiceStatus(DWORD dwCurrentState, DWORD dwWaitHint)
{
BOOL success;
SERVICE_STATUS nServiceStatus;
nServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
nServiceStatus.dwCurrentState = dwCurrentState;
if (dwCurrentState == SERVICE_START_PENDING)
{
nServiceStatus.dwControlsAccepted = 0;
}
else
{
nServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
}
nServiceStatus.dwWin32ExitCode = NO_ERROR;
nServiceStatus.dwServiceSpecificExitCode = 0;
nServiceStatus.dwCheckPoint = 0;
nServiceStatus.dwWaitHint = dwWaitHint;
void ServiceCtrlHandler(DWORD nControlCode)
{
switch(nControlCode)
{
case SERVICE_CONTROL_SHUTDOWN:
case SERVICE_CONTROL_STOP:
nServiceCurrentStatus = SERVICE_STOP_PENDING;
UpdateServiceStatus(SERVICE_STOP_PENDING, 10000);
ExitProc();
return;
default:
break;
}
UpdateServiceStatus(nServiceCurrentStatus, 0);
success = SetServiceStatus(nServiceStatusHandle, &nServiceStatus);
return success;
}
void ServiceMain(DWORD argc, LPTSTR *argv)
{
BOOL success;
nServiceStatusHandle = RegisterServiceCtrlHandler(strServiceName,
(LPHANDLER_FUNCTION)ServiceCtrlHandler);
if(!nServiceStatusHandle)
{
return;
}
success = UpdateServiceStatus(SERVICE_START_PENDING, 10000);
if(!success)
{
return;
}
nServiceCurrentStatus=SERVICE_RUNNING;
success=UpdateServiceStatus(SERVICE_RUNNING, 0);
if(!success)
{
return;
}
Run();
UpdateServiceStatus(SERVICE_STOPPED, 0);
}
void ServiceCtrlHandler(DWORD nControlCode)
{
switch(nControlCode)
{
case SERVICE_CONTROL_SHUTDOWN:
case SERVICE_CONTROL_STOP:
nServiceCurrentStatus = SERVICE_STOP_PENDING;
UpdateServiceStatus(SERVICE_STOP_PENDING, 10000);
ExitProc();
return;
default:
break;
}
UpdateServiceStatus(nServiceCurrentStatus, 0);
}
void ServiceMain(DWORD argc, LPTSTR *argv)
{
BOOL success;
nServiceStatusHandle = RegisterServiceCtrlHandler(strServiceName,
(LPHANDLER_FUNCTION)ServiceCtrlHandler);
if(!nServiceStatusHandle)
{
return;
}
success = UpdateServiceStatus(SERVICE_START_PENDING, 10000);
if(!success)
{
return;
}
nServiceCurrentStatus=SERVICE_RUNNING;
success=UpdateServiceStatus(SERVICE_RUNNING, 0);
if(!success)
{
return;
}
Run();
UpdateServiceStatus(SERVICE_STOPPED, 0);
}
void StartService(RunProc RunProcPtr)
{
Run = RunProcPtr;
SERVICE_TABLE_ENTRY servicetable[]=
{
{strServiceName,(LPSERVICE_MAIN_FUNCTION)ServiceMain},
{NULL,NULL}
};
BOOL success = StartServiceCtrlDispatcher(servicetable);
if(!success)
{
error("Could not start service");
}
SERVICE_TABLE_ENTRY servicetable[]=
{
{strServiceName,(LPSERVICE_MAIN_FUNCTION)ServiceMain},
{NULL,NULL}
};
BOOL success = StartServiceCtrlDispatcher(servicetable);
if(!success)
{
error("Could not start service");
}
}
void InstallService(int argc, char *argv[])
{
SC_HANDLE scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE);
if(!scm)
{
printf("Could not install service\n");
return;
}
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,
szCmdLine,
0,0,0,0,0);
if(!hService)
{
CloseServiceHandle(scm);
printf("Could not install service\n");
return;
}
CloseServiceHandle(hService);
CloseServiceHandle(scm);
printf("Service \"%s\" sucessfully installed\n", strServiceName);
SC_HANDLE scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE);
if(!scm)
{
printf("Could not install service\n");
return;
}
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,
szCmdLine,
0,0,0,0,0);
if(!hService)
{
CloseServiceHandle(scm);
printf("Could not install service\n");
return;
}
CloseServiceHandle(hService);
CloseServiceHandle(scm);
printf("Service \"%s\" sucessfully installed\n", strServiceName);
}
void UnInstallService()
{
BOOL success;
SC_HANDLE scm = OpenSCManager(0,0,SC_MANAGER_CONNECT);
if(!scm)
{
printf("Could not uninstall service\n");
return;
}
SC_HANDLE hService = OpenService(scm, strServiceName, STANDARD_RIGHTS_REQUIRED);
if(!hService)
{
CloseServiceHandle(scm);
printf("Could not uninstall service\n");
return;
}
success = DeleteService(hService);
if(!success)
{
error("Could not uninstall service");
}
CloseServiceHandle(hService);
CloseServiceHandle(scm);
printf("Service \"%s\" sucessfully uninstalled\n", strServiceName);
BOOL success;
SC_HANDLE scm = OpenSCManager(0,0,SC_MANAGER_CONNECT);
if(!scm)
{
printf("Could not uninstall service\n");
return;
}
SC_HANDLE hService = OpenService(scm, strServiceName, STANDARD_RIGHTS_REQUIRED);
if(!hService)
{
CloseServiceHandle(scm);
printf("Could not uninstall service\n");
return;
}
success = DeleteService(hService);
if(!success)
{
error("Could not uninstall service");
}
CloseServiceHandle(hService);
CloseServiceHandle(scm);
printf("Service \"%s\" sucessfully uninstalled\n", strServiceName);
}
void InstallUninstallServiceCheck(int argc, char *argv[])

View File

@@ -32,9 +32,8 @@
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <list>
#ifdef WIN32
#include <comutil.h>
#import "MSXML.dll" named_guids
@@ -48,11 +47,12 @@ using namespace MSXML;
#include "NZBFile.h"
#include "Log.h"
#include "DownloadInfo.h"
#include "Options.h"
#include "DiskState.h"
#include "Util.h"
bool ArticleGreater(ArticleInfo* elem1, ArticleInfo* elem2)
{
return elem1->GetPartNumber() > elem2->GetPartNumber();
}
extern Options* g_pOptions;
extern DiskState* g_pDiskState;
NZBFile::NZBFile(const char* szFileName)
{
@@ -73,7 +73,7 @@ NZBFile::~NZBFile()
free(m_szFileName);
}
for (std::vector<FileInfo*>::iterator it = m_FileInfos.begin(); it != m_FileInfos.end(); it++)
for (FileInfos::iterator it = m_FileInfos.begin(); it != m_FileInfos.end(); it++)
{
delete *it;
}
@@ -90,38 +90,6 @@ void NZBFile::DetachFileInfos()
m_FileInfos.clear();
}
bool NZBFile::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;
}
NZBFile* NZBFile::CreateFromBuffer(const char* szFileName, const char* szBuffer, int iSize)
{
return Create(szFileName, szBuffer, iSize, true);
@@ -129,19 +97,7 @@ NZBFile* NZBFile::CreateFromBuffer(const char* szFileName, const char* szBuffer,
NZBFile* NZBFile::CreateFromFile(const char* szFileName)
{
//return Create(szFileName, NULL, 0, false);
// /*
//TEST
int iBufferLength = 0;
char* szBuffer = NULL;
if (!NZBFile::LoadFileIntoBuffer(szFileName, &szBuffer, &iBufferLength))
{
return false;
}
return Create(szFileName, szBuffer, iBufferLength, true);
// */
return Create(szFileName, NULL, 0, false);
}
void NZBFile::AddArticle(FileInfo* pFileInfo, ArticleInfo* pArticleInfo)
@@ -153,8 +109,9 @@ void NZBFile::AddArticle(FileInfo* pFileInfo, ArticleInfo* pArticleInfo)
(*pFileInfo->GetArticles())[pArticleInfo->GetPartNumber() - 1] = pArticleInfo;
}
void NZBFile::DeleteEmptyArticles(FileInfo* pFileInfo)
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();)
@@ -170,6 +127,175 @@ void NZBFile::DeleteEmptyArticles(FileInfo* pFileInfo)
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
@@ -197,7 +323,12 @@ NZBFile* NZBFile::Create(const char* szFileName, const char* szBuffer, int iSize
}
else
{
_variant_t v(szFileName);
// 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)
@@ -209,7 +340,11 @@ NZBFile* NZBFile::Create(const char* szFileName, const char* szBuffer, int iSize
}
NZBFile* pFile = new NZBFile(szFileName);
if (!pFile->parseNZB(doc))
if (pFile->ParseNZB(doc))
{
pFile->CheckFilenames();
}
else
{
delete pFile;
pFile = NULL;
@@ -218,7 +353,29 @@ NZBFile* NZBFile::Create(const char* szFileName, const char* szBuffer, int iSize
return pFile;
}
bool NZBFile::parseNZB(IUnknown* nzb)
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;
@@ -233,7 +390,6 @@ bool NZBFile::parseNZB(IUnknown* nzb)
FileInfo* pFileInfo = new FileInfo();
pFileInfo->SetNZBFilename(m_szFileName);
pFileInfo->SetSubject(subject);
pFileInfo->ParseSubject();
MSXML::IXMLDOMNodeListPtr groupList = node->selectNodes("groups/group");
for (int g = 0; g < groupList->Getlength(); g++)
@@ -274,8 +430,7 @@ bool NZBFile::parseNZB(IUnknown* nzb)
}
}
DeleteEmptyArticles(pFileInfo);
m_FileInfos.push_back(pFileInfo);
AddFileInfo(pFileInfo);
}
return true;
}
@@ -287,7 +442,7 @@ NZBFile* NZBFile::Create(const char* szFileName, const char* szBuffer, int iSize
xmlTextReaderPtr doc;
if (bFromBuffer)
{
doc = xmlReaderForMemory(szBuffer,iSize-1, "", NULL, 0);
doc = xmlReaderForMemory(szBuffer, iSize-1, "", NULL, 0);
}
else
{
@@ -299,7 +454,11 @@ NZBFile* NZBFile::Create(const char* szFileName, const char* szBuffer, int iSize
}
NZBFile* pFile = new NZBFile(szFileName);
if (!pFile->parseNZB(doc))
if (pFile->ParseNZB(doc))
{
pFile->CheckFilenames();
}
else
{
delete pFile;
pFile = NULL;
@@ -310,7 +469,7 @@ NZBFile* NZBFile::Create(const char* szFileName, const char* szBuffer, int iSize
return pFile;
}
bool NZBFile::parseNZB(void* nzb)
bool NZBFile::ParseNZB(void* nzb)
{
FileInfo* pFileInfo = NULL;
xmlTextReaderPtr node = (xmlTextReaderPtr)nzb;
@@ -345,7 +504,6 @@ bool NZBFile::parseNZB(void* nzb)
xmlFree(value);
value = xmlTextReaderValue(node);
pFileInfo->SetSubject((char*)value);
pFileInfo->ParseSubject();
}
}
}
@@ -405,8 +563,7 @@ bool NZBFile::parseNZB(void* nzb)
/* Close the file element, add the new file to file-list */
if (!strcmp("file",(char*)name))
{
DeleteEmptyArticles(pFileInfo);
m_FileInfos.push_back(pFileInfo);
AddFileInfo(pFileInfo);
}
}

View File

@@ -42,11 +42,15 @@ private:
NZBFile(const char* szFileName);
void AddArticle(FileInfo* pFileInfo, ArticleInfo* pArticleInfo);
void DeleteEmptyArticles(FileInfo* pFileInfo);
void AddFileInfo(FileInfo* pFileInfo);
void ParseSubject(FileInfo* pFileInfo);
void BuildDestDirName(FileInfo* pFileInfo);
void CheckFilenames();
#ifdef WIN32
bool parseNZB(IUnknown* nzb);
bool ParseNZB(IUnknown* nzb);
static void EncodeURL(const char* szFilename, char* szURL);
#else
bool parseNZB(void* nzb);
bool ParseNZB(void* nzb);
#endif
static NZBFile* Create(const char* szFileName, const char* szBuffer, int iSize, bool bFromBuffer);
@@ -54,7 +58,6 @@ public:
virtual ~NZBFile();
static NZBFile* CreateFromBuffer(const char* szFileName, const char* szBuffer, int iSize);
static NZBFile* CreateFromFile(const char* szFileName);
static bool LoadFileIntoBuffer(const char* szFileName, char** pBuffer, int* pBufferLength);
const char* GetFileName() const { return m_szFileName; }
FileInfos* GetFileInfos() { return &m_FileInfos; }
void DetachFileInfos();

View File

@@ -35,6 +35,7 @@
#include <stdlib.h>
#include <string.h>
#include "nzbget.h"
#include "NetAddress.h"
NetAddress::NetAddress(const char* szHost, int iPort)

View File

@@ -35,6 +35,7 @@
#include <stdlib.h>
#include <string.h>
#include "nzbget.h"
#include "NewsServer.h"
#include "Log.h"

View File

@@ -64,6 +64,7 @@ static struct option long_options[] =
{"server", no_argument, 0, 's' },
{"daemon", no_argument, 0, 'D' },
{"version", no_argument, 0, 'v'},
{"serverversion", no_argument, 0, 'V'},
{"option", required_argument, 0, 'o'},
{"append", no_argument, 0, 'A'},
{"list", no_argument, 0, 'L'},
@@ -74,54 +75,63 @@ static struct option long_options[] =
{"log", required_argument, 0, 'G'},
{"top", no_argument, 0, 'T'},
{"edit", required_argument, 0, 'E'},
{"fileid", required_argument, 0, 'I'},
{"connect", no_argument, 0, 'C'},
{"quit", no_argument, 0, 'Q'},
#ifdef DEBUG
{"test", no_argument, 0, 't'},
#endif
{0, 0, 0, 0}
};
#endif
static char short_options[] = "c:hno:psvABDCG:LPUR:TE:I:Q";
static char short_options[] = "c:hno:psvABDCG:LPUR:TE:QV";
// Program options
static const char* OPTION_DESTDIR = "destdir";
static const char* OPTION_TEMPDIR = "tempdir";
static const char* OPTION_QUEUEDIR = "queuedir";
static const char* OPTION_NZBDIR = "nzbdir";
static const char* OPTION_CREATELOG = "createlog";
static const char* OPTION_LOGFILE = "logfile";
static const char* OPTION_APPENDNZBDIR = "appendnzbdir";
static const char* OPTION_LOCKFILE = "lockfile";
static const char* OPTION_OUTPUTMODE = "outputmode";
static const char* OPTION_DUPECHECK = "dupecheck";
static const char* OPTION_DOWNLOADRATE = "downloadrate";
static const char* OPTION_RENAMEBROKEN = "renamebroken";
static const char* OPTION_SERVERIP = "serverip";
static const char* OPTION_SERVERPORT = "serverport";
static const char* OPTION_SERVERPASSWORD = "serverpassword";
static const char* OPTION_CONNECTIONTIMEOUT = "connectiontimeout";
static const char* OPTION_SAVEQUEUE = "savequeue";
static const char* OPTION_RELOADQUEUE = "reloadqueue";
static const char* OPTION_CREATEBROKENLOG = "createbrokenlog";
static const char* OPTION_RESETLOG = "resetlog";
static const char* OPTION_DECODER = "decoder";
static const char* OPTION_RETRIES = "retries";
static const char* OPTION_RETRYINTERVAL = "retryinterval";
static const char* OPTION_TERMINATETIMEOUT = "terminatetimeout";
static const char* OPTION_CONTINUEPARTIAL = "continuepartial";
static const char* OPTION_LOGBUFFERSIZE = "logbuffersize";
static const char* OPTION_INFOTARGET = "infotarget";
static const char* OPTION_WARNINGTARGET = "warningtarget";
static const char* OPTION_ERRORTARGET = "errortarget";
static const char* OPTION_DEBUGTARGET = "debugtarget";
static const char* OPTION_LOADPARS = "loadpars";
static const char* OPTION_PARCHECK = "parcheck";
static const char* OPTION_PARREPAIR = "parrepair";
static const char* OPTION_POSTPROCESS = "postprocess";
static const char* OPTION_STRICTPARNAME = "strictparname";
static const char* OPTION_DESTDIR = "DestDir";
static const char* OPTION_TEMPDIR = "TempDir";
static const char* OPTION_QUEUEDIR = "QueueDir";
static const char* OPTION_NZBDIR = "NzbDir";
static const char* OPTION_CREATELOG = "CreateLog";
static const char* OPTION_LOGFILE = "LogFile";
static const char* OPTION_APPENDNZBDIR = "AppendNzbDir";
static const char* OPTION_LOCKFILE = "LockFile";
static const char* OPTION_DAEMONUSERNAME = "DaemonUserName";
static const char* OPTION_OUTPUTMODE = "OutputMode";
static const char* OPTION_DUPECHECK = "DupeCheck";
static const char* OPTION_DOWNLOADRATE = "DownloadRate";
static const char* OPTION_RENAMEBROKEN = "RenameBroken";
static const char* OPTION_SERVERIP = "ServerIp";
static const char* OPTION_SERVERPORT = "ServerPort";
static const char* OPTION_SERVERPASSWORD = "ServerPassword";
static const char* OPTION_CONNECTIONTIMEOUT = "ConnectionTimeout";
static const char* OPTION_SAVEQUEUE = "SaveQueue";
static const char* OPTION_RELOADQUEUE = "ReloadQueue";
static const char* OPTION_CREATEBROKENLOG = "CreateBrokenLog";
static const char* OPTION_RESETLOG = "ResetLog";
static const char* OPTION_DECODER = "Decoder";
static const char* OPTION_RETRIES = "Retries";
static const char* OPTION_RETRYINTERVAL = "RetryInterval";
static const char* OPTION_TERMINATETIMEOUT = "TerminateTimeout";
static const char* OPTION_CONTINUEPARTIAL = "ContinuePartial";
static const char* OPTION_LOGBUFFERSIZE = "LogBufferSize";
static const char* OPTION_INFOTARGET = "InfoTarget";
static const char* OPTION_WARNINGTARGET = "WarningTarget";
static const char* OPTION_ERRORTARGET = "ErrorTarget";
static const char* OPTION_DEBUGTARGET = "DebugTarget";
static const char* OPTION_LOADPARS = "LoadPars";
static const char* OPTION_PARCHECK = "ParCheck";
static const char* OPTION_PARREPAIR = "ParRepair";
static const char* OPTION_POSTPROCESS = "PostProcess";
static const char* OPTION_STRICTPARNAME = "StrictParName";
static const char* OPTION_UMASK = "UMask";
static const char* OPTION_UPDATEINTERVAL = "UpdateInterval";
static const char* OPTION_CURSESNZBNAME = "CursesNzbName";
static const char* OPTION_CURSESTIME = "CursesTime";
static const char* OPTION_CURSESGROUP = "CursesGroup";
static const char* OPTION_CRCCHECK = "CrcCheck";
static const char* OPTION_RETRYONCRCERROR = "RetryOnCrcError";
static const char* OPTION_THREADLIMIT = "ThreadLimit";
static const char* OPTION_DIRECTWRITE = "DirectWrite";
static const char* OPTION_WRITEBUFFERSIZE = "WriteBufferSize";
static const char* OPTION_NZBDIRINTERVAL = "NzbDirInterval";
static const char* OPTION_NZBDIRFILEAGE = "NzbDirFileAge";
#ifndef WIN32
const char* PossibleConfigLocations[] =
@@ -139,6 +149,7 @@ Options::Options(int argc, char* argv[])
{
// initialize options with default values
m_bConfigInitialized = false;
m_szConfigFilename = NULL;
m_szDestDir = NULL;
m_szTempDir = NULL;
@@ -154,8 +165,8 @@ Options::Options(int argc, char* argv[])
m_bResetLog = false;
m_fDownloadRate = 0;
m_iEditQueueAction = 0;
m_iEditQueueIDFrom = 0;
m_iEditQueueIDTo = 0;
m_pEditQueueIDList = NULL;
m_iEditQueueIDCount = 0;
m_iEditQueueOffset = 0;
m_szArgFilename = NULL;
m_iConnectionTimeout = 0;
@@ -176,6 +187,7 @@ Options::Options(int argc, char* argv[])
m_szServerIP = NULL;
m_szServerPassword = NULL;
m_szLockFile = NULL;
m_szDaemonUserName = NULL;
m_eOutputMode = omLoggable;
m_bReloadQueue = false;
m_iLogBufferSize = 0;
@@ -185,13 +197,28 @@ Options::Options(int argc, char* argv[])
m_eLoadPars = plAll;
m_bParCheck = false;
m_bParRepair = false;
m_bTest = false;
m_szPostProcess = NULL;
m_bStrictParName = false;
m_bNoConfig = false;
m_iUMask = 0;
m_iUpdateInterval = 0;
m_bCursesNZBName = false;
m_bCursesTime = false;
m_bCursesGroup = false;
m_bCrcCheck = false;
m_bRetryOnCrcError = false;
m_bDirectWrite = false;
m_iThreadLimit = 0;
m_iWriteBufferSize = 0;
m_iNzbDirInterval = 0;
m_iNzbDirFileAge = 0;
char szFilename[MAX_PATH + 1];
#ifdef WIN32
GetModuleFileName(NULL, szFilename, MAX_PATH + 1);
#else
strncpy(szFilename, argv[0], MAX_PATH + 1);
#endif
szFilename[MAX_PATH] = '\0';
NormalizePathSeparators(szFilename);
char* end = strrchr(szFilename, PATH_SEPARATOR);
@@ -199,7 +226,6 @@ Options::Options(int argc, char* argv[])
SetOption("APPDIR", szFilename);
InitDefault();
InitOptFile(argc, argv);
InitCommandLine(argc, argv);
if (m_bPrintOptions)
@@ -285,10 +311,18 @@ Options::~Options()
{
free(m_szLockFile);
}
if (m_szDaemonUserName)
{
free(m_szDaemonUserName);
}
if (m_szPostProcess)
{
free(m_szPostProcess);
}
if (m_pEditQueueIDList)
{
free(m_pEditQueueIDList);
}
for (unsigned int i = 0; i < optEntries.size(); i++)
{
@@ -356,44 +390,34 @@ void Options::InitDefault()
SetOption(OPTION_PARREPAIR, "no");
SetOption(OPTION_POSTPROCESS, "");
SetOption(OPTION_STRICTPARNAME, "yes");
SetOption(OPTION_DAEMONUSERNAME, "root");
SetOption(OPTION_UMASK, "1000");
SetOption(OPTION_UPDATEINTERVAL, "200");
SetOption(OPTION_CURSESNZBNAME, "yes");
SetOption(OPTION_CURSESTIME, "no");
SetOption(OPTION_CURSESGROUP, "no");
SetOption(OPTION_CRCCHECK, "yes");
SetOption(OPTION_RETRYONCRCERROR, "no");
SetOption(OPTION_THREADLIMIT, "100");
SetOption(OPTION_DIRECTWRITE, "no");
SetOption(OPTION_WRITEBUFFERSIZE, "0");
SetOption(OPTION_NZBDIRINTERVAL, "5");
SetOption(OPTION_NZBDIRFILEAGE, "60");
}
void Options::InitOptFile(int argc, char* argv[])
void Options::InitOptFile()
{
while (true)
if (m_bConfigInitialized)
{
int c;
#ifdef HAVE_GETOPT_LONG
int option_index = 0;
c = getopt_long(argc, argv, short_options, long_options, &option_index);
#else
c = getopt(argc, argv, short_options);
#endif
if (c == -1) break;
switch (c)
{
case 'c':
m_szConfigFilename = strdup(optarg);
break;
case 'n':
m_szConfigFilename = NULL;
m_bNoConfig = true;
return;
case '?':
exit(-1);
break;
}
return;
}
if (!m_szConfigFilename)
if (!m_szConfigFilename && !m_bNoConfig)
{
// search for config file in default locations
#ifdef WIN32
char szFilename[MAX_PATH + 1];
strncpy(szFilename, argv[0], MAX_PATH + 1);
GetModuleFileName(NULL, szFilename, MAX_PATH + 1);
szFilename[MAX_PATH] = '\0';
NormalizePathSeparators(szFilename);
char* end = strrchr(szFilename, PATH_SEPARATOR);
@@ -429,6 +453,8 @@ void Options::InitOptFile(int argc, char* argv[])
{
LoadConfig(m_szConfigFilename);
}
m_bConfigInitialized = true;
}
void Options::CheckDir(char** dir, const char* szOptionName)
@@ -457,7 +483,10 @@ void Options::CheckDir(char** dir, const char* szOptionName)
abort("FATAL ERROR: Wrong value for option \"%s\"\n", szOptionName);
}
// Ensure the dir is created
mkdir(usedir, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
if (!ForceDirectories(usedir))
{
abort("FATAL ERROR: Directory \"%s\" (option \"%s\") does not exist and could not be created\n", usedir, szOptionName);
}
*dir = usedir;
}
@@ -466,7 +495,7 @@ void Options::InitOptions()
CheckDir(&m_szDestDir, OPTION_DESTDIR);
CheckDir(&m_szTempDir, OPTION_TEMPDIR);
CheckDir(&m_szQueueDir, OPTION_QUEUEDIR);
m_szNzbDir = strdup(GetOption(OPTION_NZBDIR));
m_szPostProcess = strdup(GetOption(OPTION_POSTPROCESS));
m_fDownloadRate = (float)atof(GetOption(OPTION_DOWNLOADRATE));
@@ -478,47 +507,65 @@ void Options::InitOptions()
m_szServerIP = strdup(GetOption(OPTION_SERVERIP));
m_szServerPassword = strdup(GetOption(OPTION_SERVERPASSWORD));
m_szLockFile = strdup(GetOption(OPTION_LOCKFILE));
m_szDaemonUserName = strdup(GetOption(OPTION_DAEMONUSERNAME));
m_iLogBufferSize = atoi(GetOption(OPTION_LOGBUFFERSIZE));
m_szLogFile = strdup(GetOption(OPTION_LOGFILE));
m_iUMask = strtol(GetOption(OPTION_UMASK), NULL, 8);
m_iUpdateInterval = atoi(GetOption(OPTION_UPDATEINTERVAL));
m_iThreadLimit = atoi(GetOption(OPTION_THREADLIMIT));
m_iWriteBufferSize = atoi(GetOption(OPTION_WRITEBUFFERSIZE));
m_iNzbDirInterval = atoi(GetOption(OPTION_NZBDIRINTERVAL));
m_iNzbDirFileAge = atoi(GetOption(OPTION_NZBDIRFILEAGE));
if (m_iNzbDirInterval > 0)
{
CheckDir(&m_szNzbDir, OPTION_NZBDIR);
}
const char* BoolNames[] = { "yes", "no", "true", "false", "1", "0", "on", "off", "enable", "disable" };
const int BoolValues[] = { 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 };
const int BoolCount = 10;
m_bCreateBrokenLog = (bool)ParseOptionValue(OPTION_CREATEBROKENLOG, BoolCount, BoolNames, (const int*)BoolValues);
m_bResetLog = (bool)ParseOptionValue(OPTION_RESETLOG, BoolCount, BoolNames, (const int*)BoolValues);
m_bAppendNZBDir = (bool)ParseOptionValue(OPTION_APPENDNZBDIR, BoolCount, BoolNames, (const int*)BoolValues);
m_bContinuePartial = (bool)ParseOptionValue(OPTION_CONTINUEPARTIAL, BoolCount, BoolNames, (const int*)BoolValues);
m_bRenameBroken = (bool)ParseOptionValue(OPTION_RENAMEBROKEN, BoolCount, BoolNames, (const int*)BoolValues);
m_bSaveQueue = (bool)ParseOptionValue(OPTION_SAVEQUEUE, BoolCount, BoolNames, (const int*)BoolValues);
m_bDupeCheck = (bool)ParseOptionValue(OPTION_DUPECHECK, BoolCount, BoolNames, (const int*)BoolValues);
m_bCreateLog = (bool)ParseOptionValue(OPTION_CREATELOG, BoolCount, BoolNames, (const int*)BoolValues);
m_bParCheck = (bool)ParseOptionValue(OPTION_PARCHECK, BoolCount, BoolNames, (const int*)BoolValues);
m_bParRepair = (bool)ParseOptionValue(OPTION_PARREPAIR, BoolCount, BoolNames, (const int*)BoolValues);
m_bStrictParName = (bool)ParseOptionValue(OPTION_STRICTPARNAME, BoolCount, BoolNames, (const int*)BoolValues);
m_bReloadQueue = (bool)ParseOptionValue(OPTION_RELOADQUEUE, BoolCount, BoolNames, (const int*)BoolValues);
m_bCreateBrokenLog = (bool)ParseOptionValue(OPTION_CREATEBROKENLOG, BoolCount, BoolNames, BoolValues);
m_bResetLog = (bool)ParseOptionValue(OPTION_RESETLOG, BoolCount, BoolNames, BoolValues);
m_bAppendNZBDir = (bool)ParseOptionValue(OPTION_APPENDNZBDIR, BoolCount, BoolNames, BoolValues);
m_bContinuePartial = (bool)ParseOptionValue(OPTION_CONTINUEPARTIAL, BoolCount, BoolNames, BoolValues);
m_bRenameBroken = (bool)ParseOptionValue(OPTION_RENAMEBROKEN, BoolCount, BoolNames, BoolValues);
m_bSaveQueue = (bool)ParseOptionValue(OPTION_SAVEQUEUE, BoolCount, BoolNames, BoolValues);
m_bDupeCheck = (bool)ParseOptionValue(OPTION_DUPECHECK, BoolCount, BoolNames, BoolValues);
m_bCreateLog = (bool)ParseOptionValue(OPTION_CREATELOG, BoolCount, BoolNames, BoolValues);
m_bParCheck = (bool)ParseOptionValue(OPTION_PARCHECK, BoolCount, BoolNames, BoolValues);
m_bParRepair = (bool)ParseOptionValue(OPTION_PARREPAIR, BoolCount, BoolNames, BoolValues);
m_bStrictParName = (bool)ParseOptionValue(OPTION_STRICTPARNAME, BoolCount, BoolNames, BoolValues);
m_bReloadQueue = (bool)ParseOptionValue(OPTION_RELOADQUEUE, BoolCount, BoolNames, BoolValues);
m_bCursesNZBName = (bool)ParseOptionValue(OPTION_CURSESNZBNAME, BoolCount, BoolNames, BoolValues);
m_bCursesTime = (bool)ParseOptionValue(OPTION_CURSESTIME, BoolCount, BoolNames, BoolValues);
m_bCursesGroup = (bool)ParseOptionValue(OPTION_CURSESGROUP, BoolCount, BoolNames, BoolValues);
m_bCrcCheck = (bool)ParseOptionValue(OPTION_CRCCHECK, BoolCount, BoolNames, BoolValues);
m_bRetryOnCrcError = (bool)ParseOptionValue(OPTION_RETRYONCRCERROR, BoolCount, BoolNames, BoolValues);
m_bDirectWrite = (bool)ParseOptionValue(OPTION_DIRECTWRITE, BoolCount, BoolNames, BoolValues);
const char* OutputModeNames[] = { "loggable", "logable", "log", "colored", "color", "ncurses", "curses" };
const EOutputMode OutputModeValues[] = { omLoggable, omLoggable, omLoggable, omColored, omColored, omNCurses, omNCurses };
const int OutputModeValues[] = { omLoggable, omLoggable, omLoggable, omColored, omColored, omNCurses, omNCurses };
const int OutputModeCount = 7;
m_eOutputMode = (EOutputMode)ParseOptionValue(OPTION_OUTPUTMODE, OutputModeCount, OutputModeNames, (const int*)OutputModeValues);
m_eOutputMode = (EOutputMode)ParseOptionValue(OPTION_OUTPUTMODE, OutputModeCount, OutputModeNames, OutputModeValues);
const char* DecoderNames[] = { "uulib", "yenc", "none", "ydec", "ydecoder" };
const EDecoder DecoderValues[] = { dcUulib, dcYenc, dcNone, dcYenc, dcYenc };
const int DecoderValues[] = { dcUulib, dcYenc, dcNone, dcYenc, dcYenc };
const int DecoderCount = 5;
m_eDecoder = (EDecoder)ParseOptionValue(OPTION_DECODER, DecoderCount, DecoderNames, (const int*)DecoderValues);
m_eDecoder = (EDecoder)ParseOptionValue(OPTION_DECODER, DecoderCount, DecoderNames, DecoderValues);
const char* LoadParsNames[] = { "none", "one", "all", "1", "0" };
const ELoadPars LoadParsValues[] = { plNone, plOne, plAll, plOne, plNone };
const int LoadParsValues[] = { plNone, plOne, plAll, plOne, plNone };
const int LoadParsCount = 4;
m_eLoadPars = (ELoadPars)ParseOptionValue(OPTION_LOADPARS, LoadParsCount, LoadParsNames, (const int*)LoadParsValues);
m_eLoadPars = (ELoadPars)ParseOptionValue(OPTION_LOADPARS, LoadParsCount, LoadParsNames, LoadParsValues);
const char* TargetNames[] = { "screen", "log", "both", "none" };
const EMessageTarget TargetValues[] = { mtScreen, mtLog, mtBoth, mtNone };
const int TargetValues[] = { mtScreen, mtLog, mtBoth, mtNone };
const int TargetCount = 4;
m_eInfoTarget = (EMessageTarget)ParseOptionValue(OPTION_INFOTARGET, TargetCount, TargetNames, (const int*)TargetValues);
m_eWarningTarget = (EMessageTarget)ParseOptionValue(OPTION_WARNINGTARGET, TargetCount, TargetNames, (const int*)TargetValues);
m_eErrorTarget = (EMessageTarget)ParseOptionValue(OPTION_ERRORTARGET, TargetCount, TargetNames, (const int*)TargetValues);
m_eDebugTarget = (EMessageTarget)ParseOptionValue(OPTION_DEBUGTARGET, TargetCount, TargetNames, (const int*)TargetValues);
m_eInfoTarget = (EMessageTarget)ParseOptionValue(OPTION_INFOTARGET, TargetCount, TargetNames, TargetValues);
m_eWarningTarget = (EMessageTarget)ParseOptionValue(OPTION_WARNINGTARGET, TargetCount, TargetNames, TargetValues);
m_eErrorTarget = (EMessageTarget)ParseOptionValue(OPTION_ERRORTARGET, TargetCount, TargetNames, TargetValues);
m_eDebugTarget = (EMessageTarget)ParseOptionValue(OPTION_DEBUGTARGET, TargetCount, TargetNames, TargetValues);
}
int Options::ParseOptionValue(const char * OptName, int argc, const char * argn[], const int argv[])
@@ -563,6 +610,13 @@ void Options::InitCommandLine(int argc, char* argv[])
switch (c)
{
case 'c':
m_szConfigFilename = strdup(optarg);
break;
case 'n':
m_szConfigFilename = NULL;
m_bNoConfig = true;
break;
case 'h':
PrintUsage(argv[0]);
exit(0);
@@ -575,6 +629,7 @@ void Options::InitCommandLine(int argc, char* argv[])
m_bPrintOptions = true;
break;
case 'o':
InitOptFile();
if (!SetOptionString(optarg))
{
abort("FATAL ERROR: could not set option: %s\n", optarg);
@@ -623,25 +678,44 @@ void Options::InitCommandLine(int argc, char* argv[])
case 'E':
{
m_eClientOperation = opClientRequestEditQueue;
bool bGroup = !strcasecmp(optarg, "G");
if (bGroup)
{
optind++;
if (optind > argc)
{
abort("FATAL ERROR: Could not parse value of option 'E'\n");
}
optarg = argv[optind-1];
}
if (!strcasecmp(optarg, "T"))
{
m_iEditQueueAction = NZBMessageRequest::eActionMoveTop;
m_iEditQueueAction = bGroup ? eRemoteEditActionGroupMoveTop : eRemoteEditActionFileMoveTop;
}
else if (!strcasecmp(optarg, "B"))
{
m_iEditQueueAction = NZBMessageRequest::eActionMoveBottom;
m_iEditQueueAction = bGroup ? eRemoteEditActionGroupMoveBottom : eRemoteEditActionFileMoveBottom;
}
else if (!strcasecmp(optarg, "P"))
{
m_iEditQueueAction = NZBMessageRequest::eActionPause;
m_iEditQueueAction = bGroup ? eRemoteEditActionGroupPause : eRemoteEditActionFilePause;
}
else if (!strcasecmp(optarg, "A"))
{
m_iEditQueueAction = bGroup ? eRemoteEditActionGroupPauseAllPars : eRemoteEditActionFilePauseAllPars;
}
else if (!strcasecmp(optarg, "R"))
{
m_iEditQueueAction = bGroup ? eRemoteEditActionGroupPauseExtraPars : eRemoteEditActionFilePauseExtraPars;
}
else if (!strcasecmp(optarg, "U"))
{
m_iEditQueueAction = NZBMessageRequest::eActionResume;
m_iEditQueueAction = bGroup ? eRemoteEditActionGroupResume : eRemoteEditActionFileResume;
}
else if (!strcasecmp(optarg, "D"))
{
m_iEditQueueAction = NZBMessageRequest::eActionDelete;
m_iEditQueueAction = bGroup ? eRemoteEditActionGroupDelete : eRemoteEditActionFileDelete;
}
else
{
@@ -650,90 +724,67 @@ void Options::InitCommandLine(int argc, char* argv[])
{
abort("FATAL ERROR: Could not parse value of option 'E'\n");
}
m_iEditQueueAction = NZBMessageRequest::eActionMoveOffset;
}
break;
}
case 'I':
{
const char* p = strchr(optarg, '-');
if (p)
{
char buf[101];
int maxlen = p - optarg < 100 ? p - optarg : 100;
strncpy(buf, optarg, maxlen);
buf[maxlen] = '\0';
m_iEditQueueIDFrom = atoi(buf);
m_iEditQueueIDTo = atoi(p + 1);
if (m_iEditQueueIDFrom <= 0 || m_iEditQueueIDTo <= 0 ||
m_iEditQueueIDFrom > m_iEditQueueIDTo)
{
abort("FATAL ERROR: wrong value for option 'I'\n");
}
}
else
{
m_iEditQueueIDFrom = atoi(optarg);
if (m_iEditQueueIDFrom <= 0)
{
abort("FATAL ERROR: wrong value for option 'I'\n");
}
m_iEditQueueIDTo = m_iEditQueueIDFrom;
m_iEditQueueAction = bGroup ? eRemoteEditActionGroupMoveOffset : eRemoteEditActionFileMoveOffset;
}
break;
}
case 'Q':
m_eClientOperation = opClientRequestShutdown;
break;
#ifdef DEBUG
case 't':
m_bTest = true;
case 'V':
m_eClientOperation = opClientRequestVersion;
break;
#endif
case '?':
exit(-1);
break;
}
}
InitOptFile();
}
void Options::PrintUsage(char* com)
{
printf("Usage: %s [switches] [<nzb-file>]\n"
"Switches:\n"
" -h, --help Print this help-message\n"
" -v, --version Print version and exit\n"
" -c, --configfile <file> Filename of configuration-file\n"
" -n, --noconfigfile Prevent loading of configuration-file\n"
" (required options must be passed with --option)\n"
" -p, --printconfig Print configuration and exit\n"
" -o, --option <name=value> Set or override option in configuration-file\n"
" -s, --server Start nzbget as a server in console-mode\n"
" -D, --daemon Start nzbget as a server in daemon-mode\n"
" -Q, --quit Shutdown the server\n"
" -A, --append Send file to the server's download queue\n"
" -C, --connect Attach client to server\n"
" -L, --list Request list of downloads from the server\n"
" -P, --pause Pause downloading on the server\n"
" -U, --unpause Unpause downloading on the server\n"
" -R, --rate Set the download rate on the server\n"
" -T, --top Add file to the top (begining) of queue\n"
" (should be used with switch --append)\n"
" -G, --log <lines> Request last <lines> lines from server's screen-log\n"
" -E, --edit <action> Edit queue on the server\n"
" (must be used with switch --fileid):\n"
" where <action> is one of:\n"
" <+offset|-offset> Move file(s) in queue relative to current position\n"
" offset is an integer number\n"
" T Move file(s) to the top of queue\n"
" B Move file(s) to the bottom of queue\n"
" P Pause file(s)\n"
" U Resume (unpause) file(s)\n"
" D Delete file(s)\n"
" -I, --fileid <FileID|FileIDFrom-FileIDTo> File-id(s) for switch '-E',\n"
" as printed by switch --list\n"
"",
com);
printf("Usage:\n"
" %s [switches] [<nzb-file>]\n\n"
"Switches:\n"
" -h, --help Print this help-message\n"
" -v, --version Print version and exit\n"
" -c, --configfile <file> Filename of configuration-file\n"
" -n, --noconfigfile Prevent loading of configuration-file\n"
" (required options must be passed with --option)\n"
" -p, --printconfig Print configuration and exit\n"
" -o, --option <name=value> Set or override option in configuration-file\n"
" -s, --server Start nzbget as a server in console-mode\n"
#ifndef WIN32
" -D, --daemon Start nzbget as a server in daemon-mode\n"
#endif
" -V, --serverversion Print server's version and exit\n"
" -Q, --quit Shutdown the server\n"
" -A, --append Send file to the server's download queue\n"
" -C, --connect Attach client to server\n"
" -L, --list Request list of downloads from the server\n"
" -P, --pause Pause downloading on the server\n"
" -U, --unpause Unpause downloading on the server\n"
" -R, --rate Set the download rate on the server\n"
" -T, --top Add file to the top (begining) of queue\n"
" (should be used with switch --append)\n"
" -G, --log <lines> Request last <lines> lines from server's screen-log\n"
" -E, --edit [G] <action> <IDs> Edit queue on the server\n"
" <G> Affect all files in the group (same nzb-file)\n"
" <action> is one of:\n"
" <+offset|-offset> Move file(s) in queue relative to current position,\n"
" offset is an integer value\n"
" T Move file(s) to the top of queue\n"
" B Move file(s) to the bottom of queue\n"
" P Pause file(s)\n"
" U Resume (unpause) file(s)\n"
" A Pause all pars (for groups)\n"
" R Pause extra pars (for groups)\n"
" D Delete file(s)\n"
" <IDs> Comma-separated list of file-ids or ranges\n"
" of file-ids, e. g.: 1-5,3,10-22\n",
BaseFileName(com));
}
void Options::InitFileArg(int argc, char* argv[])
@@ -749,6 +800,10 @@ void Options::InitFileArg(int argc, char* argv[])
exit(-1);
}
}
else if (m_eClientOperation == opClientRequestEditQueue)
{
ParseFileIDList(argc, argv, optind);
}
else
{
// Check if the file-name is a relative path or an absolute path
@@ -1066,4 +1121,90 @@ void Options::CheckOptions()
abort("FATAL ERROR: Program was compiled without curses-support. Can not use \"curses\" frontend (option \"%s\")\n", OPTION_OUTPUTMODE);
}
#endif
if (m_eDecoder != dcYenc)
{
m_bDirectWrite = false;
}
}
void Options::ParseFileIDList(int argc, char* argv[], int optind)
{
std::vector<int> IDs;
IDs.clear();
while (optind < argc)
{
char* szWritableFileIDList = strdup(argv[optind++]);
char* optarg = strtok(szWritableFileIDList, ", ");
while (optarg)
{
int iEditQueueIDFrom = 0;
int iEditQueueIDTo = 0;
const char* p = strchr(optarg, '-');
if (p)
{
char buf[101];
int maxlen = p - optarg < 100 ? p - optarg : 100;
strncpy(buf, optarg, maxlen);
buf[maxlen] = '\0';
iEditQueueIDFrom = atoi(buf);
iEditQueueIDTo = atoi(p + 1);
if (iEditQueueIDFrom <= 0 || iEditQueueIDTo <= 0)
{
abort("FATAL ERROR: invalid list of file IDs\n");
}
}
else
{
iEditQueueIDFrom = atoi(optarg);
if (iEditQueueIDFrom <= 0)
{
abort("FATAL ERROR: invalid list of file IDs\n");
}
iEditQueueIDTo = iEditQueueIDFrom;
}
int iEditQueueIDCount = 0;
if (iEditQueueIDTo != 0)
{
if (iEditQueueIDFrom < iEditQueueIDTo)
{
iEditQueueIDCount = iEditQueueIDTo - iEditQueueIDFrom + 1;
}
else
{
iEditQueueIDCount = iEditQueueIDFrom - iEditQueueIDTo + 1;
}
}
else
{
iEditQueueIDCount = 1;
}
for (int i = 0; i < iEditQueueIDCount; i++)
{
if (iEditQueueIDFrom < iEditQueueIDTo || iEditQueueIDTo == 0)
{
IDs.push_back(iEditQueueIDFrom + i);
}
else
{
IDs.push_back(iEditQueueIDFrom - i);
}
}
optarg = strtok(NULL, ", ");
}
free(szWritableFileIDList);
}
m_iEditQueueIDCount = IDs.size();
m_pEditQueueIDList = (int*)malloc(sizeof(int) * m_iEditQueueIDCount);
for (int i = 0; i < m_iEditQueueIDCount; i++)
{
m_pEditQueueIDList[i] = IDs[i];
}
}

View File

@@ -43,7 +43,8 @@ public:
opClientRequestDumpDebug,
opClientRequestEditQueue,
opClientRequestLog,
opClientRequestShutdown
opClientRequestShutdown,
opClientRequestVersion
};
enum EMessageTarget
{
@@ -80,6 +81,8 @@ private:
std::vector< struct OptEntry > optEntries;
bool m_bConfigInitialized;
// Options
char* m_szConfigFilename;
char* m_szDestDir;
@@ -106,6 +109,7 @@ private:
char* m_szServerPassword;
int m_szServerPort;
char* m_szLockFile;
char* m_szDaemonUserName;
EOutputMode m_eOutputMode;
bool m_bReloadQueue;
int m_iLogBufferSize;
@@ -117,6 +121,18 @@ private:
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;
@@ -124,14 +140,13 @@ private:
bool m_bRemoteClientMode;
int m_iEditQueueAction;
int m_iEditQueueOffset;
int m_iEditQueueIDFrom;
int m_iEditQueueIDTo;
int* m_pEditQueueIDList;
int m_iEditQueueIDCount;
char* m_szArgFilename;
bool m_bPrintOptions;
bool m_bAddTop;
float m_fSetRate;
int m_iLogLines;
bool m_bTest;
// Current state
bool m_bPause;
@@ -139,7 +154,7 @@ private:
EClientOperation m_eClientOperation;
void InitDefault();
void InitOptFile(int argc, char* argv[]);
void InitOptFile();
void InitCommandLine(int argc, char* argv[]);
void InitOptions();
void InitFileArg(int argc, char* argv[]);
@@ -155,10 +170,11 @@ private:
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(int argc, char* argv[]);
~Options();
// Options
const char* GetDestDir() { return m_szDestDir; }
@@ -185,6 +201,7 @@ public:
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; }
@@ -195,6 +212,18 @@ public:
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; }
@@ -203,13 +232,12 @@ public:
EClientOperation GetClientOperation() { return m_eClientOperation; }
int GetEditQueueAction() { return m_iEditQueueAction; }
int GetEditQueueOffset() { return m_iEditQueueOffset; }
int GetEditQueueIDFrom() { return m_iEditQueueIDFrom; }
int GetEditQueueIDTo() { return m_iEditQueueIDTo; }
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; }
bool GetTest() { return m_bTest; }
// Current state
void SetPause(bool bOnOff) { m_bPause = bOnOff; }

View File

@@ -159,9 +159,7 @@ void ParChecker::Run()
Result res;
Repairer* repairer = new Repairer();
#ifdef ENABLE_PARPROGRESS
repairer->sig_filename.connect(sigc::mem_fun(*this, &ParChecker::signal_filename));
#endif
res = repairer->PreProcess(commandLine);
debug("ParChecker: PreProcess-result=%i", res);

View File

@@ -51,20 +51,46 @@
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");
struct stat buffer;
m_bCheckIncomingNZBs = !stat(g_pOptions->GetNzbDir(), &buffer) && S_ISDIR(buffer.st_mode);
m_bHasMoreJobs = false;
m_QueueCoordinatorObserver.owner = this;
g_pQueueCoordinator->Attach(&m_QueueCoordinatorObserver);
#ifndef DISABLE_PARCHECK
m_ParQueue.clear();
#ifndef DISABLE_PARCHECK
m_ParCheckerObserver.owner = this;
m_ParChecker.Attach(&m_ParCheckerObserver);
#endif
@@ -74,12 +100,10 @@ PrePostProcessor::~PrePostProcessor()
{
debug("Destroying PrePostProcessor");
#ifndef DISABLE_PARCHECK
for (ParQueue::iterator it = m_ParQueue.begin(); it != m_ParQueue.end(); it++)
{
delete *it;
}
#endif
}
void PrePostProcessor::Run()
@@ -92,9 +116,10 @@ void PrePostProcessor::Run()
#endif
while (!IsStopped())
{
if (m_bCheckIncomingNZBs && iNZBDirInterval == 5000)
if (g_pOptions->GetNzbDir() && g_pOptions->GetNzbDirInterval() > 0 &&
iNZBDirInterval == g_pOptions->GetNzbDirInterval() * 1000)
{
// check nzbdir every 5 seconds
// check nzbdir every g_pOptions->GetNzbDirInterval() seconds
CheckIncomingNZBs();
iNZBDirInterval = 0;
}
@@ -159,111 +184,48 @@ void PrePostProcessor::QueueCoordinatorUpdate(Subject * Caller, void * Aspect)
#ifndef DISABLE_PARCHECK
!AddPar(pAspect->pFileInfo, pAspect->eAction == QueueCoordinator::eaFileDeleted) &&
#endif
WasLastUnpausedInCollection(pAspect->pDownloadQueue, pAspect->pFileInfo))
WasLastInCollection(pAspect->pDownloadQueue, pAspect->pFileInfo, true))
{
char szNZBNiceName[1024];
pAspect->pFileInfo->GetNiceNZBName(szNZBNiceName, 1024);
info("Collection %s completely downloaded", szNZBNiceName);
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())
if (g_pOptions->GetParCheck() &&
pAspect->eAction == QueueCoordinator::eaFileCompleted)
{
CheckPars(pAspect->pDownloadQueue, pAspect->pFileInfo);
}
else
#endif
{
ExecPostScript(pAspect->pFileInfo->GetDestDir(), pAspect->pFileInfo->GetNZBFilename(), "", false);
ExecPostScript(pAspect->pFileInfo->GetDestDir(), pAspect->pFileInfo->GetNZBFilename(), "", PARSTATUS_NOT_CHECKED);
}
}
}
}
/**
* If the option "loadpars" is set to "none", then we pause all par2-files.
* If the option "loadpars" is set to "one", we use the following strategy:
* Firstly we find all par-files, which do not have "vol" in their names, then we pause
* all vols and download all just-pars.
* In a case, if there are no just-pars, but only vols, we find the smallest vol-file
* and download only it.
*/
void PrePostProcessor::PausePars(DownloadQueue* pDownloadQueue, const char* szNZBFilename)
{
debug("Pausing pars");
DownloadQueue Pars, Vols;
Pars.clear();
Vols.clear();
char szNZBNiceName[1024];
FileInfo::MakeNiceNZBName(szNZBFilename, szNZBNiceName, 1024);
debug("PrePostProcessor: Pausing pars");
for (DownloadQueue::iterator it = pDownloadQueue->begin(); it != pDownloadQueue->end(); it++)
{
FileInfo* pFileInfo = *it;
if (!strcmp(pFileInfo->GetNZBFilename(), szNZBFilename))
{
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 (g_pOptions->GetLoadPars() == Options::plNone)
{
info("Pausing %s%c%s", szNZBNiceName, (int)PATH_SEPARATOR, pFileInfo->GetFilename());
pFileInfo->SetPaused(true);
}
else
{
if (strstr(szLoFileName, ".vol"))
{
Vols.push_back(pFileInfo);
}
else
{
Pars.push_back(pFileInfo);
}
}
}
}
}
if (g_pOptions->GetLoadPars() == Options::plOne ||
(g_pOptions->GetLoadPars() == Options::plNone && g_pOptions->GetParCheck()))
{
if (!Pars.empty())
{
for (DownloadQueue::iterator it = Vols.begin(); it != Vols.end(); it++)
{
FileInfo* pFileInfo = *it;
info("Pausing %s%c%s", szNZBNiceName, (int)PATH_SEPARATOR, pFileInfo->GetFilename());
pFileInfo->SetPaused(true);
}
}
else
{
// pausing all Vol-files except the smallest one
FileInfo* pSmallest = NULL;
for (DownloadQueue::iterator it = Vols.begin(); it != Vols.end(); it++)
{
FileInfo* pFileInfo = *it;
if (!pSmallest)
{
pSmallest = pFileInfo;
}
else if (pSmallest->GetSize() > pFileInfo->GetSize())
{
info("Pausing %s%c%s", szNZBNiceName, (int)PATH_SEPARATOR, pSmallest->GetFilename());
pSmallest->SetPaused(true);
pSmallest = pFileInfo;
}
else
{
info("Pausing %s%c%s", szNZBNiceName, (int)PATH_SEPARATOR, pFileInfo->GetFilename());
pFileInfo->SetPaused(true);
}
}
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;
}
}
}
@@ -286,10 +248,10 @@ void PrePostProcessor::CheckIncomingNZBs()
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 > 60 &&
time(NULL) - buffer.st_ctime > 60)
time(NULL) - buffer.st_mtime > g_pOptions->GetNzbDirFileAge() &&
time(NULL) - buffer.st_ctime > g_pOptions->GetNzbDirFileAge())
{
// the file is at least 60 seconds old, we can process it
// 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))
@@ -321,16 +283,16 @@ void PrePostProcessor::CheckIncomingNZBs()
}
/**
* Check if the completed file was last unpaused file in nzb-collection
* Check if the completed file was last (unpaused, if bIgnorePaused is "true") file in nzb-collection
*/
bool PrePostProcessor::WasLastUnpausedInCollection(DownloadQueue* pDownloadQueue, FileInfo * pFileInfo)
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 && !pFileInfo2->GetPaused() &&
if (pFileInfo2 != pFileInfo && (!bIgnorePaused || !pFileInfo2->GetPaused()) &&
!strcmp(pFileInfo2->GetNZBFilename(), pFileInfo->GetNZBFilename()))
{
return false;
@@ -340,31 +302,19 @@ bool PrePostProcessor::WasLastUnpausedInCollection(DownloadQueue* pDownloadQueue
return true;
}
PrePostProcessor::ParQueue* PrePostProcessor::LockParQueue()
{
m_mutexParChecker.Lock();
return &m_ParQueue;
}
void PrePostProcessor::UnlockParQueue()
{
m_mutexParChecker.Unlock();
}
#ifndef DISABLE_PARCHECK
PrePostProcessor::QueuedFile::QueuedFile(const char * szNZBFilename, const char * szParFilename, const char * szInfoName)
{
m_szNZBFilename = strdup(szNZBFilename);
m_szParFilename = strdup(szParFilename);
m_szInfoName = strdup(szInfoName);
}
PrePostProcessor::QueuedFile::~ QueuedFile()
{
if (m_szNZBFilename)
{
free(m_szNZBFilename);
}
if (m_szParFilename)
{
free(m_szParFilename);
}
if (m_szInfoName)
{
free(m_szInfoName);
}
}
void PrePostProcessor::CheckPars(DownloadQueue * pDownloadQueue, FileInfo * pFileInfo)
{
char szNZBNiceName[1024];
@@ -396,8 +346,8 @@ void PrePostProcessor::CheckPars(DownloadQueue * pDownloadQueue, FileInfo * pFil
szParInfoName[1024-1] = '\0';
info("Queueing %s%c%s for par-check", szNZBNiceName, (int)PATH_SEPARATOR, szInfoName);
QueuedFile* pQueuedFile = new QueuedFile(pFileInfo->GetNZBFilename(), szFullFilename, szParInfoName);
m_ParQueue.push_back(pQueuedFile);
ParJob* pParJob = new ParJob(pFileInfo->GetNZBFilename(), szFullFilename, szParInfoName);
m_ParQueue.push_back(pParJob);
m_bHasMoreJobs = true;
free(szParFilename);
@@ -475,16 +425,13 @@ void PrePostProcessor::CheckParQueue()
if (!m_ParChecker.IsRunning() && !m_ParQueue.empty())
{
QueuedFile* pQueuedFile = m_ParQueue.front();
ParJob* pParJob = m_ParQueue.front();
info("Checking pars for %s", pQueuedFile->GetInfoName());
m_ParChecker.SetNZBFilename(pQueuedFile->GetNZBFilename());
m_ParChecker.SetParFilename(pQueuedFile->GetParFilename());
m_ParChecker.SetInfoName(pQueuedFile->GetInfoName());
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_ParQueue.pop_front();
delete pQueuedFile;
}
m_mutexParChecker.Unlock();
@@ -538,10 +485,26 @@ void PrePostProcessor::ParCheckerUpdate(Subject * Caller, void * Aspect)
}
}
ExecPostScript(szPath, m_ParChecker.GetNZBFilename(), m_ParChecker.GetParFilename(),
m_ParChecker.GetStatus() == ParChecker::psFinished);
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();
}
@@ -549,7 +512,7 @@ void PrePostProcessor::ParCheckerUpdate(Subject * Caller, void * Aspect)
#endif
void PrePostProcessor::ExecPostScript(const char * szPath, const char * szNZBFilename, const char * szParFilename, bool bParOK)
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)
@@ -584,16 +547,19 @@ void PrePostProcessor::ExecPostScript(const char * szPath, const char * szNZBFil
if (bCollectionCompleted)
{
m_mutexParChecker.Lock();
bCollectionCompleted = m_ParQueue.empty();
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
int iParStatus = 0;
if (strlen(szParFilename) != 0)
{
iParStatus = (int)bParOK + 1;
}
char szParStatus[10];
snprintf(szParStatus, 10, "%i", iParStatus);
szParStatus[10-1] = '\0';

View File

@@ -38,6 +38,24 @@
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;
@@ -55,39 +73,23 @@ private:
PrePostProcessor* owner;
virtual void Update(Subject* Caller, void* Aspect) { owner->ParCheckerUpdate(Caller, Aspect); }
};
class QueuedFile
{
private:
char* m_szNZBFilename;
char* m_szParFilename;
char* m_szInfoName;
public:
QueuedFile(const char* szNZBFilename, const char* szParFilename, const char* szInfoName);
~QueuedFile();
const char* GetNZBFilename() { return m_szNZBFilename; }
const char* GetParFilename() { return m_szParFilename; }
const char* GetInfoName() { return m_szInfoName; }
};
typedef std::deque<QueuedFile*> ParQueue;
#endif
private:
bool m_bCheckIncomingNZBs;
QueueCoordinatorObserver m_QueueCoordinatorObserver;
bool m_bHasMoreJobs;
void PausePars(DownloadQueue* pDownloadQueue, const char* szNZBFilename);
void CheckIncomingNZBs();
bool WasLastUnpausedInCollection(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo);
void ExecPostScript(const char* szPath, const char* szNZBFilename, const char * szParFilename, bool bParOK);
#ifndef DISABLE_PARCHECK
ParChecker m_ParChecker;
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);
@@ -105,6 +107,8 @@ public:
virtual void Stop();
void QueueCoordinatorUpdate(Subject* Caller, void* Aspect);
bool HasMoreJobs() { return m_bHasMoreJobs; }
ParQueue* LockParQueue();
void UnlockParQueue();
};
#endif

View File

@@ -34,6 +34,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#ifndef WIN32
#include <unistd.h>
#include <sys/time.h>
@@ -44,11 +45,14 @@
#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()
{
@@ -58,7 +62,19 @@ QueueCoordinator::QueueCoordinator()
m_DownloadQueue.clear();
m_ActiveDownloads.clear();
Decoder::Init();
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()
@@ -80,7 +96,7 @@ QueueCoordinator::~QueueCoordinator()
}
m_ActiveDownloads.clear();
Decoder::Final();
YDecoder::Final();
debug("QueueCoordinator destroyed");
}
@@ -91,60 +107,82 @@ void QueueCoordinator::Run()
m_mutexDownloadQueue.Lock();
m_DiskState.CleanupTempDir(&m_DownloadQueue);
if (g_pOptions->GetServerMode() && g_pOptions->GetSaveQueue() && m_DiskState.Exists())
if (g_pOptions->GetServerMode() && g_pOptions->GetSaveQueue() && g_pDiskState->Exists())
{
if (g_pOptions->GetReloadQueue())
{
m_DiskState.Load(&m_DownloadQueue);
g_pDiskState->Load(&m_DownloadQueue);
}
else
{
m_DiskState.Discard();
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())
{
while (g_pOptions->GetPause() && !IsStopped())
if (!g_pOptions->GetPause())
{
// Sleep for a while
usleep(500 * 1000);
}
if (g_pServerPool->HasFreeConnection())
{
// start download for next article
FileInfo* pFileInfo;
ArticleInfo* pArticleInfo;
m_mutexDownloadQueue.Lock();
bool bHasMoreArticles = GetNextArticle(pFileInfo, pArticleInfo);
m_bHasMoreJobs = bHasMoreArticles || !m_ActiveDownloads.empty();
if (bHasMoreArticles && !IsStopped())
NNTPConnection* pConnection = g_pServerPool->GetConnection(0, false);
if (pConnection)
{
StartArticleDownload(pFileInfo, pArticleInfo);
}
m_mutexDownloadQueue.Unlock();
// start download for next article
FileInfo* pFileInfo;
ArticleInfo* pArticleInfo;
if (!IsStopped())
{
// two possibilities:
// 1) hasMoreArticles==false: there are no jobs, waiting for a while
// 2) hasMoreArticles==true: the pause prevents starting of many threads, before the download-thread locks the connection
usleep(100 * 1000);
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
{
// there are no free connection available, waiting for a while
usleep(100 * 1000);
m_mutexDownloadQueue.Lock();
bArticeDownloadsRunning = !m_ActiveDownloads.empty();
m_mutexDownloadQueue.Unlock();
}
ResetHangingDownloads();
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
@@ -170,34 +208,67 @@ void QueueCoordinator::AddNZBFileToQueue(NZBFile* pNZBFile, bool bAddFirst)
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;
pFileInfo->BuildDestDirName(pNZBFile->GetFileName());
if (g_pOptions->GetDupeCheck() && IsDupe(pFileInfo))
if (g_pOptions->GetDupeCheck())
{
warn("File \"%s\" seems to be duplicate, skipping", pFileInfo->GetFilename());
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
{
if (bAddFirst)
{
tmpDownloadQueue.push_front(pFileInfo);
}
else
{
m_DownloadQueue.push_back(pFileInfo);
}
tmpDownloadQueue.push_back(pFileInfo);
}
}
if (bAddFirst)
for (DownloadQueue::iterator it = tmpDownloadQueue.begin(); it != tmpDownloadQueue.end(); it++)
{
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();
@@ -207,7 +278,7 @@ void QueueCoordinator::AddNZBFileToQueue(NZBFile* pNZBFile, bool bAddFirst)
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
{
m_DiskState.Save(&m_DownloadQueue, false);
g_pDiskState->Save(&m_DownloadQueue);
}
m_mutexDownloadQueue.Unlock();
@@ -224,7 +295,7 @@ bool QueueCoordinator::AddFileToQueue(const char* szFileName)
return false;
}
// Add NZBFile to Qeue
// Add NZBFile to Queue
AddNZBFileToQueue(pNZBFile, false);
delete pNZBFile;
@@ -232,45 +303,57 @@ bool QueueCoordinator::AddFileToQueue(const char* szFileName)
return true;
}
/*
* NOTE: see note to "AddSpeedReading"
*/
float QueueCoordinator::CalcCurrentDownloadSpeed()
{
float fSpeedAllDownloads = 0;
int iTotal = 0;
m_mutexDownloadQueue.Lock();
struct _timeval curtime;
gettimeofday(&curtime, 0);
for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++)
for (int i = 0; i < SPEEDMETER_SECONDS; i++)
{
ArticleDownloader* pArticleDownloader = *it;
float fSpeed = 0.0f;
struct _timeval* arttime = pArticleDownloader->GetStartTime();
#ifdef WIN32
if (arttime->time != 0)
#else
if (arttime->tv_sec != 0)
#endif
{
#ifdef WIN32
float tdiff = (float)((curtime.time - arttime->time) + (curtime.millitm - arttime->millitm) / 1000.0);
#else
float tdiff = (float)((curtime.tv_sec - arttime->tv_sec) + (curtime.tv_usec - arttime->tv_usec) / 1000000.0);
#endif
if (tdiff > 0)
{
fSpeed = (pArticleDownloader->GetBytes() / tdiff / 1024);
}
}
fSpeedAllDownloads += fSpeed;
iTotal += m_iSpeedBytes[i];
}
m_mutexDownloadQueue.Unlock();
float fSpeed = iTotal / 1024.0 / SPEEDMETER_SECONDS;
return fSpeedAllDownloads;
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()
@@ -291,153 +374,32 @@ long long QueueCoordinator::CalcRemainingSize()
return lRemainingSize;
}
int QueueCoordinator::GetFileInfoID(unsigned int iEntry)
{
int ID = 0;
m_mutexDownloadQueue.Lock();
if (iEntry < m_DownloadQueue.size())
{
ID = m_DownloadQueue[iEntry]->GetID();
}
m_mutexDownloadQueue.Unlock();
return ID;
}
FileInfo* QueueCoordinator::FindFileInfo(int iID)
{
for (DownloadQueue::iterator it = m_DownloadQueue.begin(); it != m_DownloadQueue.end(); it++)
{
FileInfo* pFileInfo = *it;
if (pFileInfo->GetID() == iID)
{
return pFileInfo;
}
}
return NULL;
}
int QueueCoordinator::GetFileInfoEntry(int iID)
{
int iEntry = 0;
for (DownloadQueue::iterator it = m_DownloadQueue.begin(); it != m_DownloadQueue.end(); it++)
{
FileInfo* pFileInfo = *it;
if (pFileInfo->GetID() == iID)
{
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
* 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::EditQueuePauseUnpauseEntry(int iID, bool bPause)
bool QueueCoordinator::DeleteQueueEntry(FileInfo* pFileInfo)
{
bool res = false;
m_mutexDownloadQueue.Lock();
FileInfo* pFileInfo = FindFileInfo(iID);
if (pFileInfo)
pFileInfo->SetDeleted(true);
bool hasDownloads = false;
for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++)
{
pFileInfo->SetPaused(bPause);
res = true;
}
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
{
m_DiskState.Save(&m_DownloadQueue, true);
}
m_mutexDownloadQueue.Unlock();
return res;
}
/*
* Removes entry with index iEntry
* returns true if successful, false if operation is not possible
*/
bool QueueCoordinator::EditQueueDeleteEntry(int iID)
{
debug("Deleting queue entry");
bool res = false;
m_mutexDownloadQueue.Lock();
FileInfo* pFileInfo = FindFileInfo(iID);
if (pFileInfo)
{
info("Deleting file %s from download queue", pFileInfo->GetFilename());
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)
{
ArticleDownloader* pArticleDownloader = *it;
if (pArticleDownloader->GetFileInfo() == pFileInfo)
{
hasDownloads = true;
pArticleDownloader->Stop();
}
}
if (!hasDownloads)
{
DeleteFileInfo(pFileInfo);
}
res = true;
}
m_mutexDownloadQueue.Unlock();
debug("Queue entry deleted");
return res;
}
/*
* Moves entry identified with iID in the queue
* returns true if successful, false if operation is not possible
*/
bool QueueCoordinator::EditQueueMoveEntry(int iID, int iOffset, bool bAutoCorrection)
{
bool res = false;
m_mutexDownloadQueue.Lock();
int iEntry = GetFileInfoEntry(iID);
if (iEntry >= 0)
{
int iNewEntry = iEntry + iOffset;
if (bAutoCorrection && iNewEntry < 0)
{
iNewEntry = 0;
}
if (bAutoCorrection && (unsigned int)iNewEntry > m_DownloadQueue.size() - 1)
{
iNewEntry = (int)m_DownloadQueue.size() - 1;
}
if (iNewEntry >= 0 && (unsigned int)iNewEntry <= m_DownloadQueue.size() - 1)
{
FileInfo* fi = m_DownloadQueue[iEntry];
m_DownloadQueue.erase(m_DownloadQueue.begin() + iEntry);
m_DownloadQueue.insert(m_DownloadQueue.begin() + iNewEntry, fi);
res = true;
hasDownloads = true;
pArticleDownloader->Stop();
}
}
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
if (!hasDownloads)
{
m_DiskState.Save(&m_DownloadQueue, true);
}
Aspect aspect = { eaFileDeleted, pFileInfo, &m_DownloadQueue, NULL };
Notify(&aspect);
m_mutexDownloadQueue.Unlock();
return res;
DeleteFileInfo(pFileInfo, false);
}
return hasDownloads;
}
void QueueCoordinator::Stop()
@@ -463,6 +425,10 @@ bool QueueCoordinator::GetNextArticle(FileInfo* &pFileInfo, ArticleInfo* &pArtic
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;
@@ -477,7 +443,7 @@ bool QueueCoordinator::GetNextArticle(FileInfo* &pFileInfo, ArticleInfo* &pArtic
return false;
}
void QueueCoordinator::StartArticleDownload(FileInfo* pFileInfo, ArticleInfo* pArticleInfo)
void QueueCoordinator::StartArticleDownload(FileInfo* pFileInfo, ArticleInfo* pArticleInfo, NNTPConnection* pConnection)
{
debug("Starting new ArticleDownloader");
@@ -486,13 +452,13 @@ void QueueCoordinator::StartArticleDownload(FileInfo* pFileInfo, ArticleInfo* pA
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();
pArticleDownloader->WaitInit();
}
void QueueCoordinator::BuildArticleFilename(ArticleDownloader* pArticleDownloader, FileInfo* pFileInfo, ArticleInfo* pArticleInfo)
@@ -502,7 +468,7 @@ void QueueCoordinator::BuildArticleFilename(ArticleDownloader* pArticleDownloade
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';
@@ -514,6 +480,13 @@ void QueueCoordinator::BuildArticleFilename(ArticleDownloader* pArticleDownloade
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()
@@ -562,26 +535,23 @@ void QueueCoordinator::ArticleCompleted(ArticleDownloader* pArticleDownloader)
bool fileCompleted = (int)pFileInfo->GetArticles()->size() == pFileInfo->GetCompleted();
if (!pFileInfo->GetFilenameConfirmed() &&
pArticleDownloader->GetStatus() == ArticleDownloader::adFinished &&
pArticleDownloader->GetArticleFilename())
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);
}
}
m_mutexDownloadQueue.Unlock();
bool deleteFileObj = false;
if (fileCompleted && !IsStopped() && !pFileInfo->GetDeleted())
if (pFileInfo->GetDeleted())
{
// all jobs done
pArticleDownloader->CompleteFileParts();
deleteFileObj = true;
}
else if (pFileInfo->GetDeleted())
{
m_mutexDownloadQueue.Lock();
int cnt = 0;
for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++)
{
@@ -590,7 +560,6 @@ void QueueCoordinator::ArticleCompleted(ArticleDownloader* pArticleDownloader)
cnt++;
}
}
m_mutexDownloadQueue.Unlock();
if (cnt == 1)
{
// this was the last Download for a file deleted from queue
@@ -598,8 +567,16 @@ void QueueCoordinator::ArticleCompleted(ArticleDownloader* pArticleDownloader)
}
}
if (fileCompleted && !IsStopped() && !pFileInfo->GetDeleted())
{
// all jobs done
m_mutexDownloadQueue.Unlock();
pArticleDownloader->CompleteFileParts();
m_mutexDownloadQueue.Lock();
deleteFileObj = true;
}
// delete Download from Queue
m_mutexDownloadQueue.Lock();
for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++)
{
ArticleDownloader* pa = *it;
@@ -617,12 +594,13 @@ void QueueCoordinator::ArticleCompleted(ArticleDownloader* pArticleDownloader)
Aspect aspect = { fileCompleted ? eaFileCompleted : eaFileDeleted, pFileInfo, &m_DownloadQueue, NULL };
Notify(&aspect);
DeleteFileInfo(pFileInfo);
DeleteFileInfo(pFileInfo, fileCompleted);
}
m_mutexDownloadQueue.Unlock();
}
void QueueCoordinator::DeleteFileInfo(FileInfo* pFileInfo)
void QueueCoordinator::DeleteFileInfo(FileInfo* pFileInfo, bool bCompleted)
{
for (DownloadQueue::iterator it = m_DownloadQueue.begin(); it != m_DownloadQueue.end(); it++)
{
@@ -636,7 +614,32 @@ void QueueCoordinator::DeleteFileInfo(FileInfo* pFileInfo)
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
{
m_DiskState.DiscardFileInfo(&m_DownloadQueue, pFileInfo);
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;
@@ -646,16 +649,19 @@ bool QueueCoordinator::IsDupe(FileInfo* pFileInfo)
{
debug("Checking if the file is already queued");
if (pFileInfo->IsDupe())
// 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()))
!strcmp(pFileInfo->GetFilename(), pQueueEntry->GetFilename()) &&
pFileInfo != pQueueEntry)
{
return true;
}
@@ -726,3 +732,50 @@ void QueueCoordinator::ResetHangingDownloads()
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();
}

View File

@@ -29,13 +29,18 @@
#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 "DiskState.h"
#include "QueueEditor.h"
#include "NNTPConnection.h"
class QueueCoordinator : public Thread, public Observer, public Subject, public DownloadSpeedMeter
{
@@ -58,18 +63,28 @@ public:
private:
DownloadQueue m_DownloadQueue;
ActiveDownloads m_ActiveDownloads;
DiskState m_DiskState;
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);
void StartArticleDownload(FileInfo* pFileInfo, ArticleInfo* pArticleInfo, NNTPConnection* pConnection);
void BuildArticleFilename(ArticleDownloader* pArticleDownloader, FileInfo* pFileInfo, ArticleInfo* pArticleInfo);
bool IsDupe(FileInfo* pFileInfo);
void ArticleCompleted(ArticleDownloader* pArticleDownloader);
FileInfo* FindFileInfo(int iID);
int GetFileInfoEntry(int iID);
void DeleteFileInfo(FileInfo* pFileInfo);
void DeleteFileInfo(FileInfo* pFileInfo, bool bCompleted);
void ResetHangingDownloads();
public:
@@ -77,20 +92,22 @@ public:
virtual ~QueueCoordinator();
virtual void Run();
virtual void Stop();
long long CalcRemainingSize();
virtual float CalcCurrentDownloadSpeed();
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 EditQueuePauseUnpauseEntry(int iID, bool bPause);
bool EditQueueDeleteEntry(int iID);
bool EditQueueMoveEntry(int iID, int iOffset, bool bAutoCorrection);
int GetFileInfoID(unsigned int iEntry);
bool HasMoreJobs() { return m_bHasMoreJobs; }
bool DeleteQueueEntry(FileInfo* pFileInfo);
QueueEditor* GetQueueEditor() { return &m_QueueEditor; }
void LogDebugInfo();
};

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

69
README
View File

@@ -30,32 +30,36 @@ Then you use client to send requests to server. The sample requests
are: download nzb-file, list files in queue, etc.
Standalone-tool, server and client are all contained in only one
executable file "NZBGet". The mode in which the program works
executable file "nzbget". The mode in which the program works
depends on command-line parameters passed to the program.
=====================================
2. Supported OS
=====================================
NZBGet is written in C++ and was initialy developen on Linux.
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.0) should run at least on:
- Linux Debian 3.1 on x86;
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;
- Solaris 10 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 few platforms (including Windows), but 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".
@@ -65,8 +69,7 @@ If you have downloaded binaries you can just jump to section
=====================================
NZBGet is developed on a linux-system, but it should run on other
POSIX platforms (tested on FreeBSD and Solaris for x86; although
the par-support and uulib were not tested).
POSIX platforms (see the list of tested platforms above).
NZBGet absolutely needs the following libraries:
@@ -75,22 +78,24 @@ NZBGet absolutely needs the following libraries:
And the following libraries are optional:
- for curses-output-mode:
- for curses-output-mode (enabled by default):
- libcurses (usually part of commercial systems)
or (better)
- libncurses (http://invisible-island.net/ncurses)
- for par-check and -repair:
- for par-check and -repair (enabled by default):
- libpar2 (http://parchive.sourceforge.net)
- libsigc++ (http://libsigc.sourceforge.net)
- for support of encoding-formats other than yEnc:
- 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 Linux distributions and
should be available as installable packages. On other systems you
may need to download the libraries at the given URLs and compile
them (see hints below).
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
@@ -129,11 +134,6 @@ You may run configure with additional arguments:
--disable-parcheck - to make without parcheck-support. Use this option
if you can not use libpar2 or libsigc++.
--disable-parprogress - to not show progress during par-check. This option
may be needed on some systems, where sigc++ does not work very well
(uClibc on MIPSEL is one of them). If par-check segfaults right after
start, then you probably need this option.
--enable-debug - to build in debug-mode, if you want to see and log
debug-messages.
@@ -318,27 +318,42 @@ To check the status of server start client and connect it to server:
nzbget -C
The client have three different (display) outputmodes, which you can select
in configuration file (on client computer) or in command line. Try them:
nzbget -o outputmode=log -C
nzbget -o outputmode=color -C
nzbget -o outputmode=curses -C
To list files in server's queue:
nzbget -L
It prints something like:
[1] nzbname\filename1.rar (50.00 MB)
[2] nzbname\filename1.r01 (50.00 MB)
[1] nzbname\filename1.rar (50.00 MB)
[2] nzbname\filename1.r01 (50.00 MB)
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 -I 2
nzbget -E T 2
or to pause files with IDs from 10 to 20:
nzbget -E P -I 10-20
nzbget -E P 10-20
or to delete file from queue:
or to delete files from queue:
nzbget -E D -I 3
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-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
The switch "o" is useful to override options in configuration files.
For example:

View File

@@ -38,13 +38,15 @@
#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 "NZBFile.h"
#include "Log.h"
#include "Util.h"
@@ -57,14 +59,14 @@ RemoteClient::RemoteClient()
m_bVerbose = true;
/*
printf("sizeof(SNZBMessageBase)=%i\n", sizeof(SNZBMessageBase));
printf("sizeof(SNZBRequestBase)=%i\n", sizeof(SNZBRequestBase));
printf("sizeof(SNZBDownloadRequest)=%i\n", sizeof(SNZBDownloadRequest));
printf("sizeof(SNZBListRequest)=%i\n", sizeof(SNZBListRequest));
printf("sizeof(SNZBListRequestAnswer)=%i\n", sizeof(SNZBListRequestAnswer));
printf("sizeof(SNZBListRequestAnswerEntry)=%i\n", sizeof(SNZBListRequestAnswerEntry));
printf("sizeof(SNZBListResponse)=%i\n", sizeof(SNZBListResponse));
printf("sizeof(SNZBListResponseEntry)=%i\n", sizeof(SNZBListResponseEntry));
printf("sizeof(SNZBLogRequest)=%i\n", sizeof(SNZBLogRequest));
printf("sizeof(SNZBLogRequestAnswer)=%i\n", sizeof(SNZBLogRequestAnswer));
printf("sizeof(SNZBLogRequestAnswerEntry)=%i\n", sizeof(SNZBLogRequestAnswerEntry));
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));
@@ -118,22 +120,44 @@ bool RemoteClient::InitConnection()
return OK;
}
void RemoteClient::InitMessageBase(SNZBMessageBase* pMessageBase, int iRequest, int iSize)
void RemoteClient::InitMessageBase(SNZBRequestBase* pMessageBase, int iRequest, int iSize)
{
pMessageBase->m_iId = NZBMESSAGE_SIGNATURE;
pMessageBase->m_iType = iRequest;
pMessageBase->m_iSize = 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';
}
void RemoteClient::ReceiveCommandResult()
bool RemoteClient::ReceiveBoolResponse()
{
char szAnswer[1024];
strcpy(szAnswer, "N/A");
printf("request sent\n");
m_pConnection->Recv(szAnswer, 1024);
printf("nzbserver returned: %s\n", szAnswer);
// 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);
}
/*
@@ -144,7 +168,7 @@ bool RemoteClient::RequestServerDownload(const char* szName, bool bAddFirst)
// Read the file into the buffer
char* szBuffer = NULL;
int iLength = 0;
if (!NZBFile::LoadFileIntoBuffer(szName, &szBuffer, &iLength))
if (!LoadFileIntoBuffer(szName, &szBuffer, &iLength))
{
printf("Could not load file %s\n", szName);
return false;
@@ -154,9 +178,9 @@ bool RemoteClient::RequestServerDownload(const char* szName, bool bAddFirst)
if (OK)
{
SNZBDownloadRequest DownloadRequest;
InitMessageBase(&DownloadRequest.m_MessageBase, NZBMessageRequest::eRequestDownload, sizeof(DownloadRequest));
DownloadRequest.m_bAddFirst = bAddFirst;
DownloadRequest.m_iTrailingDataLength = iLength;
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';
@@ -169,7 +193,7 @@ bool RemoteClient::RequestServerDownload(const char* szName, bool bAddFirst)
else
{
m_pConnection->Send(szBuffer, iLength);
ReceiveCommandResult();
OK = ReceiveBoolResponse();
m_pConnection->Disconnect();
}
}
@@ -188,9 +212,9 @@ bool RemoteClient::RequestServerList()
if (!InitConnection()) return false;
SNZBListRequest ListRequest;
InitMessageBase(&ListRequest.m_MessageBase, NZBMessageRequest::eRequestList, sizeof(ListRequest));
ListRequest.m_bFileList = true;
ListRequest.m_bServerState = true;
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)
{
@@ -199,17 +223,21 @@ bool RemoteClient::RequestServerList()
}
// Now listen for the returned list
SNZBListRequestAnswer ListRequestAnswer;
if (m_pConnection->Recv((char*) &ListRequestAnswer, sizeof(ListRequestAnswer)) < 0)
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 (ListRequestAnswer.m_iTrailingDataLength > 0)
if (ntohl(ListResponse.m_iTrailingDataLength) > 0)
{
pBuf = (char*)malloc(ListRequestAnswer.m_iTrailingDataLength);
if (!m_pConnection->RecvAll(pBuf, ListRequestAnswer.m_iTrailingDataLength))
pBuf = (char*)malloc(ntohl(ListResponse.m_iTrailingDataLength));
if (!m_pConnection->RecvAll(pBuf, ntohl(ListResponse.m_iTrailingDataLength)))
{
free(pBuf);
return false;
@@ -218,7 +246,7 @@ bool RemoteClient::RequestServerList()
m_pConnection->Disconnect();
if (ListRequestAnswer.m_iTrailingDataLength == 0)
if (ntohl(ListResponse.m_iTrailingDataLength) == 0)
{
printf("Server has no files queued for download\n");
}
@@ -230,43 +258,44 @@ bool RemoteClient::RequestServerList()
long long lRemaining = 0;
long long lPaused = 0;
char* pBufPtr = (char*)pBuf;
for (int i = 0; i < ListRequestAnswer.m_iNrTrailingEntries; i++)
for (unsigned int i = 0; i < ntohl(ListResponse.m_iNrTrailingEntries); i++)
{
SNZBListRequestAnswerEntry* pListAnswer = (SNZBListRequestAnswerEntry*) pBufPtr;
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 (pListAnswer->m_iRemainingSize < pListAnswer->m_iFileSize)
if (lRemainingSize < lFileSize)
{
sprintf(szCompleted, ", %i%s", (int)(100 - pListAnswer->m_iRemainingSize * 100.0 / pListAnswer->m_iFileSize), "\%");
sprintf(szCompleted, ", %i%s", (int)(100 - lRemainingSize * 100.0 / lFileSize), "%");
}
char szStatus[100];
if (pListAnswer->m_bPaused)
if (ntohl(pListAnswer->m_bPaused))
{
sprintf(szStatus, " (paused)");
lPaused += pListAnswer->m_iRemainingSize;
lPaused += lRemainingSize;
}
else
{
szStatus[0] = '\0';
lRemaining += pListAnswer->m_iRemainingSize;
lRemaining += lRemainingSize;
}
char* szNZBFilename = pBufPtr + sizeof(SNZBListRequestAnswerEntry);
char* szFilename = pBufPtr + sizeof(SNZBListRequestAnswerEntry) + pListAnswer->m_iNZBFilenameLen + pListAnswer->m_iSubjectLen;
char* szNZBFilename = pBufPtr + sizeof(SNZBListResponseEntry);
char* szFilename = pBufPtr + sizeof(SNZBListResponseEntry) + ntohl(pListAnswer->m_iNZBFilenameLen) + ntohl(pListAnswer->m_iSubjectLen);
char szNZBNiceName[1024];
strncpy(szNZBNiceName, BaseFileName(szNZBFilename), 1024);
szNZBNiceName[1024-1] = '\0';
if (char* p = strrchr(szNZBNiceName, '.')) *p = '\0';
FileInfo::MakeNiceNZBName(szNZBFilename, szNZBNiceName, 1024);
printf("[%i] %s%c%s (%.2f MB%s)%s\n", pListAnswer->m_iID, szNZBNiceName, (int)PATH_SEPARATOR, szFilename, pListAnswer->m_iFileSize / 1024.0 / 1024.0, szCompleted, szStatus);
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(SNZBListRequestAnswerEntry) + pListAnswer->m_iNZBFilenameLen + pListAnswer->m_iSubjectLen +
pListAnswer->m_iFilenameLen + pListAnswer->m_iDestDirLen;
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", ListRequestAnswer.m_iNrTrailingEntries);
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);
@@ -275,21 +304,48 @@ bool RemoteClient::RequestServerList()
{
printf("Remaining size: %.2f MB\n", lRemaining / 1024.0 / 1024.0);
}
printf("Download rate: %.1f KB/s\n", ListRequestAnswer.m_fDownloadRate);
printf("Current download rate: %.1f KB/s\n", (float)(ntohl(ListResponse.m_iDownloadRate) / 1024.0));
free(pBuf);
}
printf("Threads running: %i\n", ListRequestAnswer.m_iThreadCount);
printf("Server state: %s\n", ListRequestAnswer.m_bServerPaused ? "Paused" : "Running");
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 (ListRequestAnswer.m_fDownloadLimit > 0)
if (ntohl(ListResponse.m_iDownloadLimit) > 0)
{
printf("Speed limit: %.1f KB/s\n", ListRequestAnswer.m_fDownloadLimit);
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("Speed limit: Unlimited\n");
printf("Server state: %s\n", ntohl(ListResponse.m_bServerPaused) ? "Paused" : "Downloading");
}
return true;
@@ -300,8 +356,8 @@ bool RemoteClient::RequestServerLog(int iLines)
if (!InitConnection()) return false;
SNZBLogRequest LogRequest;
InitMessageBase(&LogRequest.m_MessageBase, NZBMessageRequest::eRequestLog, sizeof(LogRequest));
LogRequest.m_iLines = iLines;
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)
@@ -311,17 +367,21 @@ bool RemoteClient::RequestServerLog(int iLines)
}
// Now listen for the returned log
SNZBLogRequestAnswer LogRequestAnswer;
if (m_pConnection->Recv((char*) &LogRequestAnswer, sizeof(LogRequestAnswer)) < 0)
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 (LogRequestAnswer.m_iTrailingDataLength > 0)
if (ntohl(LogResponse.m_iTrailingDataLength) > 0)
{
pBuf = (char*)malloc(LogRequestAnswer.m_iTrailingDataLength);
if (!m_pConnection->RecvAll(pBuf, LogRequestAnswer.m_iTrailingDataLength))
pBuf = (char*)malloc(ntohl(LogResponse.m_iTrailingDataLength));
if (!m_pConnection->RecvAll(pBuf, ntohl(LogResponse.m_iTrailingDataLength)))
{
free(pBuf);
return false;
@@ -330,22 +390,22 @@ bool RemoteClient::RequestServerLog(int iLines)
m_pConnection->Disconnect();
if (LogRequestAnswer.m_iTrailingDataLength == 0)
if (LogResponse.m_iTrailingDataLength == 0)
{
printf("Log is empty\n");
}
else
{
printf("Log (last %i entries)\n", LogRequestAnswer.m_iNrTrailingEntries);
printf("Log (last %i entries)\n", ntohl(LogResponse.m_iNrTrailingEntries));
printf("-----------------------------------\n");
char* pBufPtr = (char*)pBuf;
for (int i = 0; i < LogRequestAnswer.m_iNrTrailingEntries; i++)
for (unsigned int i = 0; i < ntohl(LogResponse.m_iNrTrailingEntries); i++)
{
SNZBLogRequestAnswerEntry* pLogAnswer = (SNZBLogRequestAnswerEntry*) pBufPtr;
SNZBLogResponseEntry* pLogAnswer = (SNZBLogResponseEntry*) pBufPtr;
char* szText = pBufPtr + sizeof(SNZBLogRequestAnswerEntry);
switch (pLogAnswer->m_iKind)
char* szText = pBufPtr + sizeof(SNZBLogResponseEntry);
switch (ntohl(pLogAnswer->m_iKind))
{
case Message::mkDebug:
printf("[DEBUG] %s\n", szText);
@@ -361,7 +421,7 @@ bool RemoteClient::RequestServerLog(int iLines)
break;
}
pBufPtr += sizeof(SNZBLogRequestAnswerEntry) + pLogAnswer->m_iTextLen;
pBufPtr += sizeof(SNZBLogResponseEntry) + ntohl(pLogAnswer->m_iTextLen);
}
printf("-----------------------------------\n");
@@ -377,8 +437,8 @@ bool RemoteClient::RequestServerPauseUnpause(bool bPause)
if (!InitConnection()) return false;
SNZBPauseUnpauseRequest PauseUnpauseRequest;
InitMessageBase(&PauseUnpauseRequest.m_MessageBase, NZBMessageRequest::eRequestPauseUnpause, sizeof(PauseUnpauseRequest));
PauseUnpauseRequest.m_bPause = bPause;
InitMessageBase(&PauseUnpauseRequest.m_MessageBase, eRemoteRequestPauseUnpause, sizeof(PauseUnpauseRequest));
PauseUnpauseRequest.m_bPause = htonl(bPause);
if (m_pConnection->Send((char*)(&PauseUnpauseRequest), sizeof(PauseUnpauseRequest)) < 0)
{
@@ -387,10 +447,10 @@ bool RemoteClient::RequestServerPauseUnpause(bool bPause)
return false;
}
ReceiveCommandResult();
bool OK = ReceiveBoolResponse();
m_pConnection->Disconnect();
return true;
return OK;
}
bool RemoteClient::RequestServerSetDownloadRate(float fRate)
@@ -398,8 +458,8 @@ bool RemoteClient::RequestServerSetDownloadRate(float fRate)
if (!InitConnection()) return false;
SNZBSetDownloadRateRequest SetDownloadRateRequest;
InitMessageBase(&SetDownloadRateRequest.m_MessageBase, NZBMessageRequest::eRequestSetDownloadRate, sizeof(SetDownloadRateRequest));
SetDownloadRateRequest.m_fDownloadRate = fRate;
InitMessageBase(&SetDownloadRateRequest.m_MessageBase, eRemoteRequestSetDownloadRate, sizeof(SetDownloadRateRequest));
SetDownloadRateRequest.m_iDownloadRate = htonl((unsigned int)(fRate * 1024));
if (m_pConnection->Send((char*)(&SetDownloadRateRequest), sizeof(SetDownloadRateRequest)) < 0)
{
@@ -408,10 +468,10 @@ bool RemoteClient::RequestServerSetDownloadRate(float fRate)
return false;
}
ReceiveCommandResult();
bool OK = ReceiveBoolResponse();
m_pConnection->Disconnect();
return true;
return OK;
}
bool RemoteClient::RequestServerDumpDebug()
@@ -419,8 +479,7 @@ bool RemoteClient::RequestServerDumpDebug()
if (!InitConnection()) return false;
SNZBDumpDebugRequest DumpDebugInfo;
InitMessageBase(&DumpDebugInfo.m_MessageBase, NZBMessageRequest::eRequestDumpDebug, sizeof(DumpDebugInfo));
DumpDebugInfo.m_iLevel = 0;
InitMessageBase(&DumpDebugInfo.m_MessageBase, eRemoteRequestDumpDebug, sizeof(DumpDebugInfo));
if (m_pConnection->Send((char*)(&DumpDebugInfo), sizeof(DumpDebugInfo)) < 0)
{
@@ -429,38 +488,52 @@ bool RemoteClient::RequestServerDumpDebug()
return false;
}
ReceiveCommandResult();
bool OK = ReceiveBoolResponse();
m_pConnection->Disconnect();
return true;
return OK;
}
bool RemoteClient::RequestServerEditQueue(int iAction, int iOffset, int iIDFrom, int iIDTo)
bool RemoteClient::RequestServerEditQueue(int iAction, int iOffset, int* pIDList, int iIDCount, bool bSmartOrder)
{
if (iIDTo <= 0)
if (iIDCount <= 0 || pIDList == NULL)
{
printf("File(s) not specified (use option -I)\n");
printf("File(s) not specified\n");
return false;
}
if (!InitConnection()) return false;
SNZBEditQueueRequest EditQueueRequest;
InitMessageBase(&EditQueueRequest.m_MessageBase, NZBMessageRequest::eRequestEditQueue, sizeof(EditQueueRequest));
EditQueueRequest.m_iAction = iAction;
EditQueueRequest.m_iOffset = iOffset;
EditQueueRequest.m_iIDFrom = iIDFrom;
EditQueueRequest.m_iIDTo = iIDTo;
int iLength = sizeof(int32_t) * iIDCount;
bool OK = m_pConnection->Send((char*)(&EditQueueRequest), sizeof(EditQueueRequest)) >= 0;
if (OK)
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++)
{
ReceiveCommandResult();
pIDs[i] = htonl(pIDList[i]);
}
else
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;
@@ -470,14 +543,34 @@ bool RemoteClient::RequestServerShutdown()
{
if (!InitConnection()) return false;
SNZBMessageBase QuitRequest;
SNZBShutdownRequest ShutdownRequest;
InitMessageBase(&ShutdownRequest.m_MessageBase, eRemoteRequestShutdown, sizeof(ShutdownRequest));
InitMessageBase(&QuitRequest, NZBMessageRequest::eRequestShutdown, sizeof(QuitRequest));
bool OK = m_pConnection->Send((char*)(&QuitRequest), sizeof(QuitRequest)) >= 0;
bool OK = m_pConnection->Send((char*)(&ShutdownRequest), sizeof(ShutdownRequest)) >= 0;
if (OK)
{
ReceiveCommandResult();
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
{

View File

@@ -39,8 +39,8 @@ private:
bool m_bVerbose;
bool InitConnection();
void InitMessageBase(SNZBMessageBase* pMessageBase, int iRequest, int iSize);
void ReceiveCommandResult();
void InitMessageBase(SNZBRequestBase* pMessageBase, int iRequest, int iSize);
bool ReceiveBoolResponse();
void printf(char* msg, ...);
void perror(char* msg);
@@ -53,9 +53,10 @@ public:
bool RequestServerPauseUnpause(bool bPause);
bool RequestServerSetDownloadRate(float fRate);
bool RequestServerDumpDebug();
bool RequestServerEditQueue(int iAction, int iOffset, int iIDFrom, int iIDTo);
bool RequestServerEditQueue(int iAction, int iOffset, int* pIDList, int iIDCount, bool bSmartOrder);
bool RequestServerLog(int iLines);
bool RequestServerShutdown();
bool RequestServerVersion();
};
#endif

View File

@@ -46,17 +46,32 @@
#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_szMessageRequests[] =
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
@@ -65,49 +80,64 @@ RemoteServer::RemoteServer()
debug("Creating RemoteServer");
m_pNetAddress = new NetAddress(g_pOptions->GetServerIP(), g_pOptions->GetServerPort());
m_pConnection = new Connection(m_pNetAddress);
m_pConnection->SetTimeout(g_pOptions->GetConnectionTimeout());
m_pConnection = NULL;
}
RemoteServer::~RemoteServer()
{
debug("Destroying RemoteServer");
if (m_pConnection)
{
delete m_pConnection;
}
delete m_pNetAddress;
delete m_pConnection;
}
void RemoteServer::Run()
{
debug("Entering RemoteServer-loop");
m_pConnection->Bind();
while (!IsStopped())
{
// Accept connections and store the "new" socket value
SOCKET iSocket = m_pConnection->Accept();
if (iSocket == INVALID_SOCKET)
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;
}
// error binding on port. wait 0.5 sec. and retry
usleep(500 * 1000);
delete m_pConnection;
m_pConnection = new Connection(m_pNetAddress);
m_pConnection->SetTimeout(g_pOptions->GetConnectionTimeout());
m_pConnection->Bind();
m_pConnection = NULL;
continue;
}
MessageCommand* commandThread = new MessageCommand();
RequestProcessor* commandThread = new RequestProcessor();
commandThread->SetAutoDestroy(true);
commandThread->SetSocket(iSocket);
commandThread->Start();
}
m_pConnection->Disconnect();
if (m_pConnection)
{
m_pConnection->Disconnect();
}
debug("Exiting RemoteServer-loop");
}
@@ -115,97 +145,267 @@ void RemoteServer::Run()
void RemoteServer::Stop()
{
Thread::Stop();
m_pConnection->Cancel();
if (m_pConnection)
{
m_pConnection->SetSuppressErrors(true);
m_pConnection->Cancel();
#ifdef WIN32
m_pConnection->Disconnect();
m_pConnection->Disconnect();
#endif
}
}
//*****************************************************************
// MessageCommand
// RequestProcessor
void MessageCommand::SendResponse(char* szAnswer)
void RequestProcessor::Run()
{
send(m_iSocket, szAnswer, strlen(szAnswer), 0);
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 MessageCommand::ProcessRequest()
void RequestProcessor::Dispatch()
{
SNZBMessageBase* pMessageBase = (SNZBMessageBase*) & m_RequestBuffer;
switch (pMessageBase->m_iType)
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))
{
case NZBMessageRequest::eRequestDownload:
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:
{
RequestDownload();
command = new DownloadCommand();
break;
}
case NZBMessageRequest::eRequestList:
case eRemoteRequestList:
{
RequestList();
command = new ListCommand();
break;
}
case NZBMessageRequest::eRequestLog:
case eRemoteRequestLog:
{
RequestLog();
command = new LogCommand();
break;
}
case NZBMessageRequest::eRequestPauseUnpause:
case eRemoteRequestPauseUnpause:
{
SNZBPauseUnpauseRequest* pPauseUnpauseRequest = (SNZBPauseUnpauseRequest*) & m_RequestBuffer;
g_pOptions->SetPause(pPauseUnpauseRequest->m_bPause);
SendResponse("Pause-/Unpause-Command completed successfully");
command = new PauseUnpauseCommand();
break;
}
case NZBMessageRequest::eRequestEditQueue:
case eRemoteRequestEditQueue:
{
RequestEditQueue();
command = new EditQueueCommand();
break;
}
case NZBMessageRequest::eRequestSetDownloadRate:
case eRemoteRequestSetDownloadRate:
{
SNZBSetDownloadRateRequest* pSetDownloadRequest = (SNZBSetDownloadRateRequest*) & m_RequestBuffer;
g_pOptions->SetDownloadRate(pSetDownloadRequest->m_fDownloadRate);
SendResponse("Rate-Command completed successfully");
command = new SetDownloadRateCommand();
break;
}
case NZBMessageRequest::eRequestDumpDebug:
case eRemoteRequestDumpDebug:
{
g_pQueueCoordinator->LogDebugInfo();
SendResponse("Debug-Command completed successfully");
command = new DumpDebugCommand();
break;
}
case NZBMessageRequest::eRequestShutdown:
case eRemoteRequestShutdown:
{
SendResponse("Stopping server");
ExitProc();
command = new ShutdownCommand();
break;
}
case eRemoteRequestVersion:
{
command = new VersionCommand();
break;
}
default:
error("NZB-Request not yet supported");
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;
}
}
void MessageCommand::RequestDownload()
//*****************************************************************
// Commands
void MessageCommand::SendBoolResponse(bool bSuccess, const char* szText)
{
SNZBDownloadRequest* pDownloadRequest = (SNZBDownloadRequest*) & m_RequestBuffer;
const char* pExtraData = (m_iExtraDataLength > 0) ? ((char*)pDownloadRequest + pDownloadRequest->m_MessageBase.m_iSize) : NULL;
int NeedBytes = pDownloadRequest->m_iTrailingDataLength - m_iExtraDataLength;
char* pRecvBuffer = (char*)malloc(pDownloadRequest->m_iTrailingDataLength + 1);
memcpy(pRecvBuffer, pExtraData, m_iExtraDataLength);
char* pBufPtr = pRecvBuffer + m_iExtraDataLength;
// 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);
@@ -221,44 +421,49 @@ void MessageCommand::RequestDownload()
if (NeedBytes == 0)
{
NZBFile* pNZBFile = NZBFile::CreateFromBuffer(pDownloadRequest->m_szFilename, pRecvBuffer, pDownloadRequest->m_iTrailingDataLength);
NZBFile* pNZBFile = NZBFile::CreateFromBuffer(DownloadRequest.m_szFilename, pRecvBuffer, ntohl(DownloadRequest.m_iTrailingDataLength));
if (pNZBFile)
{
info("Request: Queue collection %s", pDownloadRequest->m_szFilename);
g_pQueueCoordinator->AddNZBFileToQueue(pNZBFile, pDownloadRequest->m_bAddFirst);
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(pDownloadRequest->m_szFilename));
snprintf(tmp, 1024, "Collection %s added to queue", BaseFileName(DownloadRequest.m_szFilename));
tmp[1024-1] = '\0';
SendResponse(tmp);
SendBoolResponse(true, tmp);
}
else
{
char tmp[1024];
snprintf(tmp, 1024, "Download Request failed for %s", BaseFileName(pDownloadRequest->m_szFilename));
snprintf(tmp, 1024, "Download Request failed for %s", BaseFileName(DownloadRequest.m_szFilename));
tmp[1024-1] = '\0';
SendResponse(tmp);
SendBoolResponse(false, tmp);
}
}
free(pRecvBuffer);
}
void MessageCommand::RequestList()
void ListCommand::Execute()
{
SNZBListRequest* pListRequest = (SNZBListRequest*) & m_RequestBuffer;
SNZBListRequest ListRequest;
if (!ReceiveRequest(&ListRequest, sizeof(ListRequest)))
{
return;
}
SNZBListRequestAnswer ListRequestAnswer;
memset(&ListRequestAnswer, 0, sizeof(ListRequestAnswer));
ListRequestAnswer.m_iSize = sizeof(ListRequestAnswer);
ListRequestAnswer.m_iEntrySize = sizeof(SNZBListRequestAnswerEntry);
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 (pListRequest->m_bFileList)
if (ntohl(ListRequest.m_bFileList))
{
// Make a data structure and copy all the elements of the list into it
DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue();
@@ -266,7 +471,7 @@ void MessageCommand::RequestList()
int NrEntries = pDownloadQueue->size();
// calculate required buffer size
bufsize = NrEntries * sizeof(SNZBListRequestAnswerEntry);
bufsize = NrEntries * sizeof(SNZBListResponseEntry);
for (DownloadQueue::iterator it = pDownloadQueue->begin(); it != pDownloadQueue->end(); it++)
{
FileInfo* pFileInfo = *it;
@@ -280,45 +485,67 @@ void MessageCommand::RequestList()
char* bufptr = buf;
for (DownloadQueue::iterator it = pDownloadQueue->begin(); it != pDownloadQueue->end(); it++)
{
unsigned int iSizeHi, iSizeLo;
FileInfo* pFileInfo = *it;
SNZBListRequestAnswerEntry* pListAnswer = (SNZBListRequestAnswerEntry*) bufptr;
pListAnswer->m_iID = pFileInfo->GetID();
pListAnswer->m_iFileSize = (int)pFileInfo->GetSize();
pListAnswer->m_bFilenameConfirmed = pFileInfo->GetFilenameConfirmed();
pListAnswer->m_iRemainingSize = (int)pFileInfo->GetRemainingSize();
pListAnswer->m_bPaused = pFileInfo->GetPaused();
pListAnswer->m_iNZBFilenameLen = strlen(pFileInfo->GetNZBFilename()) + 1;
pListAnswer->m_iSubjectLen = strlen(pFileInfo->GetSubject()) + 1;
pListAnswer->m_iFilenameLen = strlen(pFileInfo->GetFilename()) + 1;
pListAnswer->m_iDestDirLen = strlen(pFileInfo->GetDestDir()) + 1;
bufptr += sizeof(SNZBListRequestAnswerEntry);
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 += pListAnswer->m_iNZBFilenameLen;
bufptr += ntohl(pListAnswer->m_iNZBFilenameLen);
strcpy(bufptr, pFileInfo->GetSubject());
bufptr += pListAnswer->m_iSubjectLen;
bufptr += ntohl(pListAnswer->m_iSubjectLen);
strcpy(bufptr, pFileInfo->GetFilename());
bufptr += pListAnswer->m_iFilenameLen;
bufptr += ntohl(pListAnswer->m_iFilenameLen);
strcpy(bufptr, pFileInfo->GetDestDir());
bufptr += pListAnswer->m_iDestDirLen;
bufptr += ntohl(pListAnswer->m_iDestDirLen);
}
g_pQueueCoordinator->UnlockQueue();
ListRequestAnswer.m_iNrTrailingEntries = NrEntries;
ListRequestAnswer.m_iTrailingDataLength = bufsize;
ListResponse.m_iNrTrailingEntries = htonl(NrEntries);
ListResponse.m_iTrailingDataLength = htonl(bufsize);
}
if (pListRequest->m_bServerState)
if (htonl(ListRequest.m_bServerState))
{
ListRequestAnswer.m_fDownloadRate = g_pQueueCoordinator->CalcCurrentDownloadSpeed();
ListRequestAnswer.m_lRemainingSize = g_pQueueCoordinator->CalcRemainingSize();
ListRequestAnswer.m_fDownloadLimit = g_pOptions->GetDownloadRate();
ListRequestAnswer.m_bServerPaused = g_pOptions->GetPause();
ListRequestAnswer.m_iThreadCount = Thread::GetThreadCount() - 1; // not counting itself
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*) &ListRequestAnswer, sizeof(ListRequestAnswer), 0);
send(m_iSocket, (char*) &ListResponse, sizeof(ListResponse), 0);
// Send the data
if (bufsize > 0)
@@ -332,14 +559,18 @@ void MessageCommand::RequestList()
}
}
void MessageCommand::RequestLog()
void LogCommand::Execute()
{
SNZBLogRequest* pLogRequest = (SNZBLogRequest*) & m_RequestBuffer;
SNZBLogRequest LogRequest;
if (!ReceiveRequest(&LogRequest, sizeof(LogRequest)))
{
return;
}
Log::Messages* pMessages = g_pLog->LockMessages();
int iNrEntries = pLogRequest->m_iLines;
unsigned int iIDFrom = pLogRequest->m_iIDFrom;
int iNrEntries = ntohl(LogRequest.m_iLines);
unsigned int iIDFrom = ntohl(LogRequest.m_iIDFrom);
int iStart = pMessages->size();
if (iNrEntries > 0)
{
@@ -364,7 +595,7 @@ void MessageCommand::RequestLog()
}
// calculate required buffer size
int bufsize = iNrEntries * sizeof(SNZBLogRequestAnswerEntry);
int bufsize = iNrEntries * sizeof(SNZBLogResponseEntry);
for (unsigned int i = (unsigned int)iStart; i < pMessages->size(); i++)
{
Message* pMessage = (*pMessages)[i];
@@ -376,26 +607,27 @@ void MessageCommand::RequestLog()
for (unsigned int i = (unsigned int)iStart; i < pMessages->size(); i++)
{
Message* pMessage = (*pMessages)[i];
SNZBLogRequestAnswerEntry* pLogAnswer = (SNZBLogRequestAnswerEntry*) bufptr;
pLogAnswer->m_iID = pMessage->GetID();
pLogAnswer->m_iKind = pMessage->GetKind();
pLogAnswer->m_tTime = pMessage->GetTime();
pLogAnswer->m_iTextLen = strlen(pMessage->GetText()) + 1;
bufptr += sizeof(SNZBLogRequestAnswerEntry);
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 += pLogAnswer->m_iTextLen;
bufptr += ntohl(pLogAnswer->m_iTextLen);
}
g_pLog->UnlockMessages();
SNZBLogRequestAnswer LogRequestAnswer;
LogRequestAnswer.m_iSize = sizeof(LogRequestAnswer);
LogRequestAnswer.m_iEntrySize = sizeof(SNZBLogRequestAnswerEntry);
LogRequestAnswer.m_iNrTrailingEntries = iNrEntries;
LogRequestAnswer.m_iTrailingDataLength = bufsize;
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*) &LogRequestAnswer, sizeof(LogRequestAnswer), 0);
send(m_iSocket, (char*) &LogResponse, sizeof(LogResponse), 0);
// Send the data
if (bufsize > 0)
@@ -406,122 +638,68 @@ void MessageCommand::RequestLog()
free(buf);
}
void MessageCommand::RequestEditQueue()
void EditQueueCommand::Execute()
{
SNZBEditQueueRequest* pEditQueueRequest = (SNZBEditQueueRequest*) & m_RequestBuffer;
int From = pEditQueueRequest->m_iIDFrom;
int To = pEditQueueRequest->m_iIDTo;
int Step = 1;
if ((pEditQueueRequest->m_iAction == NZBMessageRequest::eActionMoveTop) ||
((pEditQueueRequest->m_iAction == NZBMessageRequest::eActionMoveOffset) &&
(pEditQueueRequest->m_iOffset < 0)))
{
Step = -1;
int tmp = From; From = To; To = tmp;
}
for (int ID = From; ID != To + Step; ID += Step)
{
switch (pEditQueueRequest->m_iAction)
{
case NZBMessageRequest::eActionPause:
case NZBMessageRequest::eActionResume:
{
g_pQueueCoordinator->EditQueuePauseUnpauseEntry(ID, pEditQueueRequest->m_iAction == NZBMessageRequest::eActionPause);
break;
}
case NZBMessageRequest::eActionMoveOffset:
{
g_pQueueCoordinator->EditQueueMoveEntry(ID, pEditQueueRequest->m_iOffset, true);
break;
}
case NZBMessageRequest::eActionMoveTop:
{
g_pQueueCoordinator->EditQueueMoveEntry(ID, -1000000, true);
break;
}
case NZBMessageRequest::eActionMoveBottom:
{
g_pQueueCoordinator->EditQueueMoveEntry(ID, + 1000000, true);
break;
}
case NZBMessageRequest::eActionDelete:
{
g_pQueueCoordinator->EditQueueDeleteEntry(ID);
break;
}
}
}
SendResponse("Edit-Command completed successfully");
}
void MessageCommand::SetSocket(SOCKET iSocket)
{
m_iSocket = iSocket;
}
void MessageCommand::Run()
{
int iRequestReceived = 0;
// Read the first package which needs to be a request
iRequestReceived = recv(m_iSocket, (char*) & m_RequestBuffer, sizeof(m_RequestBuffer), 0);
if (iRequestReceived < 0)
SNZBEditQueueRequest EditQueueRequest;
if (!ReceiveRequest(&EditQueueRequest, sizeof(EditQueueRequest)))
{
return;
}
SNZBMessageBase* pMessageBase = (SNZBMessageBase*) & m_RequestBuffer;
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);
// Make sure this is a nzbget request from a client
if (pMessageBase->m_iId != NZBMESSAGE_SIGNATURE)
if (iNrEntries * sizeof(int32_t) != iBufLength)
{
warn("Non-nzbget request received on port %i", g_pOptions->GetServerPort());
if (m_iSocket > -1)
{
closesocket(m_iSocket);
}
error("Invalid struct size");
return;
}
if (strcmp(pMessageBase->m_szPassword, g_pOptions->GetServerPassword()))
if (iNrEntries <= 0)
{
warn("nzbget request received on port %i, but password invalid", g_pOptions->GetServerPort());
if (m_iSocket > -1)
{
closesocket(m_iSocket);
}
SendBoolResponse(false, "Edit-Command failed: no IDs specified");
return;
}
// Info - connection received
struct sockaddr_in PeerName;
int iPeerNameLength = sizeof(PeerName);
if (getpeername(m_iSocket, (struct sockaddr*)&PeerName, (socklen_t*) &iPeerNameLength) >= 0)
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)
{
#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_szMessageRequests[pMessageBase->m_iType], ip);
iResult = recv(m_iSocket, pBufPtr, NeedBytes, 0);
// Did the recv succeed?
if (iResult <= 0)
{
error("invalid request");
break;
}
pBufPtr += iResult;
NeedBytes -= iResult;
}
m_iExtraDataLength = iRequestReceived - pMessageBase->m_iSize;
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);
ProcessRequest();
free(pIDs);
// Close the socket
closesocket(m_iSocket);
if (bOK)
{
SendBoolResponse(true, "Edit-Command completed successfully");
}
else
{
SendBoolResponse(false, "Edit-Command failed");
}
}

View File

@@ -27,13 +27,13 @@
#ifndef REMOTESERVER_H
#define REMOTESERVER_H
#include <list>
#include "Thread.h"
#include "NetAddress.h"
#include "Connection.h"
#include "MessageBase.h"
static int const REQUESTBUFFERSIZE = 8192;
class RemoteServer : public Thread
{
private:
@@ -47,23 +47,87 @@ public:
virtual void Stop();
};
class MessageCommand : public Thread
class RequestProcessor : public Thread
{
private:
SOCKET m_iSocket;
char m_RequestBuffer[REQUESTBUFFERSIZE];
int m_iExtraDataLength;
SNZBRequestBase m_MessageBase;
void ProcessRequest();
void RequestDownload();
void RequestList();
void RequestLog();
void RequestEditQueue();
void SendResponse(char* szAnswer);
void Dispatch();
public:
void SetSocket(SOCKET iSocket);
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

View File

@@ -51,6 +51,14 @@
#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");
@@ -58,7 +66,7 @@ ServerPool::ServerPool()
m_iMaxLevel = 0;
m_iTimeout = 60;
m_Servers.clear();
m_FreeConnections.clear();
m_Connections.clear();
m_Semaphores.clear();
}
@@ -66,8 +74,6 @@ ServerPool::~ ServerPool()
{
debug("Destroying ServerPool");
m_FreeConnections.clear();
for (Semaphores::iterator it = m_Semaphores.begin(); it != m_Semaphores.end(); it++)
{
delete *it;
@@ -79,6 +85,12 @@ ServerPool::~ ServerPool()
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)
@@ -102,7 +114,9 @@ void ServerPool::InitConnections()
}
for (int i = 0; i < pNewsServer->GetMaxConnections(); i++)
{
m_FreeConnections.push_back(pNewsServer);
PooledConnection* pConnection = new PooledConnection(pNewsServer);
pConnection->SetTimeout(m_iTimeout);
m_Connections.push_back(pConnection);
}
}
@@ -123,38 +137,41 @@ void ServerPool::InitConnections()
}
}
NNTPConnection* ServerPool::GetConnection(int level)
NNTPConnection* ServerPool::GetConnection(int iLevel, bool bWait)
{
debug("Getting connection");
debug("sem_wait...");
// decrease m_Semaphore counter or block
bool bWaitVal = m_Semaphores[level]->Wait();
debug("sem_wait...OK");
bool bWaitVal = false;
if (bWait)
{
debug("Getting connection (wait)");
bWaitVal = m_Semaphores[iLevel]->Wait();
}
else
{
bWaitVal = m_Semaphores[iLevel]->TryWait();
}
if (!bWaitVal)
{
debug("semaphore error: %i", errno);
// signal received or wait timeout
return NULL;
}
m_mutexFree.Lock();
m_mutexConnections.Lock();
NNTPConnection* pConnection = NULL;
for (Servers::iterator it = m_FreeConnections.begin(); it != m_FreeConnections.end(); it++)
PooledConnection* pConnection = NULL;
for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++)
{
NewsServer* server = *it;
if (server->GetLevel() == level)
PooledConnection* pConnection1 = *it;
if (!pConnection1->GetInUse() && pConnection1->GetNewsServer()->GetLevel() == iLevel)
{
// free connection found, take it!
pConnection = new NNTPConnection(server);
pConnection->SetTimeout(m_iTimeout);
m_FreeConnections.erase(it);
pConnection = pConnection1;
pConnection->SetInUse(true);
break;
}
}
m_mutexFree.Unlock();
m_mutexConnections.Unlock();
if (!pConnection)
{
@@ -164,22 +181,46 @@ NNTPConnection* ServerPool::GetConnection(int level)
return pConnection;
}
void ServerPool::FreeConnection(NNTPConnection* pConnection)
void ServerPool::FreeConnection(NNTPConnection* pConnection, bool bUsed)
{
debug("Freeing connection");
if (bUsed)
{
debug("Freeing used connection");
}
// give back free connection
m_mutexFree.Lock();
m_FreeConnections.push_back(pConnection->GetNewsServer());
m_mutexConnections.Lock();
((PooledConnection*)pConnection)->SetInUse(false);
if (bUsed)
{
((PooledConnection*)pConnection)->SetFreeTimeNow();
}
m_Semaphores[pConnection->GetNewsServer()->GetLevel()]->Post();
m_mutexFree.Unlock();
delete pConnection;
m_mutexConnections.Unlock();
}
bool ServerPool::HasFreeConnection()
void ServerPool::CloseUnusedConnections()
{
return !m_Semaphores[0]->IsLocked();
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()
@@ -189,12 +230,12 @@ void ServerPool::LogDebugInfo()
debug(" Max-Level: %i", m_iMaxLevel);
m_mutexFree.Lock();
m_mutexConnections.Lock();
debug(" Free Connections: %i", m_FreeConnections.size());
for (Servers::iterator it = m_FreeConnections.begin(); it != m_FreeConnections.end(); it++)
debug(" Connections: %i", m_Connections.size());
for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++)
{
debug(" Free Connection: level=%i", (*it)->GetLevel());
debug(" Connection: Level=%i, InUse:%i", (*it)->GetNewsServer()->GetLevel(), (int)(*it)->GetInUse());
}
/*
debug(" Semaphores: %i", m_Semaphores.size());
@@ -206,5 +247,5 @@ void ServerPool::LogDebugInfo()
debug(" Semaphore: level=%i, value=%i", iLevel, iSemValue);
}
*/
m_mutexFree.Unlock();
m_mutexConnections.Unlock();
}

View File

@@ -28,7 +28,8 @@
#ifndef SERVERPOOL_H
#define SERVERPOOL_H
#include <list>
#include <vector>
#include <time.h>
#include "Thread.h"
#include "NewsServer.h"
@@ -37,14 +38,28 @@
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;
Servers m_FreeConnections;
Connections m_Connections;
Semaphores m_Semaphores;
int m_iMaxLevel;
Mutex m_mutexFree;
Mutex m_mutexConnections;
int m_iTimeout;
public:
@@ -54,9 +69,9 @@ public:
void AddServer(NewsServer *s);
void InitConnections();
int GetMaxLevel() { return m_iMaxLevel; }
NNTPConnection* GetConnection(int level);
bool HasFreeConnection();
void FreeConnection(NNTPConnection* con);
NNTPConnection* GetConnection(int iLevel, bool bWait);
void FreeConnection(NNTPConnection* pConnection, bool bUsed);
void CloseUnusedConnections();
void LogDebugInfo();
};

View File

@@ -35,6 +35,10 @@
#include <stdlib.h>
#include <stdio.h>
#ifdef WIN32
#include <process.h>
#endif
#include "Log.h"
#include "Thread.h"
@@ -63,6 +67,14 @@ 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
@@ -123,6 +135,15 @@ bool Semaphore::Wait()
#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
@@ -195,8 +216,7 @@ void Thread::Start()
m_mutexThread.Lock();
#ifdef WIN32
DWORD ThreadId;
m_Thread = CreateThread(NULL, 0, Thread::thread_handler, (void *) this, 0, &ThreadId);
m_Thread = (HANDLE)_beginthread(Thread::thread_handler, 0, (void *)this);
m_bRunning = m_Thread != NULL;
#else
pthread_attr_t m_Attr;
@@ -238,7 +258,7 @@ bool Thread::Kill()
}
#ifdef WIN32
DWORD WINAPI Thread::thread_handler(void* pObject)
void __cdecl Thread::thread_handler(void* pObject)
#else
void* Thread::thread_handler(void* pObject)
#endif
@@ -267,7 +287,9 @@ void* Thread::thread_handler(void* pObject)
m_iThreadCount--;
m_mutexThread.Unlock();
#ifndef WIN32
return NULL;
#endif
}
int Thread::GetThreadCount()

View File

@@ -64,6 +64,7 @@ public:
~Semaphore();
void Post();
bool Wait();
bool TryWait();
bool TimedWait(int iMSec);
bool IsLocked();
};
@@ -84,7 +85,7 @@ private:
bool m_bAutoDestroy;
#ifdef WIN32
static DWORD WINAPI thread_handler(void* pObject);
static void __cdecl thread_handler(void* pObject);
#else
static void *thread_handler(void* pObject);
#endif

205
Util.cpp
View File

@@ -27,13 +27,19 @@
#include <config.h>
#endif
#include <string.h>
#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"
@@ -109,7 +115,10 @@ int getopt(int argc, char *argv[], char *optstring)
char *cp = strchr(optstring, c);
if (cp == NULL || c == ':')
{
fprintf(stderr, "Invalid option %c", c);
return '?';
}
cp++;
if (*cp == ':')
@@ -126,6 +135,7 @@ int getopt(int argc, char *argv[], char *optstring)
}
else
{
fprintf(stderr, "Option %c needs an argument", c);
return '?';
}
}
@@ -207,9 +217,9 @@ const char* DirBrowser::Next()
#endif
void NormalizePathSeparators(char* Path)
void NormalizePathSeparators(char* szPath)
{
for (char* p = Path; *p; p++)
for (char* p = szPath; *p; p++)
{
if (*p == ALT_PATH_SEPARATOR)
{
@@ -217,3 +227,190 @@ void NormalizePathSeparators(char* Path)
}
}
}
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
}

13
Util.h
View File

@@ -29,6 +29,7 @@
#ifdef WIN32
#include <stdio.h>
#include <io.h>
#include <sys/timeb.h>
#else
#include <dirent.h>
#endif
@@ -58,7 +59,17 @@ public:
};
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);
void NormalizePathSeparators(char* Path);
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

174
aclocal.m4 vendored
View File

@@ -1,7 +1,7 @@
# generated automatically by aclocal 1.9.5 -*- Autoconf -*-
# generated automatically by aclocal 1.10 -*- Autoconf -*-
# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
# 2005 Free Software Foundation, Inc.
# 2005, 2006 Free Software Foundation, Inc.
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
@@ -11,7 +11,12 @@
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE.
# Copyright (C) 2002, 2003, 2005 Free Software Foundation, Inc.
m4_if(m4_PACKAGE_VERSION, [2.61],,
[m4_fatal([this file was generated for autoconf 2.61.
You have another version of autoconf. If you want to use that,
you should regenerate the build system entirely.], [63])])
# Copyright (C) 2002, 2003, 2005, 2006 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -21,14 +26,29 @@
# ----------------------------
# Automake X.Y traces this macro to ensure aclocal.m4 has been
# generated from the m4 files accompanying Automake X.Y.
AC_DEFUN([AM_AUTOMAKE_VERSION], [am__api_version="1.9"])
# (This private macro should not be called outside this file.)
AC_DEFUN([AM_AUTOMAKE_VERSION],
[am__api_version='1.10'
dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to
dnl require some minimum version. Point them to the right macro.
m4_if([$1], [1.10], [],
[AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl
])
# _AM_AUTOCONF_VERSION(VERSION)
# -----------------------------
# aclocal traces this macro to find the Autoconf version.
# This is a private macro too. Using m4_define simplifies
# the logic in aclocal, which can simply ignore this definition.
m4_define([_AM_AUTOCONF_VERSION], [])
# AM_SET_CURRENT_AUTOMAKE_VERSION
# -------------------------------
# Call AM_AUTOMAKE_VERSION so it can be traced.
# Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced.
# This function is AC_REQUIREd by AC_INIT_AUTOMAKE.
AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION],
[AM_AUTOMAKE_VERSION([1.9.5])])
[AM_AUTOMAKE_VERSION([1.10])dnl
_AM_AUTOCONF_VERSION(m4_PACKAGE_VERSION)])
# AM_AUX_DIR_EXPAND -*- Autoconf -*-
@@ -85,14 +105,14 @@ am_aux_dir=`cd $ac_aux_dir && pwd`
# AM_CONDITIONAL -*- Autoconf -*-
# Copyright (C) 1997, 2000, 2001, 2003, 2004, 2005
# Copyright (C) 1997, 2000, 2001, 2003, 2004, 2005, 2006
# Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# serial 7
# serial 8
# AM_CONDITIONAL(NAME, SHELL-CONDITION)
# -------------------------------------
@@ -101,8 +121,10 @@ AC_DEFUN([AM_CONDITIONAL],
[AC_PREREQ(2.52)dnl
ifelse([$1], [TRUE], [AC_FATAL([$0: invalid condition: $1])],
[$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl
AC_SUBST([$1_TRUE])
AC_SUBST([$1_FALSE])
AC_SUBST([$1_TRUE])dnl
AC_SUBST([$1_FALSE])dnl
_AM_SUBST_NOTMAKE([$1_TRUE])dnl
_AM_SUBST_NOTMAKE([$1_FALSE])dnl
if $2; then
$1_TRUE=
$1_FALSE='#'
@@ -116,15 +138,14 @@ AC_CONFIG_COMMANDS_PRE(
Usually this means the macro was only invoked conditionally.]])
fi])])
# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005
# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006
# Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# serial 8
# serial 9
# There are a few dirty hacks below to avoid letting `AC_PROG_CC' be
# written in clear, in which case automake, when reading aclocal.m4,
@@ -152,6 +173,7 @@ AC_REQUIRE([AM_DEP_TRACK])dnl
ifelse([$1], CC, [depcc="$CC" am_compiler_list=],
[$1], CXX, [depcc="$CXX" am_compiler_list=],
[$1], OBJC, [depcc="$OBJC" am_compiler_list='gcc3 gcc'],
[$1], UPC, [depcc="$UPC" am_compiler_list=],
[$1], GCJ, [depcc="$GCJ" am_compiler_list='gcc3 gcc'],
[depcc="$$1" am_compiler_list=])
@@ -217,6 +239,7 @@ AC_CACHE_CHECK([dependency style of $depcc],
depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \
$SHELL ./depcomp $depcc -c -o sub/conftest.${OBJEXT-o} sub/conftest.c \
>/dev/null 2>conftest.err &&
grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 &&
grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 &&
grep sub/conftest.${OBJEXT-o} sub/conftest.Po > /dev/null 2>&1 &&
${MAKE-make} -s -f confmf > /dev/null 2>&1; then
@@ -269,7 +292,8 @@ if test "x$enable_dependency_tracking" != xno; then
AMDEPBACKSLASH='\'
fi
AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno])
AC_SUBST([AMDEPBACKSLASH])
AC_SUBST([AMDEPBACKSLASH])dnl
_AM_SUBST_NOTMAKE([AMDEPBACKSLASH])dnl
])
# Generate code to set up dependency tracking. -*- Autoconf -*-
@@ -294,8 +318,9 @@ AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS],
# some people rename them; so instead we look at the file content.
# Grep'ing the first line is not enough: some people post-process
# each Makefile.in and add a new line on top of each file to say so.
# So let's grep whole file.
if grep '^#.*generated by automake' $mf > /dev/null 2>&1; then
# Grep'ing the whole file is not good either: AIX grep has a line
# limit of 2048, but all sed's we know have understand at least 4000.
if sed 10q "$mf" | grep '^#.*generated by automake' > /dev/null 2>&1; then
dirpart=`AS_DIRNAME("$mf")`
else
continue
@@ -342,8 +367,8 @@ AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS],
# Do all the work for Automake. -*- Autoconf -*-
# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005
# Free Software Foundation, Inc.
# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
# 2005, 2006 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -366,16 +391,20 @@ AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS],
# arguments mandatory, and then we can depend on a new Autoconf
# release and drop the old call support.
AC_DEFUN([AM_INIT_AUTOMAKE],
[AC_PREREQ([2.58])dnl
[AC_PREREQ([2.60])dnl
dnl Autoconf wants to disallow AM_ names. We explicitly allow
dnl the ones we care about.
m4_pattern_allow([^AM_[A-Z]+FLAGS$])dnl
AC_REQUIRE([AM_SET_CURRENT_AUTOMAKE_VERSION])dnl
AC_REQUIRE([AC_PROG_INSTALL])dnl
# test to see if srcdir already configured
if test "`cd $srcdir && pwd`" != "`pwd`" &&
test -f $srcdir/config.status; then
AC_MSG_ERROR([source directory already configured; run "make distclean" there first])
if test "`cd $srcdir && pwd`" != "`pwd`"; then
# Use -I$(srcdir) only when $(srcdir) != ., so that make's output
# is not polluted with repeated "-I."
AC_SUBST([am__isrc], [' -I$(srcdir)'])_AM_SUBST_NOTMAKE([am__isrc])dnl
# test to see if srcdir already configured
if test -f $srcdir/config.status; then
AC_MSG_ERROR([source directory already configured; run "make distclean" there first])
fi
fi
# test whether we have cygpath
@@ -395,6 +424,9 @@ m4_ifval([$2],
AC_SUBST([PACKAGE], [$1])dnl
AC_SUBST([VERSION], [$2])],
[_AM_SET_OPTIONS([$1])dnl
dnl Diagnose old-style AC_INIT with new-style AM_AUTOMAKE_INIT.
m4_if(m4_ifdef([AC_PACKAGE_NAME], 1)m4_ifdef([AC_PACKAGE_VERSION], 1), 11,,
[m4_fatal([AC_INIT should be called with package and version arguments])])dnl
AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl
AC_SUBST([VERSION], ['AC_PACKAGE_VERSION'])])dnl
@@ -430,6 +462,10 @@ AC_PROVIDE_IFELSE([AC_PROG_CXX],
[_AM_DEPENDENCIES(CXX)],
[define([AC_PROG_CXX],
defn([AC_PROG_CXX])[_AM_DEPENDENCIES(CXX)])])dnl
AC_PROVIDE_IFELSE([AC_PROG_OBJC],
[_AM_DEPENDENCIES(OBJC)],
[define([AC_PROG_OBJC],
defn([AC_PROG_OBJC])[_AM_DEPENDENCIES(OBJC)])])dnl
])
])
@@ -465,7 +501,7 @@ echo "timestamp for $1" >`AS_DIRNAME([$1])`/stamp-h[]$_am_stamp_count])
# Define $install_sh.
AC_DEFUN([AM_PROG_INSTALL_SH],
[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
install_sh=${install_sh-"$am_aux_dir/install-sh"}
install_sh=${install_sh-"\$(SHELL) $am_aux_dir/install-sh"}
AC_SUBST(install_sh)])
# Copyright (C) 2003, 2005 Free Software Foundation, Inc.
@@ -543,14 +579,14 @@ rm -f confinc confmf
# Fake the existence of programs that GNU maintainers use. -*- Autoconf -*-
# Copyright (C) 1997, 1999, 2000, 2001, 2003, 2005
# Copyright (C) 1997, 1999, 2000, 2001, 2003, 2004, 2005
# Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# serial 4
# serial 5
# AM_MISSING_PROG(NAME, PROGRAM)
# ------------------------------
@@ -566,6 +602,7 @@ AC_SUBST($1)])
# If it does, set am_missing_run to use it, otherwise, to nothing.
AC_DEFUN([AM_MISSING_HAS_RUN],
[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
AC_REQUIRE_AUX_FILE([missing])dnl
test x"${MISSING+set}" = xset || MISSING="\${SHELL} $am_aux_dir/missing"
# Use eval to expand $SHELL
if eval "$MISSING --run true"; then
@@ -576,7 +613,7 @@ else
fi
])
# Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc.
# Copyright (C) 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
@@ -584,60 +621,23 @@ fi
# AM_PROG_MKDIR_P
# ---------------
# Check whether `mkdir -p' is supported, fallback to mkinstalldirs otherwise.
#
# Automake 1.8 used `mkdir -m 0755 -p --' to ensure that directories
# created by `make install' are always world readable, even if the
# installer happens to have an overly restrictive umask (e.g. 077).
# This was a mistake. There are at least two reasons why we must not
# use `-m 0755':
# - it causes special bits like SGID to be ignored,
# - it may be too restrictive (some setups expect 775 directories).
#
# Do not use -m 0755 and let people choose whatever they expect by
# setting umask.
#
# We cannot accept any implementation of `mkdir' that recognizes `-p'.
# Some implementations (such as Solaris 8's) are not thread-safe: if a
# parallel make tries to run `mkdir -p a/b' and `mkdir -p a/c'
# concurrently, both version can detect that a/ is missing, but only
# one can create it and the other will error out. Consequently we
# restrict ourselves to GNU make (using the --version option ensures
# this.)
# Check for `mkdir -p'.
AC_DEFUN([AM_PROG_MKDIR_P],
[if mkdir -p --version . >/dev/null 2>&1 && test ! -d ./--version; then
# We used to keeping the `.' as first argument, in order to
# allow $(mkdir_p) to be used without argument. As in
# $(mkdir_p) $(somedir)
# where $(somedir) is conditionally defined. However this is wrong
# for two reasons:
# 1. if the package is installed by a user who cannot write `.'
# make install will fail,
# 2. the above comment should most certainly read
# $(mkdir_p) $(DESTDIR)$(somedir)
# so it does not work when $(somedir) is undefined and
# $(DESTDIR) is not.
# To support the latter case, we have to write
# test -z "$(somedir)" || $(mkdir_p) $(DESTDIR)$(somedir),
# so the `.' trick is pointless.
mkdir_p='mkdir -p --'
else
# On NextStep and OpenStep, the `mkdir' command does not
# recognize any option. It will interpret all options as
# directories to create, and then abort because `.' already
# exists.
for d in ./-p ./--version;
do
test -d $d && rmdir $d
done
# $(mkinstalldirs) is defined by Automake if mkinstalldirs exists.
if test -f "$ac_aux_dir/mkinstalldirs"; then
mkdir_p='$(mkinstalldirs)'
else
mkdir_p='$(install_sh) -d'
fi
fi
AC_SUBST([mkdir_p])])
[AC_PREREQ([2.60])dnl
AC_REQUIRE([AC_PROG_MKDIR_P])dnl
dnl Automake 1.8 to 1.9.6 used to define mkdir_p. We now use MKDIR_P,
dnl while keeping a definition of mkdir_p for backward compatibility.
dnl @MKDIR_P@ is magic: AC_OUTPUT adjusts its value for each Makefile.
dnl However we cannot define mkdir_p as $(MKDIR_P) for the sake of
dnl Makefile.ins that do not define MKDIR_P, so we do our own
dnl adjustment using top_builddir (which is defined more often than
dnl MKDIR_P).
AC_SUBST([mkdir_p], ["$MKDIR_P"])dnl
case $mkdir_p in
[[\\/$]]* | ?:[[\\/]]*) ;;
*/*) mkdir_p="\$(top_builddir)/$mkdir_p" ;;
esac
])
# Helper functions for option handling. -*- Autoconf -*-
@@ -749,9 +749,21 @@ dnl Don't test for $cross_compiling = yes, because it might be `maybe'.
if test "$cross_compiling" != no; then
AC_CHECK_TOOL([STRIP], [strip], :)
fi
INSTALL_STRIP_PROGRAM="\${SHELL} \$(install_sh) -c -s"
INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s"
AC_SUBST([INSTALL_STRIP_PROGRAM])])
# Copyright (C) 2006 Free Software Foundation, Inc.
#
# This file is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# _AM_SUBST_NOTMAKE(VARIABLE)
# ---------------------------
# Prevent Automake from outputing VARIABLE = @VARIABLE@ in Makefile.in.
# This macro is traced by Automake.
AC_DEFUN([_AM_SUBST_NOTMAKE])
# Check how to create a tarball. -*- Autoconf -*-
# Copyright (C) 2004, 2005 Free Software Foundation, Inc.

View File

@@ -9,10 +9,6 @@
/* Define to 1 to disable smart par-verification and restoration */
#undef DISABLE_PARCHECK
/* Define to 1 to show progress during par-check (it must be disabled if
sigc++ doesn't work correctly) */
#undef ENABLE_PARPROGRESS
/* Define to 1 to include support for uulib */
#undef ENABLE_UULIB

7669
configure vendored
View File

File diff suppressed because it is too large Load Diff

View File

@@ -2,8 +2,8 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.59)
AC_INIT(nzbget, 0.3.0, hugbug@users.sourceforge.net)
AM_INIT_AUTOMAKE(nzbget, 0.3.0)
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])
@@ -342,20 +342,6 @@ if test "$ENABLEPARCHECK" = "yes"; then
dnl AC_MSG_ERROR("libpar2 library not found in $LIBVAL.")
dnl fi
dnl
dnl Enable par-check-progress (via sigc++)? Deafult: yes
dnl It doesn't work on WL500gP (uClibc), so we need a way to disable it
dnl
AC_MSG_CHECKING(whether to show progress during par-check )
AC_ARG_ENABLE(parprogress,
[ --disable-parprogress do not show progress during par-check (it must be disabled if sigc++ doesn't work correctly)],
[ USEPARPROGRESS=$enableval ],
[ USEPARPROGRESS=yes] )
AC_MSG_RESULT($USEPARPROGRESS)
if test "$USEPARPROGRESS" = "yes"; then
AC_DEFINE([ENABLE_PARPROGRESS],1,[Define to 1 to show progress during par-check (it must be disabled if sigc++ doesn't work correctly)])
fi
else
AC_DEFINE([DISABLE_PARCHECK],1,[Define to 1 to disable smart par-verification and restoration])
fi

View File

@@ -423,3 +423,14 @@ diff -urN libpar2-0.2-original/par2cmdline.h libpar2-0.2-modified/par2cmdline.h
#else // WIN32
#ifdef HAVE_CONFIG_H
diff -urN libpar2-0.2-original/par2repairer.cpp libpar2-0.2-modified/par2repairer.cpp
--- libpar2-0.2-original/par2repairer.cpp 2006-01-20 18:25:20.000000000 +0100
+++ libpar2-0.2-modified/par2repairer.cpp 2008-01-31 14:35:11.110360000 +0100
@@ -78,6 +78,7 @@
delete mainpacket;
delete creatorpacket;
+ delete headers;
}

View File

@@ -1,16 +1,23 @@
# Sample configuration file for nzbget
# Put this file to one of the following locations:
#
# On POSIX put this file to one of the following locations:
# ~/.nzbget
# /etc/nzbget.conf
# /usr/etc/nzbget.conf
# /usr/local/etc/nzbget.conf
# /opt/etc/nzbget.conf
#
# On Windows put this file in program's directory.
#
# You can also put the file into any location, if you specify the path to it
# using switch "-c", e.g:
# nzbget -c /home/user/myconig.txt
# For quick start change the option MAINDIR and configure one news-server
# See section NEWS-SERVERS.
# PROGRAM OPTIONS
##############################################################################
### PATHS ###
# Root directory for all related tasks
# MAINDIR is a variable and therefore starts with "$"
@@ -19,162 +26,26 @@
$MAINDIR=~/download
# Destination-directory to store the downloaded files
destdir=${MAINDIR}/dst
# Directory to store download queue
queuedir=${MAINDIR}/queue
DestDir=${MAINDIR}/dst
# Directory to monitor for incoming nzb-jobs
nzbdir=${MAINDIR}/nzb
NzbDir=${MAINDIR}/nzb
# Directory to store download queue
QueueDir=${MAINDIR}/queue
# Directory to store temporary files
tempdir=${MAINDIR}/tmp
TempDir=${MAINDIR}/tmp
# Lock-file for daemon-mode, contains process-id (PID)
lockfile=/tmp/nzbget.lock
# Lock-file for daemon-mode, contains process-id (PID) (POSIX only)
LockFile=/tmp/nzbget.lock
# Where to store log file, if it needs to be created (see "createlog")
logfile=${destdir}/nzbget.log
# Save download queue to disk. This allows to reload it on next start (yes, no)
savequeue=yes
# Reload download queue on start, if it exists (yes, no)
reloadqueue=yes
# Create log file (yes, no)
createlog=yes
# Delete log file upon server start (only in server-mode) (yes, no)
resetlog=no
# How various messages must be printed (screen, log, both, none)
# Debug-messages can be only printed if the programm was compiled in
# debug-mode: "./configure --enable-debug"
infotarget=both
warningtarget=both
errortarget=both
debugtarget=both
# Create subdirectory with nzb-filename in destination-directory (yes, no)
appendnzbdir=yes
# Set screen-outputmode (loggable, colored, curses)
outputmode=colored
# Check if the destination file already exists (yes, no)
# If the file exists it will not be added to queue.
# If "no" the file will be downloaded and renamed to "filename_duplicate1",
# no existing files are deleted or overwritten.
dupecheck=no
# Set the maximum download rate in KB/s, "0" means no speed control
downloadrate=0
# Visibly rename broken files on download appending "_broken" (yes, no)
renamebroken=no
# Create a log of all broken files (yes ,no)
# It is a text file placed near downloaded files, which contains
# the names of broken files
createbrokenlog=yes
# Set the IP on which the server listen and which client uses to contact the server
# It could be ip-address oder dns-hostname
serverip=127.0.0.1
# Set the port which the server & client use
serverport=6789
# Set the password needed to succesfully queue a request
serverpassword=tegbzn6789
# Determine how the articles should be decoded (uulib, yenc, none)
# uulib - use uulib to decode files. Supports many encoding formats, but is slow.
# yenc - use internal yEnc-Decoder. Supports only yEnc-format and is very fast.
# none - the articles will not be decoded and joined. External programs
# ("uudeview" is one of them) could be used to decode an join downloaded articles.
decoder=yEnc
# How much retries should be attempted if a download error occurs
retries=4
# Set the interval between retries, in seconds
retryinterval=10
# Set connection timeout, in seconds
connectiontimeout=60
# Timeout until a download-thread is killed (helps on hanging downloads), in seconds
terminatetimeout=600
# How many par2-files to load (none, all, one)
# none - all added par2-files must be automatically paused
# all - all added par2-files must be downloaded
# one - only one main par2-file must be dowloaded and other must be paused
# Paused files remain in queue and should be deleted manually,
# when they not needed anymore
loadpars=all
# Automatic par-verification (yes, no)
# To download only needed par2-files (smart par-files loading) set also
# the option "loadpars" to "one". If option "loadpars" is set to "all",
# all par2-files will be downloaded before verification and repair starts.
# The option "renamebroken" must be set to "no", otherwise the par-checker
# may not find renamed files and failed.
parcheck=no
# Automatic par-repair (yes, no)
# If option "parcheck" is enabled and "parrepair" is not, the program
# only verify downloaded files and downloads needed par2-files, but do
# not start repair-process. This is useful if the server does not have
# enough CPU power, since repairing of large files may take too much
# resources and time on a slow computers.
# This option has effect only if the option "parcheck" is enabled.
parrepair=yes
# Use only par2-files with matching names (yes, no)
# If par-check needs extra par-blocks it searches a par2-files
# in download queue, which can be unpauseed and used for restore. These par2-files
# should have the same base name as a main par2-file, currently loaded in par-checker.
# Sometimes extra par files (especially if they were uploaded not from original poster)
# have not matching names. Normally par-checker does not use these files, but
# you can allow it to use these file by setting "strictparname" to "no".
# This has however a side effect: if NZB-file contains more than one collection
# of files (with different par-sets), par-checker may download par-files from
# a wrong collection. This increases you traffic (but not harm par-check).
# NOTE: par-checker always uses only par-files added from the same NZB-file.
# Option "strictparname" does not change this behavior.
strictparname=yes
# Set path to program, that must be executed after the download of
# nzb-file or one collection in nzb-file (if par-check enabled)
# is completed and possibly par-checked/repaired.
# Six arguments are being passed to this program:
# - path to destination dir, where downloaded files are located;
# - name of nzb-file processed;
# - name of par-file processed (if par-checked) or empty string (if not);
# - result of par-check:
# 0 - not checked: par-check disabled or nzb-file does not contain any par-files;
# 1 - checked and sucessfully repaired;
# 2 - checked and failed to repair;
# - state of nzb-job:
# 0 - there are more collection in this nzb-file queued;
# 1 - this was the last collection in nzb-file;
# NOTE: if par-check is enabled and nzb-file contains more than one collection
# of files the postprocess-program is called after each collection is completed
# and par-checked. If you want to clean up the directory (delete par-files, etc.)
# there are two possibilities, when you can do this:
# 1) you parse the "name of par-file processed" to find out the base name
# of collection and clean up only files from this collection;
# 2) or you just check the parameter "state of nzb-job" and do clean up,
# only if it is equal to "1" (which means, that this was the last
# collection in nzb-file and all files are now completed);
# NOTE: do not forget to uncomment next line.
#postprocess=~/myscript.sh
# Where to store log file, if it needs to be created (see "CreateLog")
LogFile=${destdir}/nzbget.log
# NEWS-SERVERS
##############################################################################
### NEWS-SERVERS ###
# This section defines which servers nzbget should connect to.
# The servers will be ordered by their level, i.e. nzbget will at
@@ -182,8 +53,9 @@ strictparname=yes
# If that server fails, nzbget proceeds with the level-1-server, etc.
# A good idea is surely to put your major download-server at level 0
# and your fill-servers at levels 1,2,...
# NOTE 1: Do not leave out a level in your server-list and start with level 0!
# NOTE 2: Several servers with the same level may be used.
# NOTE: Do not leave out a level in your server-list and start with level 0!
# NOTE: Several servers with the same level may be used, they will have
# the same priority.
# First server, on level 0
# Level of newsserver
@@ -214,3 +86,351 @@ server1.connections=4
#server3.username=me2
#server3.password=mypass2
#server3.connections=1
##############################################################################
### PERMISSIONS (POSIX ONLY) ###
# User name for daemon-mode (POSIX in daemon-mode only).
# Set the user that the daemon normally runs at.
# Set $MAINDIR with an absolute path to be sure where it will write.
# This allows nzbget daemon to be launched in rc.local (at boot), and
# download items as a specific user id.
# NOTE: This option has effect only if the program was started from
# root-account, otherwise it is ignored and the daemon runs under
# current user id
DaemonUserName=root
# Specify default umask (affects file permissions) for newly created
# files (POSIX only).
# The value should be written in octal form (the same as for "umask" shell
# command). If umask not specified (or a value greater than 0777 used, useful
# to disable current config-setting via command-line parameter) the umask-mode
# will not be set and current umask-mode (set via shell) will be used
# NOTE: do not forget to uncomment the next line
#UMask=022
##############################################################################
### DOWNLOAD QUEUE ###
# Save download queue to disk. This allows to reload it on next start (yes, no)
SaveQueue=yes
# Reload download queue on start, if it exists (yes, no)
ReloadQueue=yes
# Reuse articles saved in temp-directory from previous program start (yes, no)
# This allows to continue download of file, if program was exited before
# the file was completed.
ContinuePartial=yes
# Create subdirectory with nzb-filename in destination-directory (yes, no)
AppendNzbDir=yes
# How often incoming-directory (option "NzbDir") must be checked for new
# nzb-files, in seconds.
# Value "0" disables the check.
NzbDirInterval=5
# How old nzb-file should at least be for it to be loaded to queue, in seconds.
# Nzbget checks if nzb-file was not modified in last few seconds, defined by
# this option. That safety interval prevents the loading of files, which
# were not yet completely saved to disk, for example if they are still being
# downloaded in web-browser.
NzbDirFileAge=60
# Check for duplicate files (yes, no)
# If this option is enabled the program checks by adding of a new nzb-file:
# 1) if nzb-file contains duplicate entries. This check aims on detecting
# of reposted files (if first file was not fully uploaded);
# If the program find two files with identical names, only the
# biggest of these files will be added to queue;
# 2) if download queue already contains file with the same name;
# 3) if destination file on disk already exists.
# In last two cases: if the file exists it will not be added to queue;
# If this option is disabled, all files are downloaded and duplicate files
# are renamed to "filename_duplicate1".
# Existing files are never deleted or overwritten.
DupeCheck=no
# Visibly rename broken files on download appending "_broken" (yes, no)
# Do not activate this option if par-check is enabled.
RenameBroken=no
# Determine how the articles should be decoded (yenc, uulib, none)
# yenc - use internal yEnc-Decoder. Supports only yEnc-format, but is
# very fast and does not create temporary files with articles' text
# decoding them on the fly.
# uulib - use uulib to decode files. Supports many encoding formats,
# but is slow. Not available in Windows-version.
# none - the articles will not be decoded and joined. External programs
# (like "uudeview") can be used to decode and join downloaded articles.
Decoder=yEnc
# Write decoded articles directly into destination output file (yes, no)
# With this option enabled the program at first creates the output
# destination file with required size (total size of all articles),
# then writes on the fly decoded articles directly to the file
# without creating of any temporary files, even for decoded articles.
# This may results in major performance improvement, but this higly
# depends on OS and filesystem used.
# Can improve performance on a very fast internet connections,
# but you need to test if it works in your case.
# The option works only with internal decoder ("decoder=yenc")
# INFO: Tests showed, that on Linux with EXT3-partition activating of
# this option results in up to 20% better performance, but on Windows with NTFS
# or Linux with FAT32-partitions the performance were decreased.
# The possible reason is that on EXT3-partition Linux can create large files
# very fast (if the content of file does not need to be initialized),
# but Windows on NTFS-partition and also Linux on FAT32-partition need to
# initialize created large file with nulls, resulting in a big performace
# degradation.
# NOTE: for testing try to download few big files (with total size 500-1000MB)
# and measure required time. Do not rely on the program's speed indicator,
# it is not accurate.
# NOTE: if both options "DirectWrite" and "ContinuePartial" are enabled,
# the program will create empty articles-files in temp-directrory. They
# are used to continue download of file on a next program start. To minimize
# disk-io it is recommended to disable option "ContinuePartial", if
# "DirectWrite" is enabled. Especially on a fast connections (where you
# would want to activate "DirectWrite") it should not be a problem to
# redownload the interrupted file.
DirectWrite=no
# Check CRC of downloaded and decoded articles (yes, no)
# Normally this option should be enabled for better detecting of download
# errors. However checking of CRC needs about the same CPU time as
# decoding of articles. On a fast connections with slow CPUs disabling of
# CPU-check may slightly improve performance (if CPU is a limiting factor).
# The option works only with internal decoder ("decoder=yenc")
CrcCheck=yes
# How much retries should be attempted if a download error occurs
Retries=4
# Set the interval between retries, in seconds
RetryInterval=10
# Redownload article if CRC-check fails (yes, no)
# Helps to minimize number of broken files, but may be effective
# only if you have multiple download servers (even from the same provider
# but from different locations (e.g. europe, usa)).
# In any case the option increases your traffic.
# For slow connections loading of extra par-blocks may be more effective
# The option "CrcCheck" must be enabled for option "RetryOnCrcError" to work.
RetryOnCrcError=no
# Set connection timeout, in seconds
ConnectionTimeout=60
# Timeout until a download-thread is killed, in seconds
# This can help on hanging downloads, but is dangerous.
# Do not use small values!
TerminateTimeout=600
# Set the maximum number of threads program may create.
# Connection errors or fast connection with slow cpu can cause
# the creating of many (thousands) threads, which results in program crash.
# Limiting the number of threads helps in such situations.
# The option affects only download threads, so the number of existing threads
# may be slightly more than set by the option.
ThreadLimit=100
# Set the maximum download rate in KB/s, "0" means no speed control
DownloadRate=0
# Set the size of memory buffer used by writing the articles, in Bytes.
# Bigger values decrease disk-io, but increase memory usage.
# Value "0" causes the OS-dependend default value to be used.
# With value "-1" (which means "max/auto") the program sets the size of
# buffer according to the size of current article (typically less than 500K).
# NOTE: the value must be written in bytes, do not use postfixes "K" or "M".
# NOTE: to calculate the memory usage multiply WriteBufferSize by max number
# of connections, configured in section "NEWS-SERVERS".
# NOTE: typical article's size not exceed 500000 bytes, so using bigger values
# (like several megabytes) will just waste memory.
# NOTE: for desktop computers with large amount of memory value "-1" (max/auto)
# is recommended, but for computers with very low memory (routers, NAS)
# value "0" (default OS-dependend size) could be better alternative.
# NOTE: write-buffer is managed by OS (system libraries) and therefore
# the effect of the option is highly OS-dependend.
WriteBufferSize=0
##############################################################################
### LOGGING ###
# Create log file (yes, no)
CreateLog=yes
# Delete log file upon server start (only in server-mode) (yes, no)
ResetLog=no
# How various messages must be printed (screen, log, both, none)
# Debug-messages can be only printed if the programm was compiled in
# debug-mode: "./configure --enable-debug"
InfoTarget=both
WarningTarget=both
ErrorTarget=both
DebugTarget=both
# Number of messages stored in buffer and available for remote clients
LogBufferSize=1000
# Create a log of all broken files (yes ,no)
# It is a text file placed near downloaded files, which contains
# the names of broken files
CreateBrokenLog=yes
# See also option "logfile" in secion "PATHS"
##############################################################################
### DISPLAY ###
# Set screen-outputmode (loggable, colored, curses)
# loggable - only messages will be printed to standard output;
# colored - prints messages (with simple coloring for messages categories)
# and download progress info; uses escape-sequenses to move cursor;
# curses - advanced interactive iterface with the ability to edit
# download queue and variaous output options;
OutputMode=curses
# Shows NZB-Filename in file list in curses-outputmode (yes, no)
# This option controls the initial state of curses-frontend,
# it can be switched on/off in run-time with Z-key
CursesNzbName=yes
# Show files in groups (NZB-files) in queue list in curses-outputmode (yes, no)
# This option controls the initial state of curses-frontend,
# it can be switched on/off in run-time with G-key
CursesGroup=no
# Show timestamps in message list in curses-outputmode (yes, no)
# This option controls the initial state of curses-frontend,
# it can be switched on/off in run-time with T-key
CursesTime=no
# Update interval for Frontend-output in MSec (min value 25)
# Bigger values reduce CPU usage (especially in curses-outputmode)
# and network traffic in remote-client mode
UpdateInterval=200
##############################################################################
### CLIENT/SERVER COMMUNICATION ###
# Set the IP on which the server listen and which client uses to contact
# the server. It could be dns-hostname or ip-address (more effective since
# does not require dns-lookup).
# If you want the server to listen to all interfaces, use "0.0.0.0"
ServerIp=127.0.0.1
# Set the port which the server & client use
ServerPort=6789
# Set the password needed to succesfully queue a request
ServerPassword=tegbzn6789
# See also option "logbuffersize" in section "LOGGING"
##############################################################################
### PAR CHECK AND REPAIR ###
# How many par2-files to load (none, all, one)
# none - all added par2-files must be automatically paused
# all - all added par2-files must be downloaded
# one - only one main par2-file must be dowloaded and other must be paused
# Paused files remain in queue and should be deleted manually,
# when they not needed anymore
LoadPars=one
# Automatic par-verification (yes, no)
# To download only needed par2-files (smart par-files loading) set also
# the option "loadpars" to "one". If option "loadpars" is set to "all",
# all par2-files will be downloaded before verification and repair starts.
# The option "renamebroken" must be set to "no", otherwise the par-checker
# may not find renamed files and fail
ParCheck=no
# Automatic par-repair (yes, no)
# If option "parcheck" is enabled and "parrepair" is not, the program
# only verifies downloaded files and downloads needed par2-files, but do
# not start repair-process. This is useful if the server does not have
# enough CPU power, since repairing of large files may take too much
# resources and time on a slow computers.
# This option has effect only if the option "parcheck" is enabled
ParRepair=yes
# Use only par2-files with matching names (yes, no)
# If par-check needs extra par-blocks it searches for par2-files
# in download queue, which can be unpaused and used for restore.
# These par2-files should have the same base name as the main par2-file,
# currently loaded in par-checker. Sometimes extra par files (especially if
# they were uploaded from a different poster) have not matching names.
# Normally par-checker does not use these files, but you can allow it
# to use these files by setting "strictparname" to "no".
# This has however a side effect: if NZB-file contains more than one collection
# of files (with different par-sets), par-checker may download par-files from
# a wrong collection. This increases you traffic (but not harm par-check).
# NOTE: par-checker always uses only par-files added from the same NZB-file
# and the option "strictparname" does not change this behavior
StrictParName=yes
##############################################################################
### POSTPROCESSING ###
# Set path to program, that must be executed after the download of
# nzb-file or one collection in nzb-file (if par-check enabled)
# is completed and possibly par-checked/repaired.
# Six arguments are being passed to this program:
# - path to destination dir, where downloaded files are located;
# - name of nzb-file processed;
# - name of par-file processed (if par-checked) or empty string (if not);
# - result of par-check:
# 0 - not checked: par-check disabled or nzb-file does not contain any
# par-files;
# 1 - checked and failed to repair;
# 2 - checked and sucessfully repaired;
# 3 - checked and can be repaired but repair is disabled;
# - state of nzb-job:
# 0 - there are more collections in this nzb-file queued;
# 1 - this was the last collection in nzb-file;
# NOTE: if par-check is enabled and nzb-file contains more than one collection
# of files the postprocess-program is called after each collection is completed
# and par-checked. If you want to clean up the directory (delete par-files,
# etc.) there are two possibilities, when you can do this:
# 1) you parse the "name of par-file processed" to find out the base name
# of collection and clean up only files from this collection;
# 2) or you just check the parameter "state of nzb-job" and do clean up,
# only if it is equal to "1" (which means, that this was the last
# collection in nzb-file and all files are now completed);
# NOTE: do not forget to uncomment the next line
#PostProcess=~/myscript.sh
##############################################################################
### PERFORMANCE ###
# On a very fast connection and slow CPU and/or drive the following
# settings may improve performance:
# 1) Disable par-checking and -repairing ("ParCheck=no"). VERY important,
# because par-checking/repairing needs a lot of CPU-power and
# significantly increases disk usage;
# 2) Use internal Decoder ("Decoder=yenc");
# 3) Try to activate option "DirectWrite" ("DirectWrite=yes");
# 4) Disable option "CrcCheck" ("CrcCheck=no");
# 5) Disable option "ContinuePartial" ("ContinuePartial=no");
# 6) Do not limit download rate ("DownloadRate=0"), because the bandwidth
# throttling eats CPU time;
# 7) Disable logging for info- and debug-messages ("InfoTarget=none",
# "DebugTarget=none");
# 8) Run the program in daemon (Posix) or service (Windows) mode and use
# remote client for short periods of time needed for controlling of
# download process on server. Daemon/Service mode eats less CPU
# resources due to not updating of output on screen.
# 9) Increase the value of option "WriteBufferSize" or better set it to
# "-1" (max/auto) if you have spare 5-20 MB of memory.

View File

@@ -38,6 +38,8 @@
#include <winsvc.h>
#else
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
@@ -45,12 +47,6 @@
#include <stdio.h>
#include <fcntl.h>
#ifdef DEBUG
#ifndef DISABLE_PARCHECK
#include <sigc++/sigc++.h>
#endif
#endif
#include "nzbget.h"
#include "ServerPool.h"
#include "Log.h"
@@ -63,6 +59,7 @@
#include "RemoteServer.h"
#include "RemoteClient.h"
#include "MessageBase.h"
#include "DiskState.h"
#include "PrePostProcessor.h"
#include "ParChecker.h"
#ifdef WIN32
@@ -77,18 +74,17 @@ void ProcessClientRequest();
void InstallSignalHandlers();
void Daemonize();
#endif
#ifdef DEBUG
void DoTest();
#endif
Thread* g_pFrontend = NULL;
Options* g_pOptions = NULL;
Thread* g_pFrontend = NULL;
Options* g_pOptions = NULL;
ServerPool* g_pServerPool = NULL;
QueueCoordinator* g_pQueueCoordinator = NULL;
RemoteServer* g_pRemoteServer = NULL;
DownloadSpeedMeter* g_pDownloadSpeedMeter = NULL;
Log* g_pLog = NULL;
PrePostProcessor* g_pPrePostProcessor;
PrePostProcessor* g_pPrePostProcessor = NULL;
DiskState* g_pDiskState = NULL;
/*
* Main loop
@@ -106,6 +102,14 @@ int main(int argc, char *argv[])
debug("Options parsing");
g_pOptions = new Options(argc, argv);
#ifndef WIN32
if (g_pOptions->GetUMask() < 01000)
{
/* set newly created file permissions */
umask(g_pOptions->GetUMask());
}
#endif
if (g_pOptions->GetServerMode() && g_pOptions->GetCreateLog() && g_pOptions->GetResetLog())
{
debug("deleting old log-file");
@@ -114,12 +118,13 @@ int main(int argc, char *argv[])
if (g_pOptions->GetDaemonMode())
{
info("nzbget daemon-mode");
#ifdef WIN32
info("nzbget service-mode");
StartService(Run);
return 0;
#else
Daemonize();
info("nzbget daemon-mode");
#endif
}
else if (g_pOptions->GetServerMode())
@@ -153,7 +158,7 @@ void Run()
}
// Create the queue coordinator
if (!g_pOptions->GetRemoteClientMode() && !g_pOptions->GetTest())
if (!g_pOptions->GetRemoteClientMode())
{
g_pQueueCoordinator = new QueueCoordinator();
g_pDownloadSpeedMeter = g_pQueueCoordinator;
@@ -166,7 +171,14 @@ void Run()
g_pRemoteServer->Start();
}
// Create the front-end
// Starting a thread with the PrePostProcessor
if (!g_pOptions->GetRemoteClientMode())
{
g_pPrePostProcessor = new PrePostProcessor();
g_pPrePostProcessor->Start();
}
// Create the frontend
if (!g_pOptions->GetDaemonMode())
{
switch (g_pOptions->GetOutputMode())
@@ -191,13 +203,9 @@ void Run()
g_pFrontend->Start();
}
// Start QueueCoordinator
if (!g_pOptions->GetRemoteClientMode() && !g_pOptions->GetTest())
// Starting QueueCoordinator
if (!g_pOptions->GetRemoteClientMode())
{
g_pPrePostProcessor = new PrePostProcessor();
g_pPrePostProcessor->Start();
// Standalone-mode
if (!g_pOptions->GetServerMode() && !g_pQueueCoordinator->AddFileToQueue(g_pOptions->GetArgFilename()))
{
@@ -205,6 +213,11 @@ void Run()
return;
}
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
{
g_pDiskState = new DiskState();
}
g_pQueueCoordinator->Start();
// enter main program-loop
@@ -249,14 +262,6 @@ void Run()
debug("RemoteServer stopped");
}
#ifdef DEBUG
if (g_pOptions->GetTest())
{
DoTest();
}
#endif
// Stop Frontend
if (g_pFrontend)
{
@@ -302,7 +307,7 @@ void ProcessClientRequest()
else if (g_pOptions->GetClientOperation() == Options::opClientRequestEditQueue)
{
Client->RequestServerEditQueue(g_pOptions->GetEditQueueAction(), g_pOptions->GetEditQueueOffset(),
g_pOptions->GetEditQueueIDFrom(), g_pOptions->GetEditQueueIDTo());
g_pOptions->GetEditQueueIDList(), g_pOptions->GetEditQueueIDCount(), true);
}
else if (g_pOptions->GetClientOperation() == Options::opClientRequestLog)
{
@@ -316,6 +321,10 @@ void ProcessClientRequest()
{
Client->RequestServerDownload(g_pOptions->GetArgFilename(), g_pOptions->GetAddTop());
}
else if (g_pOptions->GetClientOperation() == Options::opClientRequestVersion)
{
Client->RequestServerVersion();
}
delete Client;
}
@@ -372,6 +381,11 @@ void SignalProc(int iSignal)
debug("SIGPIPE received, ignoring");
break;
case SIGSEGV:
signal(SIGSEGV, SIG_DFL); // Reset the signal handler
debug("SIGSEGV received");
break;
default:
debug("Signal %i received", iSignal);
if (SignalProcList[iSignal - 1])
@@ -435,6 +449,14 @@ void Cleanup()
}
debug("Frontend deleted");
debug("Deleting DiskState");
if (g_pDiskState)
{
delete g_pDiskState;
g_pDiskState = NULL;
}
debug("DiskState deleted");
debug("Deleting Options");
if (g_pOptions)
{
@@ -480,11 +502,26 @@ void Daemonize()
setsid(); /* obtain a new process group */
for (i = getdtablesize();i >= 0;--i) close(i); /* close all descriptors */
i = open("/dev/null", O_RDWR); dup(i); dup(i); /* handle standart I/O */
umask(027); /* set newly created file permissions */
chdir(g_pOptions->GetDestDir()); /* change running directory */
lfp = open(g_pOptions->GetLockFile(), O_RDWR | O_CREAT, 0640);
if (lfp < 0) exit(1); /* can not open */
if (lockf(lfp, F_TLOCK, 0) < 0) exit(0); /* can not lock */
/* Drop user if there is one, and we were run as root */
if ( getuid() == 0 || geteuid() == 0 )
{
struct passwd *pw = getpwnam(g_pOptions->GetDaemonUserName());
if (pw)
{
setgroups( 0, (const gid_t*) 0 ); /* Set aux groups to null. */
setgid(pw->pw_gid); /* Set primary group. */
/* Try setting aux groups correctly - not critical if this fails. */
initgroups( g_pOptions->GetDaemonUserName(),pw->pw_gid);
/* Finally, set uid. */
setuid(pw->pw_uid);
}
}
/* first instance continues */
sprintf(str, "%d\n", getpid());
write(lfp, str, strlen(str)); /* record pid to lockfile */
@@ -494,36 +531,3 @@ void Daemonize()
signal(SIGTTIN, SIG_IGN);
}
#endif
#ifdef DEBUG
#ifndef DISABLE_PARCHECK
/*
class T1 { public:
sigc::signal<int, std::string> sig_filename;
int Test1() { std::string str = "test"; return sig_filename.emit(str); }
};
class T2 { public:
void Test1() {
T1 t1;
t1.sig_filename.connect(sigc::mem_fun(*this, &T2::signal_filename));
if (t1.Test1() == 4) {
printf("ok\n");
//exit(0);
} else {
printf("error\n");
//exit(-1);
}
}
int signal_filename(std::string str) {
printf("%s\n", str.c_str());
return str.length();
}
};
*/
#endif
void DoTest()
{
printf("testing\n");
}
#endif

View File

@@ -31,6 +31,7 @@
// WIN32
#define snprintf _snprintf
#define strdup _strdup
#define ctime_r(timep, buf, bufsize) ctime_s(buf, bufsize, timep)
#define int32_t __int32
#define mkdir(dir, flags) _mkdir(dir)
@@ -44,6 +45,7 @@
#define __S_ISTYPE(mode, mask) (((mode) & _S_IFMT) == (mask))
#define S_ISDIR(mode) __S_ISTYPE((mode), _S_IFDIR)
#define S_ISREG(mode) __S_ISTYPE((mode), _S_IFREG)
#define S_DIRMODE NULL
#define usleep(usec) Sleep((usec) / 1000)
#define gettimeofday(tm, ignore) _ftime(tm)
@@ -64,6 +66,7 @@
#define PATH_SEPARATOR '/'
#define ALT_PATH_SEPARATOR '\\'
#define MAX_PATH 1024
#define S_DIRMODE (S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)
#endif

View File

@@ -131,7 +131,7 @@
<Tool
Name="VCLinkerTool"
AdditionalDependencies="WS2_32.Lib ole32.lib OleAut32.Lib comsuppwd.lib Advapi32.lib sigc-2.0.lib par2.lib"
LinkIncremental="2"
LinkIncremental="0"
GenerateDebugInformation="true"
SubSystem="1"
OptimizeReferences="2"
@@ -339,6 +339,14 @@
RelativePath=".\QueueCoordinator.h"
>
</File>
<File
RelativePath=".\QueueEditor.cpp"
>
</File>
<File
RelativePath=".\QueueEditor.h"
>
</File>
<File
RelativePath=".\RemoteClient.cpp"
>

View File

@@ -30,10 +30,6 @@
/* Define to 1 to disable smart par-verification and restoration */
#undef DISABLE_PARCHECK
/* Define to 1 to show progress during par-check (it must be disabled if
sigc++ doesn't work correctly) */
#define ENABLE_PARPROGRESS
/* Define to 1 to include support for uulib */
#undef ENABLE_UULIB
@@ -56,7 +52,7 @@
/* Define to 1 if variadic macros are supported */
#define HAVE_VARIADIC_MACROS
#define VERSION "0.3.0"
#define VERSION "0.3.1"
/* Suppress warnings */
#define _CRT_SECURE_NO_DEPRECATE