pax_global_header00006660000000000000000000000064122645063300014513gustar00rootroot0000000000000052 comment=167c77e76a312401df0d9c4b95b653dba866551e nzbget-12.0+dfsg/000077500000000000000000000000001226450633000136455ustar00rootroot00000000000000nzbget-12.0+dfsg/AUTHORS000066400000000000000000000003501226450633000147130ustar00rootroot00000000000000nzbget: Sven Henkel (versions 0.1.0 - ?) Bo Cordes Petersen (versions ? - 0.2.3) Andrey Prygunkov (versions 0.3.0 and later) nzbget-12.0+dfsg/ArticleDownloader.cpp000066400000000000000000000767261226450633000177750ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 917 $ * $Date: 2013-12-10 21:37:02 +0100 (Tue, 10 Dec 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #ifdef WIN32 #include #else #include #include #endif #include #include #include "nzbget.h" #include "ArticleDownloader.h" #include "Decoder.h" #include "Log.h" #include "Options.h" #include "ServerPool.h" #include "Util.h" extern DownloadSpeedMeter* g_pDownloadSpeedMeter; extern DownloadQueueHolder* g_pDownloadQueueHolder; extern Options* g_pOptions; extern ServerPool* g_pServerPool; ArticleDownloader::ArticleDownloader() { debug("Creating ArticleDownloader"); m_szResultFilename = NULL; m_szTempFilename = NULL; m_szArticleFilename = NULL; m_szInfoName = NULL; m_szOutputFilename = NULL; m_pConnection = NULL; m_eStatus = adUndefined; m_bDuplicate = false; m_eFormat = Decoder::efUnknown; SetLastUpdateTimeNow(); } ArticleDownloader::~ArticleDownloader() { debug("Destroying ArticleDownloader"); free(m_szTempFilename); free(m_szArticleFilename); free(m_szInfoName); free(m_szOutputFilename); } void ArticleDownloader::SetTempFilename(const char* v) { m_szTempFilename = strdup(v); } void ArticleDownloader::SetOutputFilename(const char* v) { m_szOutputFilename = strdup(v); } void ArticleDownloader::SetInfoName(const char * v) { m_szInfoName = strdup(v); } /* * How server management (for one particular article) works: - there is a list of failed servers which is initially empty; - level is initially 0; - request a connection from server pool for current level; Exception: this step is skipped for the very first download attempt, because a level-0 connection is initially passed from queue manager; - try to download from server; - if connection to server cannot be established or download fails due to interrupted connection, try again (as many times as needed without limit) the same server until connection is OK; - if download fails with error "Not-Found" (article or group not found) or with CRC error, add the server to failed server list; - if download fails with general failure error (article incomplete, other unknown error codes), try the same server again as many times as defined by option ; if all attempts fail, add the server to failed server list; - if all servers from current level were tried, increase level; - if all servers from all levels were tried, break the loop with failure status. */ void ArticleDownloader::Run() { debug("Entering ArticleDownloader-loop"); SetStatus(adRunning); BuildOutputFilename(); m_szResultFilename = m_pArticleInfo->GetResultFilename(); if (g_pOptions->GetContinuePartial()) { if (Util::FileExists(m_szResultFilename)) { // file exists from previous program's start detail("Article %s already downloaded, skipping", m_szInfoName); FreeConnection(true); SetStatus(adFinished); Notify(NULL); return; } } EStatus Status = adFailed; int iRetries = g_pOptions->GetRetries() > 0 ? g_pOptions->GetRetries() : 1; int iRemainedRetries = iRetries; Servers failedServers; failedServers.reserve(g_pServerPool->GetServers()->size()); NewsServer* pWantServer = NULL; NewsServer* pLastServer = NULL; int iLevel = 0; int iServerConfigGeneration = g_pServerPool->GetGeneration(); while (!IsStopped()) { Status = adFailed; SetStatus(adWaiting); while (!m_pConnection && !(IsStopped() || iServerConfigGeneration != g_pServerPool->GetGeneration())) { m_pConnection = g_pServerPool->GetConnection(iLevel, pWantServer, &failedServers); usleep(5 * 1000); } SetLastUpdateTimeNow(); SetStatus(adRunning); if (IsStopped() || g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2() || iServerConfigGeneration != g_pServerPool->GetGeneration()) { Status = adRetry; break; } pLastServer = m_pConnection->GetNewsServer(); m_pConnection->SetSuppressErrors(false); // test connection bool bConnected = m_pConnection && m_pConnection->Connect(); if (bConnected && !IsStopped()) { NewsServer* pNewsServer = m_pConnection->GetNewsServer(); detail("Downloading %s @ %s (%s)", m_szInfoName, pNewsServer->GetName(), m_pConnection->GetHost()); Status = Download(); if (Status == adFinished || Status == adFailed || Status == adNotFound || Status == adCrcError) { m_ServerStats.SetStat(pNewsServer->GetID(), Status == adFinished ? 1 : 0, Status == adFinished ? 0 : 1, false); } } if (bConnected) { if (Status == adConnectError) { m_pConnection->Disconnect(); bConnected = false; Status = adFailed; } else { // freeing connection allows other threads to start. // we doing this only if the problem was with article or group. // if the problem occurs by connecting or authorization we do not // free the connection, to prevent starting of thousands of threads // (cause each of them will also free it's connection after the // same connect-error). FreeConnection(Status == adFinished || Status == adNotFound); } } if (Status == adFinished || Status == adFatalError) { break; } pWantServer = NULL; if (bConnected && Status == adFailed) { iRemainedRetries--; } if (!bConnected || (Status == adFailed && iRemainedRetries > 0)) { pWantServer = pLastServer; } if (pWantServer && !(IsStopped() || g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2() || iServerConfigGeneration != g_pServerPool->GetGeneration())) { detail("Waiting %i sec to retry", g_pOptions->GetRetryInterval()); SetStatus(adWaiting); int msec = 0; while (!(IsStopped() || g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2() || iServerConfigGeneration != g_pServerPool->GetGeneration()) && msec < g_pOptions->GetRetryInterval() * 1000) { usleep(100 * 1000); msec += 100; } SetLastUpdateTimeNow(); SetStatus(adRunning); } if (IsStopped() || g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2() || iServerConfigGeneration != g_pServerPool->GetGeneration()) { Status = adRetry; break; } if (!pWantServer) { failedServers.push_back(pLastServer); // if all servers from current level were tried, increase level // if all servers from all levels were tried, break the loop with failure status bool bAllServersOnLevelFailed = true; for (Servers::iterator it = g_pServerPool->GetServers()->begin(); it != g_pServerPool->GetServers()->end(); it++) { NewsServer* pCandidateServer = *it; if (pCandidateServer->GetNormLevel() == iLevel) { bool bServerFailed = !pCandidateServer->GetActive() || pCandidateServer->GetMaxConnections() == 0; if (!bServerFailed) { for (Servers::iterator it = failedServers.begin(); it != failedServers.end(); it++) { NewsServer* pIgnoreServer = *it; if (pIgnoreServer == pCandidateServer || (pIgnoreServer->GetGroup() > 0 && pIgnoreServer->GetGroup() == pCandidateServer->GetGroup() && pIgnoreServer->GetNormLevel() == pCandidateServer->GetNormLevel())) { bServerFailed = true; break; } } } if (!bServerFailed) { bAllServersOnLevelFailed = false; break; } } } if (bAllServersOnLevelFailed) { if (iLevel < g_pServerPool->GetMaxNormLevel()) { detail("Article %s @ all level %i servers failed, increasing level", m_szInfoName, iLevel); iLevel++; } else { detail("Article %s @ all servers failed", m_szInfoName); Status = adFailed; break; } } iRemainedRetries = iRetries; } } FreeConnection(Status == adFinished); if (m_bDuplicate) { Status = adFinished; } if (Status != adFinished && Status != adRetry) { Status = adFailed; } if (IsStopped()) { detail("Download %s cancelled", m_szInfoName); Status = adRetry; } if (Status == adFailed) { detail("Download %s failed", m_szInfoName); } SetStatus(Status); Notify(NULL); debug("Exiting ArticleDownloader-loop"); } ArticleDownloader::EStatus ArticleDownloader::Download() { const char* szResponse = NULL; EStatus Status = adRunning; if (m_pConnection->GetNewsServer()->GetJoinGroup()) { // change group for (FileInfo::Groups::iterator it = m_pFileInfo->GetGroups()->begin(); it != m_pFileInfo->GetGroups()->end(); it++) { szResponse = m_pConnection->JoinGroup(*it); if (szResponse && !strncmp(szResponse, "2", 1)) { break; } } Status = CheckResponse(szResponse, "could not join group"); if (Status != adFinished) { return Status; } } // retrieve article char tmp[1024]; snprintf(tmp, 1024, "ARTICLE %s\r\n", m_pArticleInfo->GetMessageID()); tmp[1024-1] = '\0'; for (int retry = 3; retry > 0; retry--) { szResponse = m_pConnection->Request(tmp); if ((szResponse && !strncmp(szResponse, "2", 1)) || m_pConnection->GetAuthError()) { break; } } Status = CheckResponse(szResponse, "could not fetch article"); if (Status != adFinished) { return Status; } // positive answer! if (g_pOptions->GetDecode()) { m_YDecoder.Clear(); m_YDecoder.SetAutoSeek(g_pOptions->GetDirectWrite()); m_YDecoder.SetCrcCheck(g_pOptions->GetCrcCheck()); m_UDecoder.Clear(); } m_pOutFile = NULL; bool bBody = false; bool bEnd = false; const int LineBufSize = 1024*10; char* szLineBuf = (char*)malloc(LineBufSize); Status = adRunning; while (!IsStopped()) { SetLastUpdateTimeNow(); // Throttle the bandwidth while (!IsStopped() && (g_pOptions->GetDownloadRate() > 0.0f) && (g_pDownloadSpeedMeter->CalcCurrentDownloadSpeed() > g_pOptions->GetDownloadRate())) { SetLastUpdateTimeNow(); usleep(10 * 1000); } int iLen = 0; char* line = m_pConnection->ReadLine(szLineBuf, LineBufSize, &iLen); g_pDownloadSpeedMeter->AddSpeedReading(iLen); // Have we encountered a timeout? if (!line) { if (!IsStopped()) { detail("Article %s @ %s (%s) failed: Unexpected end of article", m_szInfoName, m_pConnection->GetNewsServer()->GetName(), m_pConnection->GetHost()); } Status = adFailed; break; } //detect end of article if (!strcmp(line, ".\r\n") || !strcmp(line, ".\n")) { bEnd = true; break; } //detect lines starting with "." (marked as "..") if (!strncmp(line, "..", 2)) { line++; iLen--; } if (!bBody) { // detect body of article if (*line == '\r' || *line == '\n') { bBody = true; } // check id of returned article 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 detail("Article %s @ %s (%s) failed: Wrong message-id, expected %s, returned %s", m_szInfoName, m_pConnection->GetNewsServer()->GetName(), m_pConnection->GetHost(), m_pArticleInfo->GetMessageID(), p); Status = adFailed; break; } } } else if (m_eFormat == Decoder::efUnknown && g_pOptions->GetDecode()) { m_eFormat = Decoder::DetectFormat(line, iLen); } // write to output file if (((bBody && m_eFormat != Decoder::efUnknown) || !g_pOptions->GetDecode()) && !Write(line, iLen)) { Status = adFatalError; break; } } free(szLineBuf); if (m_pOutFile) { fclose(m_pOutFile); } if (!bEnd && Status == adRunning && !IsStopped()) { detail("Article %s @ %s (%s) failed: article incomplete", m_szInfoName, m_pConnection->GetNewsServer()->GetName(), m_pConnection->GetHost()); Status = adFailed; } if (IsStopped()) { Status = adFailed; } if (Status == adRunning) { FreeConnection(true); return DecodeCheck(); } else { remove(m_szTempFilename); return Status; } } ArticleDownloader::EStatus ArticleDownloader::CheckResponse(const char* szResponse, const char* szComment) { if (!szResponse) { if (!IsStopped()) { detail("Article %s @ %s (%s) failed, %s: Connection closed by remote host", m_szInfoName, m_pConnection->GetNewsServer()->GetName(), m_pConnection->GetHost(), szComment); } return adConnectError; } else if (m_pConnection->GetAuthError() || !strncmp(szResponse, "400", 3) || !strncmp(szResponse, "499", 3)) { detail("Article %s @ %s (%s) failed, %s: %s", m_szInfoName, m_pConnection->GetNewsServer()->GetName(), m_pConnection->GetHost(), szComment, szResponse); return adConnectError; } else if (!strncmp(szResponse, "41", 2) || !strncmp(szResponse, "42", 2) || !strncmp(szResponse, "43", 2)) { detail("Article %s @ %s (%s) failed, %s: %s", m_szInfoName, m_pConnection->GetNewsServer()->GetName(), m_pConnection->GetHost(), szComment, szResponse); return adNotFound; } else if (!strncmp(szResponse, "2", 1)) { // OK return adFinished; } else { // unknown error, no special handling detail("Article %s @ %s (%s) failed, %s: %s", m_szInfoName, m_pConnection->GetNewsServer()->GetName(), m_pConnection->GetHost(), szComment, szResponse); return adFailed; } } bool ArticleDownloader::Write(char* szLine, int iLen) { if (!m_pOutFile && !PrepareFile(szLine)) { return false; } if (g_pOptions->GetDecode()) { bool bOK = false; if (m_eFormat == Decoder::efYenc) { bOK = m_YDecoder.Write(szLine, iLen, m_pOutFile); } else if (m_eFormat == Decoder::efUx) { bOK = m_UDecoder.Write(szLine, iLen, m_pOutFile); } else { detail("Decoding %s failed: unsupported encoding", m_szInfoName); return false; } if (!bOK) { debug("Failed line: %s", szLine); detail("Decoding %s failed", m_szInfoName); } return bOK; } else { return fwrite(szLine, 1, iLen, m_pOutFile) > 0; } } bool ArticleDownloader::PrepareFile(char* szLine) { bool bOpen = false; // prepare file for writing if (m_eFormat == Decoder::efYenc) { if (!strncmp(szLine, "=ybegin ", 8)) { if (g_pOptions->GetDupeCheck() && m_pFileInfo->GetNZBInfo()->GetDupeMode() != dmForce && !m_pFileInfo->GetNZBInfo()->GetManyDupeFiles()) { m_pFileInfo->LockOutputFile(); bool bOutputInitialized = m_pFileInfo->GetOutputInitialized(); if (!bOutputInitialized) { char* pb = strstr(szLine, " name="); if (pb) { pb += 6; //=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 (!g_pOptions->GetDirectWrite()) { m_pFileInfo->SetOutputInitialized(true); } m_pFileInfo->UnlockOutputFile(); if (!bOutputInitialized && m_szArticleFilename && Util::FileExists(m_pFileInfo->GetNZBInfo()->GetDestDir(), m_szArticleFilename)) { m_bDuplicate = true; return false; } } if (g_pOptions->GetDirectWrite()) { char* pb = strstr(szLine, " size="); if (pb) { m_pFileInfo->LockOutputFile(); if (!m_pFileInfo->GetOutputInitialized()) { pb += 6; //=strlen(" size=") long iArticleFilesize = atol(pb); if (!CreateOutputFile(iArticleFilesize)) { m_pFileInfo->UnlockOutputFile(); return false; } m_pFileInfo->SetOutputInitialized(true); } m_pFileInfo->UnlockOutputFile(); bOpen = true; } } else { bOpen = true; } } } else { bOpen = true; } if (bOpen) { bool bDirectWrite = g_pOptions->GetDirectWrite() && m_eFormat == Decoder::efYenc; const char* szFilename = bDirectWrite ? m_szOutputFilename : m_szTempFilename; m_pOutFile = fopen(szFilename, bDirectWrite ? "rb+" : "wb"); if (!m_pOutFile) { char szSysErrStr[256]; error("Could not %s file %s! Errcode: %i, %s", bDirectWrite ? "open" : "create", szFilename, errno, Util::GetLastErrorMessage(szSysErrStr, sizeof(szSysErrStr))); 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; } /* creates output file and subdirectores */ bool ArticleDownloader::CreateOutputFile(int iSize) { if (g_pOptions->GetDirectWrite() && Util::FileExists(m_szOutputFilename) && Util::FileSize(m_szOutputFilename) == iSize) { // keep existing old file from previous program session return true; } // delete eventually existing old file from previous program session remove(m_szOutputFilename); // ensure the directory exist char szDestDir[1024]; int iMaxlen = Util::BaseFileName(m_szOutputFilename) - m_szOutputFilename; if (iMaxlen > 1024-1) iMaxlen = 1024-1; strncpy(szDestDir, m_szOutputFilename, iMaxlen); szDestDir[iMaxlen] = '\0'; char szErrBuf[1024]; if (!Util::ForceDirectories(szDestDir, szErrBuf, sizeof(szErrBuf))) { error("Could not create directory %s: %s", szDestDir, szErrBuf); return false; } if (!Util::CreateSparseFile(m_szOutputFilename, iSize)) { error("Could not create file %s", m_szOutputFilename); return false; } return true; } void ArticleDownloader::BuildOutputFilename() { char szFilename[1024]; snprintf(szFilename, 1024, "%s%i.%03i", g_pOptions->GetTempDir(), m_pFileInfo->GetID(), m_pArticleInfo->GetPartNumber()); szFilename[1024-1] = '\0'; m_pArticleInfo->SetResultFilename(szFilename); char tmpname[1024]; snprintf(tmpname, 1024, "%s.tmp", szFilename); tmpname[1024-1] = '\0'; SetTempFilename(tmpname); if (g_pOptions->GetDirectWrite()) { m_pFileInfo->LockOutputFile(); if (m_pFileInfo->GetOutputFilename()) { strncpy(szFilename, m_pFileInfo->GetOutputFilename(), 1024); szFilename[1024-1] = '\0'; } else { snprintf(szFilename, 1024, "%s%c%i.out.tmp", m_pFileInfo->GetNZBInfo()->GetDestDir(), (int)PATH_SEPARATOR, m_pFileInfo->GetID()); szFilename[1024-1] = '\0'; m_pFileInfo->SetOutputFilename(szFilename); } m_pFileInfo->UnlockOutputFile(); SetOutputFilename(szFilename); } } ArticleDownloader::EStatus ArticleDownloader::DecodeCheck() { bool bDirectWrite = g_pOptions->GetDirectWrite() && m_eFormat == Decoder::efYenc; if (g_pOptions->GetDecode()) { SetStatus(adDecoding); Decoder* pDecoder = NULL; if (m_eFormat == Decoder::efYenc) { pDecoder = &m_YDecoder; } else if (m_eFormat == Decoder::efUx) { pDecoder = &m_UDecoder; } else { detail("Decoding %s failed: no binary data or unsupported encoding format", m_szInfoName); return adFailed; } Decoder::EStatus eStatus = pDecoder->Check(); bool bOK = eStatus == Decoder::eFinished; if (!bDirectWrite && bOK) { if (!Util::MoveFile(m_szTempFilename, m_szResultFilename)) { error("Could not rename file %s to %s! Errcode: %i", m_szTempFilename, m_szResultFilename, errno); } } if (!m_szArticleFilename && pDecoder->GetArticleFilename()) { m_szArticleFilename = strdup(pDecoder->GetArticleFilename()); } remove(m_szTempFilename); if (bOK) { detail("Successfully downloaded %s", m_szInfoName); if (bDirectWrite && g_pOptions->GetContinuePartial()) { // create empty flag-file to indicate that the artcile was downloaded FILE* flagfile = fopen(m_szResultFilename, "wb"); if (!flagfile) { error("Could not create file %s", m_szResultFilename); // this error can be ignored } fclose(flagfile); } return adFinished; } else { remove(m_szResultFilename); if (eStatus == Decoder::eCrcError) { detail("Decoding %s failed: CRC-Error", m_szInfoName); return adCrcError; } else if (eStatus == Decoder::eArticleIncomplete) { detail("Decoding %s failed: article incomplete", m_szInfoName); return adFailed; } else if (eStatus == Decoder::eInvalidSize) { detail("Decoding %s failed: size mismatch", m_szInfoName); return adFailed; } else if (eStatus == Decoder::eNoBinaryData) { detail("Decoding %s failed: no binary data found", m_szInfoName); return adFailed; } else { detail("Decoding %s failed", m_szInfoName); return adFailed; } } } else { // rawmode if (Util::MoveFile(m_szTempFilename, m_szResultFilename)) { detail("Article %s successfully downloaded", m_szInfoName); } else { error("Could not move file %s to %s! Errcode: %i", m_szTempFilename, m_szResultFilename, errno); } return adFinished; } } void ArticleDownloader::LogDebugInfo() { char szTime[50]; #ifdef HAVE_CTIME_R_3 ctime_r(&m_tLastUpdateTime, szTime, 50); #else ctime_r(&m_tLastUpdateTime, szTime); #endif debug(" Download: status=%i, LastUpdateTime=%s, filename=%s", m_eStatus, szTime, Util::BaseFileName(GetTempFilename())); } void ArticleDownloader::Stop() { debug("Trying to stop ArticleDownloader"); Thread::Stop(); m_mutexConnection.Lock(); if (m_pConnection) { m_pConnection->SetSuppressErrors(true); m_pConnection->Cancel(); } m_mutexConnection.Unlock(); debug("ArticleDownloader stopped successfully"); } bool ArticleDownloader::Terminate() { NNTPConnection* pConnection = m_pConnection; bool terminated = Kill(); if (terminated && pConnection) { debug("Terminating connection"); pConnection->SetSuppressErrors(true); pConnection->Cancel(); pConnection->Disconnect(); g_pServerPool->FreeConnection(pConnection, true); } return terminated; } void ArticleDownloader::FreeConnection(bool bKeepConnected) { if (m_pConnection) { debug("Releasing connection"); m_mutexConnection.Lock(); if (!bKeepConnected || m_pConnection->GetStatus() == Connection::csCancelled) { m_pConnection->Disconnect(); } g_pServerPool->FreeConnection(m_pConnection, true); m_pConnection = NULL; m_mutexConnection.Unlock(); } } void ArticleDownloader::CompleteFileParts() { debug("Completing file parts"); debug("ArticleFilename: %s", m_pFileInfo->GetFilename()); SetStatus(adJoining); bool bDirectWrite = g_pOptions->GetDirectWrite() && m_pFileInfo->GetOutputInitialized(); char szNZBName[1024]; char szNZBDestDir[1024]; // the locking is needed for accessing the memebers of NZBInfo g_pDownloadQueueHolder->LockQueue(); strncpy(szNZBName, m_pFileInfo->GetNZBInfo()->GetName(), 1024); strncpy(szNZBDestDir, m_pFileInfo->GetNZBInfo()->GetDestDir(), 1024); g_pDownloadQueueHolder->UnlockQueue(); szNZBName[1024-1] = '\0'; szNZBDestDir[1024-1] = '\0'; char InfoFilename[1024]; snprintf(InfoFilename, 1024, "%s%c%s", szNZBName, (int)PATH_SEPARATOR, m_pFileInfo->GetFilename()); InfoFilename[1024-1] = '\0'; if (!g_pOptions->GetDecode()) { detail("Moving articles for %s", InfoFilename); } else if (bDirectWrite) { detail("Checking articles for %s", InfoFilename); } else { detail("Joining articles for %s", InfoFilename); } // Ensure the DstDir is created char szErrBuf[1024]; if (!Util::ForceDirectories(szNZBDestDir, szErrBuf, sizeof(szErrBuf))) { error("Could not create directory %s: %s", szNZBDestDir, szErrBuf); SetStatus(adJoined); return; } char ofn[1024]; Util::MakeUniqueFilename(ofn, 1024, szNZBDestDir, m_pFileInfo->GetFilename()); FILE* outfile = NULL; char tmpdestfile[1024]; snprintf(tmpdestfile, 1024, "%s.tmp", ofn); tmpdestfile[1024-1] = '\0'; if (g_pOptions->GetDecode() && !bDirectWrite) { remove(tmpdestfile); outfile = fopen(tmpdestfile, "wb+"); if (!outfile) { error("Could not create file %s!", tmpdestfile); SetStatus(adJoined); 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->GetDecode()) { remove(tmpdestfile); if (!Util::CreateDirectory(ofn)) { error("Could not create directory %s! Errcode: %i", ofn, errno); SetStatus(adJoined); return; } } bool complete = true; int iBrokenCount = 0; static const int BUFFER_SIZE = 1024 * 50; char* buffer = NULL; if (g_pOptions->GetDecode() && !bDirectWrite) { buffer = (char*)malloc(BUFFER_SIZE); } for (FileInfo::Articles::iterator it = m_pFileInfo->GetArticles()->begin(); it != m_pFileInfo->GetArticles()->end(); it++) { ArticleInfo* pa = *it; if (pa->GetStatus() != ArticleInfo::aiFinished) { iBrokenCount++; complete = false; } else if (g_pOptions->GetDecode() && !bDirectWrite) { FILE* infile; const char* fn = pa->GetResultFilename(); infile = fopen(fn, "rb"); if (infile) { int cnt = BUFFER_SIZE; while (cnt == BUFFER_SIZE) { cnt = (int)fread(buffer, 1, BUFFER_SIZE, infile); fwrite(buffer, 1, cnt, outfile); SetLastUpdateTimeNow(); } fclose(infile); } else { complete = false; iBrokenCount++; detail("Could not find file %s. Status is broken", fn); } } else if (!g_pOptions->GetDecode()) { const char* fn = pa->GetResultFilename(); char dstFileName[1024]; snprintf(dstFileName, 1024, "%s%c%03i", ofn, (int)PATH_SEPARATOR, pa->GetPartNumber()); dstFileName[1024-1] = '\0'; if (!Util::MoveFile(fn, dstFileName)) { error("Could not move file %s to %s! Errcode: %i", fn, dstFileName, errno); } } } free(buffer); if (outfile) { fclose(outfile); if (!Util::MoveFile(tmpdestfile, ofn)) { error("Could not move file %s to %s! Errcode: %i", tmpdestfile, ofn, errno); } } if (bDirectWrite) { if (!Util::MoveFile(m_szOutputFilename, ofn)) { error("Could not move file %s to %s! Errcode: %i", m_szOutputFilename, ofn, errno); } // if destination directory was changed delete the old directory (if empty) int iLen = strlen(szNZBDestDir); if (!(!strncmp(szNZBDestDir, m_szOutputFilename, iLen) && (m_szOutputFilename[iLen] == PATH_SEPARATOR || m_szOutputFilename[iLen] == ALT_PATH_SEPARATOR))) { debug("Checking old dir for: %s", m_szOutputFilename); char szOldDestDir[1024]; int iMaxlen = Util::BaseFileName(m_szOutputFilename) - m_szOutputFilename; if (iMaxlen > 1024-1) iMaxlen = 1024-1; strncpy(szOldDestDir, m_szOutputFilename, iMaxlen); szOldDestDir[iMaxlen] = '\0'; if (Util::DirEmpty(szOldDestDir)) { debug("Deleting old dir: %s", szOldDestDir); rmdir(szOldDestDir); } } } if (!bDirectWrite || g_pOptions->GetContinuePartial()) { for (FileInfo::Articles::iterator it = m_pFileInfo->GetArticles()->begin(); it != m_pFileInfo->GetArticles()->end(); it++) { ArticleInfo* pa = *it; remove(pa->GetResultFilename()); } } if (complete) { info("Successfully downloaded %s", InfoFilename); } else { warn("%i of %i article downloads failed for \"%s\"", iBrokenCount + m_pFileInfo->GetMissedArticles(), m_pFileInfo->GetTotalArticles(), InfoFilename); if (g_pOptions->GetCreateBrokenLog()) { char szBrokenLogName[1024]; snprintf(szBrokenLogName, 1024, "%s%c_brokenlog.txt", szNZBDestDir, (int)PATH_SEPARATOR); szBrokenLogName[1024-1] = '\0'; FILE* file = fopen(szBrokenLogName, "ab"); fprintf(file, "%s (%i/%i)%s", m_pFileInfo->GetFilename(), m_pFileInfo->GetTotalArticles() - iBrokenCount - m_pFileInfo->GetMissedArticles(), m_pFileInfo->GetTotalArticles(), LINE_ENDING); fclose(file); } } // the locking is needed for accessing the members of NZBInfo g_pDownloadQueueHolder->LockQueue(); m_pFileInfo->GetNZBInfo()->GetCompletedFiles()->push_back(strdup(ofn)); if (strcmp(m_pFileInfo->GetNZBInfo()->GetDestDir(), szNZBDestDir)) { // destination directory was changed during completion, need to move the file MoveCompletedFiles(m_pFileInfo->GetNZBInfo(), szNZBDestDir); } g_pDownloadQueueHolder->UnlockQueue(); SetStatus(adJoined); } bool ArticleDownloader::MoveCompletedFiles(NZBInfo* pNZBInfo, const char* szOldDestDir) { if (pNZBInfo->GetCompletedFiles()->empty()) { return true; } // Ensure the DstDir is created char szErrBuf[1024]; if (!Util::ForceDirectories(pNZBInfo->GetDestDir(), szErrBuf, sizeof(szErrBuf))) { error("Could not create directory %s: %s", pNZBInfo->GetDestDir(), szErrBuf); return false; } // move already downloaded files to new destination for (NZBInfo::Files::iterator it = pNZBInfo->GetCompletedFiles()->begin(); it != pNZBInfo->GetCompletedFiles()->end(); it++) { char* szFileName = *it; char szNewFileName[1024]; snprintf(szNewFileName, 1024, "%s%c%s", pNZBInfo->GetDestDir(), (int)PATH_SEPARATOR, Util::BaseFileName(szFileName)); szNewFileName[1024-1] = '\0'; // check if file was not moved already if (strcmp(szFileName, szNewFileName)) { // prevent overwriting of existing files Util::MakeUniqueFilename(szNewFileName, 1024, pNZBInfo->GetDestDir(), Util::BaseFileName(szFileName)); detail("Moving file %s to %s", szFileName, szNewFileName); if (Util::MoveFile(szFileName, szNewFileName)) { free(szFileName); *it = strdup(szNewFileName); } else { error("Could not move file %s to %s! Errcode: %i", szFileName, szNewFileName, errno); } } } // move brokenlog.txt if (g_pOptions->GetCreateBrokenLog()) { char szOldBrokenLogName[1024]; snprintf(szOldBrokenLogName, 1024, "%s%c_brokenlog.txt", szOldDestDir, (int)PATH_SEPARATOR); szOldBrokenLogName[1024-1] = '\0'; if (Util::FileExists(szOldBrokenLogName)) { char szBrokenLogName[1024]; snprintf(szBrokenLogName, 1024, "%s%c_brokenlog.txt", pNZBInfo->GetDestDir(), (int)PATH_SEPARATOR); szBrokenLogName[1024-1] = '\0'; detail("Moving file %s to %s", szOldBrokenLogName, szBrokenLogName); if (Util::FileExists(szBrokenLogName)) { // copy content to existing new file, then delete old file FILE* outfile; outfile = fopen(szBrokenLogName, "ab"); if (outfile) { FILE* infile; infile = fopen(szOldBrokenLogName, "rb"); if (infile) { static const int BUFFER_SIZE = 1024 * 50; int cnt = BUFFER_SIZE; char* buffer = (char*)malloc(BUFFER_SIZE); while (cnt == BUFFER_SIZE) { cnt = (int)fread(buffer, 1, BUFFER_SIZE, infile); fwrite(buffer, 1, cnt, outfile); } fclose(infile); free(buffer); remove(szOldBrokenLogName); } else { error("Could not open file %s", szOldBrokenLogName); } fclose(outfile); } else { error("Could not open file %s", szBrokenLogName); } } else { // move to new destination if (!Util::MoveFile(szOldBrokenLogName, szBrokenLogName)) { error("Could not move file %s to %s! Errcode: %i", szOldBrokenLogName, szBrokenLogName, errno); } } } } // delete old directory (if empty) if (Util::DirEmpty(szOldDestDir)) { rmdir(szOldDestDir); } return true; } nzbget-12.0+dfsg/ArticleDownloader.h000066400000000000000000000071341226450633000174250ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 794 $ * $Date: 2013-08-16 23:53:32 +0200 (Fri, 16 Aug 2013) $ * */ #ifndef ARTICLEDOWNLOADER_H #define ARTICLEDOWNLOADER_H #include #include "Observer.h" #include "DownloadInfo.h" #include "Thread.h" #include "NNTPConnection.h" #include "Decoder.h" class ArticleDownloader : public Thread, public Subject { public: enum EStatus { adUndefined, adRunning, adWaiting, adFinished, adFailed, adRetry, adCrcError, adDecoding, adJoining, adJoined, adNotFound, adConnectError, adFatalError }; private: FileInfo* m_pFileInfo; ArticleInfo* m_pArticleInfo; NNTPConnection* m_pConnection; EStatus m_eStatus; Mutex m_mutexConnection; const char* m_szResultFilename; char* m_szTempFilename; char* m_szArticleFilename; char* m_szInfoName; char* m_szOutputFilename; time_t m_tLastUpdateTime; Decoder::EFormat m_eFormat; YDecoder m_YDecoder; UDecoder m_UDecoder; FILE* m_pOutFile; bool m_bDuplicate; ServerStatList m_ServerStats; EStatus Download(); bool Write(char* szLine, int iLen); bool PrepareFile(char* szLine); bool CreateOutputFile(int iSize); void BuildOutputFilename(); EStatus DecodeCheck(); void FreeConnection(bool bKeepConnected); EStatus CheckResponse(const char* szResponse, const char* szComment); void SetStatus(EStatus eStatus) { m_eStatus = eStatus; } const char* GetTempFilename() { return m_szTempFilename; } void SetTempFilename(const char* v); void SetOutputFilename(const char* v); public: ArticleDownloader(); ~ArticleDownloader(); void SetFileInfo(FileInfo* pFileInfo) { m_pFileInfo = pFileInfo; } FileInfo* GetFileInfo() { return m_pFileInfo; } void SetArticleInfo(ArticleInfo* pArticleInfo) { m_pArticleInfo = pArticleInfo; } ArticleInfo* GetArticleInfo() { return m_pArticleInfo; } EStatus GetStatus() { return m_eStatus; } ServerStatList* GetServerStats() { return &m_ServerStats; } virtual void Run(); virtual void Stop(); bool Terminate(); time_t GetLastUpdateTime() { return m_tLastUpdateTime; } void SetLastUpdateTimeNow() { m_tLastUpdateTime = ::time(NULL); } const char* GetArticleFilename() { return m_szArticleFilename; } void SetInfoName(const char* v); const char* GetInfoName() { return m_szInfoName; } void CompleteFileParts(); static bool MoveCompletedFiles(NZBInfo* pNZBInfo, const char* szOldDestDir); void SetConnection(NNTPConnection* pConnection) { m_pConnection = pConnection; } void LogDebugInfo(); }; class DownloadSpeedMeter { public: virtual ~DownloadSpeedMeter() {}; virtual int CalcCurrentDownloadSpeed() = 0; virtual void AddSpeedReading(int iBytes) = 0; }; #endif nzbget-12.0+dfsg/BinRpc.cpp000066400000000000000000001105071226450633000155320ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2005 Bo Cordes Petersen * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 911 $ * $Date: 2013-11-24 20:29:52 +0100 (Sun, 24 Nov 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #ifndef WIN32 #include #include #include #include #endif #include "nzbget.h" #include "BinRpc.h" #include "Log.h" #include "Options.h" #include "QueueCoordinator.h" #include "UrlCoordinator.h" #include "QueueEditor.h" #include "PrePostProcessor.h" #include "Util.h" #include "DownloadInfo.h" #include "Scanner.h" extern Options* g_pOptions; extern QueueCoordinator* g_pQueueCoordinator; extern UrlCoordinator* g_pUrlCoordinator; extern PrePostProcessor* g_pPrePostProcessor; extern Scanner* g_pScanner; extern void ExitProc(); extern void Reload(); const char* g_szMessageRequestNames[] = { "N/A", "Download", "Pause/Unpause", "List", "Set download rate", "Dump debug", "Edit queue", "Log", "Quit", "Reload", "Version", "Post-queue", "Write log", "Scan", "Pause/Unpause postprocessor", "History", "Download URL", "URL-queue" }; const unsigned int g_iMessageRequestSizes[] = { 0, sizeof(SNZBDownloadRequest), sizeof(SNZBPauseUnpauseRequest), sizeof(SNZBListRequest), sizeof(SNZBSetDownloadRateRequest), sizeof(SNZBDumpDebugRequest), sizeof(SNZBEditQueueRequest), sizeof(SNZBLogRequest), sizeof(SNZBShutdownRequest), sizeof(SNZBReloadRequest), sizeof(SNZBVersionRequest), sizeof(SNZBPostQueueRequest), sizeof(SNZBWriteLogRequest), sizeof(SNZBScanRequest), sizeof(SNZBHistoryRequest), sizeof(SNZBDownloadUrlRequest), sizeof(SNZBUrlQueueRequest) }; //***************************************************************** // BinProcessor BinRpcProcessor::BinRpcProcessor() { m_MessageBase.m_iSignature = (int)NZBMESSAGE_SIGNATURE; } void BinRpcProcessor::Execute() { // Read the first package which needs to be a request if (!m_pConnection->Recv(((char*)&m_MessageBase) + sizeof(m_MessageBase.m_iSignature), sizeof(m_MessageBase) - sizeof(m_MessageBase.m_iSignature))) { warn("Non-nzbget request received on port %i from %s", g_pOptions->GetControlPort(), m_pConnection->GetRemoteAddr()); return; } if ((strlen(g_pOptions->GetControlUsername()) > 0 && strcmp(m_MessageBase.m_szUsername, g_pOptions->GetControlUsername())) || strcmp(m_MessageBase.m_szPassword, g_pOptions->GetControlPassword())) { warn("nzbget request received on port %i from %s, but username or password invalid", g_pOptions->GetControlPort(), m_pConnection->GetRemoteAddr()); return; } debug("%s request received from %s", g_szMessageRequestNames[ntohl(m_MessageBase.m_iType)], m_pConnection->GetRemoteAddr()); Dispatch(); } void BinRpcProcessor::Dispatch() { if (ntohl(m_MessageBase.m_iType) >= (int)eRemoteRequestDownload && ntohl(m_MessageBase.m_iType) <= (int)eRemoteRequestHistory && g_iMessageRequestSizes[ntohl(m_MessageBase.m_iType)] != ntohl(m_MessageBase.m_iStructSize)) { error("Invalid size of request: expected %i Bytes, but received %i Bytes", g_iMessageRequestSizes[ntohl(m_MessageBase.m_iType)], ntohl(m_MessageBase.m_iStructSize)); return; } BinCommand* command = NULL; switch (ntohl(m_MessageBase.m_iType)) { case eRemoteRequestDownload: command = new DownloadBinCommand(); break; case eRemoteRequestList: command = new ListBinCommand(); break; case eRemoteRequestLog: command = new LogBinCommand(); break; case eRemoteRequestPauseUnpause: command = new PauseUnpauseBinCommand(); break; case eRemoteRequestEditQueue: command = new EditQueueBinCommand(); break; case eRemoteRequestSetDownloadRate: command = new SetDownloadRateBinCommand(); break; case eRemoteRequestDumpDebug: command = new DumpDebugBinCommand(); break; case eRemoteRequestShutdown: command = new ShutdownBinCommand(); break; case eRemoteRequestReload: command = new ReloadBinCommand(); break; case eRemoteRequestVersion: command = new VersionBinCommand(); break; case eRemoteRequestPostQueue: command = new PostQueueBinCommand(); break; case eRemoteRequestWriteLog: command = new WriteLogBinCommand(); break; case eRemoteRequestScan: command = new ScanBinCommand(); break; case eRemoteRequestHistory: command = new HistoryBinCommand(); break; case eRemoteRequestDownloadUrl: command = new DownloadUrlBinCommand(); break; case eRemoteRequestUrlQueue: command = new UrlQueueBinCommand(); break; default: error("Received unsupported request %i", ntohl(m_MessageBase.m_iType)); break; } if (command) { command->SetConnection(m_pConnection); command->SetMessageBase(&m_MessageBase); command->Execute(); delete command; } } //***************************************************************** // Commands void BinCommand::SendBoolResponse(bool bSuccess, const char* szText) { // all bool-responses have the same format of structure, we use SNZBDownloadResponse here SNZBDownloadResponse BoolResponse; memset(&BoolResponse, 0, sizeof(BoolResponse)); BoolResponse.m_MessageBase.m_iSignature = htonl(NZBMESSAGE_SIGNATURE); BoolResponse.m_MessageBase.m_iStructSize = htonl(sizeof(BoolResponse)); BoolResponse.m_bSuccess = htonl(bSuccess); int iTextLen = strlen(szText) + 1; BoolResponse.m_iTrailingDataLength = htonl(iTextLen); // Send the request answer m_pConnection->Send((char*) &BoolResponse, sizeof(BoolResponse)); m_pConnection->Send((char*)szText, iTextLen); } bool BinCommand::ReceiveRequest(void* pBuffer, int iSize) { memcpy(pBuffer, m_pMessageBase, sizeof(SNZBRequestBase)); iSize -= sizeof(SNZBRequestBase); if (iSize > 0) { if (!m_pConnection->Recv(((char*)pBuffer) + sizeof(SNZBRequestBase), iSize)) { error("invalid request"); return false; } } return true; } void PauseUnpauseBinCommand::Execute() { SNZBPauseUnpauseRequest PauseUnpauseRequest; if (!ReceiveRequest(&PauseUnpauseRequest, sizeof(PauseUnpauseRequest))) { return; } g_pOptions->SetResumeTime(0); switch (ntohl(PauseUnpauseRequest.m_iAction)) { case eRemotePauseUnpauseActionDownload: g_pOptions->SetPauseDownload(ntohl(PauseUnpauseRequest.m_bPause)); break; case eRemotePauseUnpauseActionDownload2: g_pOptions->SetPauseDownload2(ntohl(PauseUnpauseRequest.m_bPause)); break; case eRemotePauseUnpauseActionPostProcess: g_pOptions->SetPausePostProcess(ntohl(PauseUnpauseRequest.m_bPause)); break; case eRemotePauseUnpauseActionScan: g_pOptions->SetPauseScan(ntohl(PauseUnpauseRequest.m_bPause)); break; } SendBoolResponse(true, "Pause-/Unpause-Command completed successfully"); } void SetDownloadRateBinCommand::Execute() { SNZBSetDownloadRateRequest SetDownloadRequest; if (!ReceiveRequest(&SetDownloadRequest, sizeof(SetDownloadRequest))) { return; } g_pOptions->SetDownloadRate(ntohl(SetDownloadRequest.m_iDownloadRate)); SendBoolResponse(true, "Rate-Command completed successfully"); } void DumpDebugBinCommand::Execute() { SNZBDumpDebugRequest DumpDebugRequest; if (!ReceiveRequest(&DumpDebugRequest, sizeof(DumpDebugRequest))) { return; } g_pQueueCoordinator->LogDebugInfo(); g_pUrlCoordinator->LogDebugInfo(); SendBoolResponse(true, "Debug-Command completed successfully"); } void ShutdownBinCommand::Execute() { SNZBShutdownRequest ShutdownRequest; if (!ReceiveRequest(&ShutdownRequest, sizeof(ShutdownRequest))) { return; } SendBoolResponse(true, "Stopping server"); ExitProc(); } void ReloadBinCommand::Execute() { SNZBReloadRequest ReloadRequest; if (!ReceiveRequest(&ReloadRequest, sizeof(ReloadRequest))) { return; } SendBoolResponse(true, "Reloading server"); Reload(); } void VersionBinCommand::Execute() { SNZBVersionRequest VersionRequest; if (!ReceiveRequest(&VersionRequest, sizeof(VersionRequest))) { return; } SendBoolResponse(true, Util::VersionRevision()); } void DownloadBinCommand::Execute() { SNZBDownloadRequest DownloadRequest; if (!ReceiveRequest(&DownloadRequest, sizeof(DownloadRequest))) { return; } int iBufLen = ntohl(DownloadRequest.m_iTrailingDataLength); char* pRecvBuffer = (char*)malloc(iBufLen); if (!m_pConnection->Recv(pRecvBuffer, iBufLen)) { error("invalid request"); free(pRecvBuffer); return; } int iPriority = ntohl(DownloadRequest.m_iPriority); bool bAddPaused = ntohl(DownloadRequest.m_bAddPaused); bool bAddTop = ntohl(DownloadRequest.m_bAddFirst); bool bOK = g_pScanner->AddExternalFile(DownloadRequest.m_szFilename, DownloadRequest.m_szCategory, iPriority, NULL, 0, dmScore, NULL, bAddTop, bAddPaused, NULL, pRecvBuffer, iBufLen) != Scanner::asFailed; char tmp[1024]; snprintf(tmp, 1024, bOK ? "Collection %s added to queue" : "Download Request failed for %s", Util::BaseFileName(DownloadRequest.m_szFilename)); tmp[1024-1] = '\0'; SendBoolResponse(bOK, tmp); free(pRecvBuffer); } void ListBinCommand::Execute() { SNZBListRequest ListRequest; if (!ReceiveRequest(&ListRequest, sizeof(ListRequest))) { return; } SNZBListResponse ListResponse; memset(&ListResponse, 0, sizeof(ListResponse)); ListResponse.m_MessageBase.m_iSignature = htonl(NZBMESSAGE_SIGNATURE); ListResponse.m_MessageBase.m_iStructSize = htonl(sizeof(ListResponse)); ListResponse.m_iEntrySize = htonl(sizeof(SNZBListResponseFileEntry)); ListResponse.m_bRegExValid = 0; char* buf = NULL; int bufsize = 0; if (ntohl(ListRequest.m_bFileList)) { eRemoteMatchMode eMatchMode = (eRemoteMatchMode)ntohl(ListRequest.m_iMatchMode); bool bMatchGroup = ntohl(ListRequest.m_bMatchGroup); const char* szPattern = ListRequest.m_szPattern; RegEx *pRegEx = NULL; if (eMatchMode == eRemoteMatchModeRegEx) { pRegEx = new RegEx(szPattern); ListResponse.m_bRegExValid = pRegEx->IsValid(); } // Make a data structure and copy all the elements of the list into it DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue(); // calculate required buffer size for nzbs int iNrNZBEntries = pDownloadQueue->GetNZBInfoList()->size(); int iNrPPPEntries = 0; bufsize += iNrNZBEntries * sizeof(SNZBListResponseNZBEntry); for (NZBInfoList::iterator it = pDownloadQueue->GetNZBInfoList()->begin(); it != pDownloadQueue->GetNZBInfoList()->end(); it++) { NZBInfo* pNZBInfo = *it; bufsize += strlen(pNZBInfo->GetFilename()) + 1; bufsize += strlen(pNZBInfo->GetName()) + 1; bufsize += strlen(pNZBInfo->GetDestDir()) + 1; bufsize += strlen(pNZBInfo->GetCategory()) + 1; bufsize += strlen(pNZBInfo->GetQueuedFilename()) + 1; // align struct to 4-bytes, needed by ARM-processor (and may be others) bufsize += bufsize % 4 > 0 ? 4 - bufsize % 4 : 0; // calculate required buffer size for pp-parameters for (NZBParameterList::iterator it = pNZBInfo->GetParameters()->begin(); it != pNZBInfo->GetParameters()->end(); it++) { NZBParameter* pNZBParameter = *it; bufsize += sizeof(SNZBListResponsePPPEntry); bufsize += strlen(pNZBParameter->GetName()) + 1; bufsize += strlen(pNZBParameter->GetValue()) + 1; // align struct to 4-bytes, needed by ARM-processor (and may be others) bufsize += bufsize % 4 > 0 ? 4 - bufsize % 4 : 0; iNrPPPEntries++; } } // calculate required buffer size for files int iNrFileEntries = pDownloadQueue->GetFileQueue()->size(); bufsize += iNrFileEntries * sizeof(SNZBListResponseFileEntry); for (FileQueue::iterator it = pDownloadQueue->GetFileQueue()->begin(); it != pDownloadQueue->GetFileQueue()->end(); it++) { FileInfo* pFileInfo = *it; bufsize += strlen(pFileInfo->GetSubject()) + 1; bufsize += strlen(pFileInfo->GetFilename()) + 1; // align struct to 4-bytes, needed by ARM-processor (and may be others) bufsize += bufsize % 4 > 0 ? 4 - bufsize % 4 : 0; } buf = (char*) malloc(bufsize); char* bufptr = buf; // write nzb entries for (NZBInfoList::iterator it = pDownloadQueue->GetNZBInfoList()->begin(); it != pDownloadQueue->GetNZBInfoList()->end(); it++) { unsigned long iSizeHi, iSizeLo; NZBInfo* pNZBInfo = *it; SNZBListResponseNZBEntry* pListAnswer = (SNZBListResponseNZBEntry*) bufptr; Util::SplitInt64(pNZBInfo->GetSize(), &iSizeHi, &iSizeLo); pListAnswer->m_iSizeLo = htonl(iSizeLo); pListAnswer->m_iSizeHi = htonl(iSizeHi); pListAnswer->m_bMatch = htonl(bMatchGroup && (!pRegEx || pRegEx->Match(pNZBInfo->GetName()))); pListAnswer->m_iFilenameLen = htonl(strlen(pNZBInfo->GetFilename()) + 1); pListAnswer->m_iNameLen = htonl(strlen(pNZBInfo->GetName()) + 1); pListAnswer->m_iDestDirLen = htonl(strlen(pNZBInfo->GetDestDir()) + 1); pListAnswer->m_iCategoryLen = htonl(strlen(pNZBInfo->GetCategory()) + 1); pListAnswer->m_iQueuedFilenameLen = htonl(strlen(pNZBInfo->GetQueuedFilename()) + 1); bufptr += sizeof(SNZBListResponseNZBEntry); strcpy(bufptr, pNZBInfo->GetFilename()); bufptr += ntohl(pListAnswer->m_iFilenameLen); strcpy(bufptr, pNZBInfo->GetName()); bufptr += ntohl(pListAnswer->m_iNameLen); strcpy(bufptr, pNZBInfo->GetDestDir()); bufptr += ntohl(pListAnswer->m_iDestDirLen); strcpy(bufptr, pNZBInfo->GetCategory()); bufptr += ntohl(pListAnswer->m_iCategoryLen); strcpy(bufptr, pNZBInfo->GetQueuedFilename()); bufptr += ntohl(pListAnswer->m_iQueuedFilenameLen); // align struct to 4-bytes, needed by ARM-processor (and may be others) if ((size_t)bufptr % 4 > 0) { pListAnswer->m_iQueuedFilenameLen = htonl(ntohl(pListAnswer->m_iQueuedFilenameLen) + 4 - (size_t)bufptr % 4); memset(bufptr, 0, 4 - (size_t)bufptr % 4); //suppress valgrind warning "uninitialized data" bufptr += 4 - (size_t)bufptr % 4; } } // write ppp entries int iNZBIndex = 1; for (NZBInfoList::iterator it = pDownloadQueue->GetNZBInfoList()->begin(); it != pDownloadQueue->GetNZBInfoList()->end(); it++, iNZBIndex++) { NZBInfo* pNZBInfo = *it; for (NZBParameterList::iterator it = pNZBInfo->GetParameters()->begin(); it != pNZBInfo->GetParameters()->end(); it++) { NZBParameter* pNZBParameter = *it; SNZBListResponsePPPEntry* pListAnswer = (SNZBListResponsePPPEntry*) bufptr; pListAnswer->m_iNZBIndex = htonl(iNZBIndex); pListAnswer->m_iNameLen = htonl(strlen(pNZBParameter->GetName()) + 1); pListAnswer->m_iValueLen = htonl(strlen(pNZBParameter->GetValue()) + 1); bufptr += sizeof(SNZBListResponsePPPEntry); strcpy(bufptr, pNZBParameter->GetName()); bufptr += ntohl(pListAnswer->m_iNameLen); strcpy(bufptr, pNZBParameter->GetValue()); bufptr += ntohl(pListAnswer->m_iValueLen); // align struct to 4-bytes, needed by ARM-processor (and may be others) if ((size_t)bufptr % 4 > 0) { pListAnswer->m_iValueLen = htonl(ntohl(pListAnswer->m_iValueLen) + 4 - (size_t)bufptr % 4); memset(bufptr, 0, 4 - (size_t)bufptr % 4); //suppress valgrind warning "uninitialized data" bufptr += 4 - (size_t)bufptr % 4; } } } // write file entries for (FileQueue::iterator it = pDownloadQueue->GetFileQueue()->begin(); it != pDownloadQueue->GetFileQueue()->end(); it++) { unsigned long iSizeHi, iSizeLo; FileInfo* pFileInfo = *it; SNZBListResponseFileEntry* pListAnswer = (SNZBListResponseFileEntry*) bufptr; pListAnswer->m_iID = htonl(pFileInfo->GetID()); int iNZBIndex = 0; for (unsigned int i = 0; i < pDownloadQueue->GetNZBInfoList()->size(); i++) { iNZBIndex++; if (pDownloadQueue->GetNZBInfoList()->at(i) == pFileInfo->GetNZBInfo()) { break; } } pListAnswer->m_iNZBIndex = htonl(iNZBIndex); if (pRegEx && !bMatchGroup) { char szFilename[MAX_PATH]; snprintf(szFilename, sizeof(szFilename) - 1, "%s/%s", pFileInfo->GetNZBInfo()->GetName(), Util::BaseFileName(pFileInfo->GetFilename())); pListAnswer->m_bMatch = htonl(pRegEx->Match(szFilename)); } Util::SplitInt64(pFileInfo->GetSize(), &iSizeHi, &iSizeLo); pListAnswer->m_iFileSizeLo = htonl(iSizeLo); pListAnswer->m_iFileSizeHi = htonl(iSizeHi); Util::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_iActiveDownloads = htonl(pFileInfo->GetActiveDownloads()); pListAnswer->m_iPriority = htonl(pFileInfo->GetPriority()); pListAnswer->m_iSubjectLen = htonl(strlen(pFileInfo->GetSubject()) + 1); pListAnswer->m_iFilenameLen = htonl(strlen(pFileInfo->GetFilename()) + 1); bufptr += sizeof(SNZBListResponseFileEntry); strcpy(bufptr, pFileInfo->GetSubject()); bufptr += ntohl(pListAnswer->m_iSubjectLen); strcpy(bufptr, pFileInfo->GetFilename()); bufptr += ntohl(pListAnswer->m_iFilenameLen); // align struct to 4-bytes, needed by ARM-processor (and may be others) if ((size_t)bufptr % 4 > 0) { pListAnswer->m_iFilenameLen = htonl(ntohl(pListAnswer->m_iFilenameLen) + 4 - (size_t)bufptr % 4); memset(bufptr, 0, 4 - (size_t)bufptr % 4); //suppress valgrind warning "uninitialized data" bufptr += 4 - (size_t)bufptr % 4; } } g_pQueueCoordinator->UnlockQueue(); delete pRegEx; ListResponse.m_iNrTrailingNZBEntries = htonl(iNrNZBEntries); ListResponse.m_iNrTrailingPPPEntries = htonl(iNrPPPEntries); ListResponse.m_iNrTrailingFileEntries = htonl(iNrFileEntries); ListResponse.m_iTrailingDataLength = htonl(bufsize); } if (htonl(ListRequest.m_bServerState)) { unsigned long iSizeHi, iSizeLo; ListResponse.m_iDownloadRate = htonl(g_pQueueCoordinator->CalcCurrentDownloadSpeed()); Util::SplitInt64(g_pQueueCoordinator->CalcRemainingSize(), &iSizeHi, &iSizeLo); ListResponse.m_iRemainingSizeHi = htonl(iSizeHi); ListResponse.m_iRemainingSizeLo = htonl(iSizeLo); ListResponse.m_iDownloadLimit = htonl(g_pOptions->GetDownloadRate()); ListResponse.m_bDownloadPaused = htonl(g_pOptions->GetPauseDownload()); ListResponse.m_bDownload2Paused = htonl(g_pOptions->GetPauseDownload2()); ListResponse.m_bPostPaused = htonl(g_pOptions->GetPausePostProcess()); ListResponse.m_bScanPaused = htonl(g_pOptions->GetPauseScan()); ListResponse.m_iThreadCount = htonl(Thread::GetThreadCount() - 1); // not counting itself PostQueue* pPostQueue = g_pQueueCoordinator->LockQueue()->GetPostQueue(); ListResponse.m_iPostJobCount = htonl(pPostQueue->size()); g_pQueueCoordinator->UnlockQueue(); 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_bDownloadStandBy = htonl(bStandBy); Util::SplitInt64(iAllBytes, &iSizeHi, &iSizeLo); ListResponse.m_iDownloadedBytesHi = htonl(iSizeHi); ListResponse.m_iDownloadedBytesLo = htonl(iSizeLo); } // Send the request answer m_pConnection->Send((char*) &ListResponse, sizeof(ListResponse)); // Send the data if (bufsize > 0) { m_pConnection->Send(buf, bufsize); } free(buf); } void LogBinCommand::Execute() { SNZBLogRequest LogRequest; if (!ReceiveRequest(&LogRequest, sizeof(LogRequest))) { return; } Log::Messages* pMessages = g_pLog->LockMessages(); int iNrEntries = ntohl(LogRequest.m_iLines); unsigned int iIDFrom = ntohl(LogRequest.m_iIDFrom); int iStart = pMessages->size(); if (iNrEntries > 0) { if (iNrEntries > (int)pMessages->size()) { iNrEntries = pMessages->size(); } iStart = pMessages->size() - iNrEntries; } if (iIDFrom > 0 && !pMessages->empty()) { iStart = iIDFrom - pMessages->front()->GetID(); if (iStart < 0) { iStart = 0; } iNrEntries = pMessages->size() - iStart; if (iNrEntries < 0) { iNrEntries = 0; } } // calculate required buffer size int bufsize = iNrEntries * sizeof(SNZBLogResponseEntry); for (unsigned int i = (unsigned int)iStart; i < pMessages->size(); i++) { Message* pMessage = (*pMessages)[i]; bufsize += strlen(pMessage->GetText()) + 1; // align struct to 4-bytes, needed by ARM-processor (and may be others) bufsize += bufsize % 4 > 0 ? 4 - bufsize % 4 : 0; } char* buf = (char*) malloc(bufsize); char* bufptr = buf; for (unsigned int i = (unsigned int)iStart; i < pMessages->size(); i++) { Message* pMessage = (*pMessages)[i]; SNZBLogResponseEntry* pLogAnswer = (SNZBLogResponseEntry*) bufptr; pLogAnswer->m_iID = htonl(pMessage->GetID()); pLogAnswer->m_iKind = htonl(pMessage->GetKind()); pLogAnswer->m_tTime = htonl((int)pMessage->GetTime()); pLogAnswer->m_iTextLen = htonl(strlen(pMessage->GetText()) + 1); bufptr += sizeof(SNZBLogResponseEntry); strcpy(bufptr, pMessage->GetText()); bufptr += ntohl(pLogAnswer->m_iTextLen); // align struct to 4-bytes, needed by ARM-processor (and may be others) if ((size_t)bufptr % 4 > 0) { pLogAnswer->m_iTextLen = htonl(ntohl(pLogAnswer->m_iTextLen) + 4 - (size_t)bufptr % 4); memset(bufptr, 0, 4 - (size_t)bufptr % 4); //suppress valgrind warning "uninitialized data" bufptr += 4 - (size_t)bufptr % 4; } } g_pLog->UnlockMessages(); SNZBLogResponse LogResponse; LogResponse.m_MessageBase.m_iSignature = htonl(NZBMESSAGE_SIGNATURE); LogResponse.m_MessageBase.m_iStructSize = htonl(sizeof(LogResponse)); LogResponse.m_iEntrySize = htonl(sizeof(SNZBLogResponseEntry)); LogResponse.m_iNrTrailingEntries = htonl(iNrEntries); LogResponse.m_iTrailingDataLength = htonl(bufsize); // Send the request answer m_pConnection->Send((char*) &LogResponse, sizeof(LogResponse)); // Send the data if (bufsize > 0) { m_pConnection->Send(buf, bufsize); } free(buf); } void EditQueueBinCommand::Execute() { SNZBEditQueueRequest EditQueueRequest; if (!ReceiveRequest(&EditQueueRequest, sizeof(EditQueueRequest))) { return; } int iNrIDEntries = ntohl(EditQueueRequest.m_iNrTrailingIDEntries); int iNrNameEntries = ntohl(EditQueueRequest.m_iNrTrailingNameEntries); int iNameEntriesLen = ntohl(EditQueueRequest.m_iTrailingNameEntriesLen); int iAction = ntohl(EditQueueRequest.m_iAction); int iMatchMode = ntohl(EditQueueRequest.m_iMatchMode); int iOffset = ntohl(EditQueueRequest.m_iOffset); int iTextLen = ntohl(EditQueueRequest.m_iTextLen); bool bSmartOrder = ntohl(EditQueueRequest.m_bSmartOrder); unsigned int iBufLength = ntohl(EditQueueRequest.m_iTrailingDataLength); if (iNrIDEntries * sizeof(int32_t) + iTextLen + iNameEntriesLen != iBufLength) { error("Invalid struct size"); return; } char* pBuf = (char*)malloc(iBufLength); if (!m_pConnection->Recv(pBuf, iBufLength)) { error("invalid request"); free(pBuf); return; } if (iNrIDEntries <= 0 && iNrNameEntries <= 0) { SendBoolResponse(false, "Edit-Command failed: no IDs/Names specified"); return; } char* szText = iTextLen > 0 ? pBuf : NULL; int32_t* pIDs = (int32_t*)(pBuf + iTextLen); char* pNames = (pBuf + iTextLen + iNrIDEntries * sizeof(int32_t)); IDList cIDList; NameList cNameList; if (iNrIDEntries > 0) { cIDList.reserve(iNrIDEntries); for (int i = 0; i < iNrIDEntries; i++) { cIDList.push_back(ntohl(pIDs[i])); } } if (iNrNameEntries > 0) { cNameList.reserve(iNrNameEntries); for (int i = 0; i < iNrNameEntries; i++) { cNameList.push_back(pNames); pNames += strlen(pNames) + 1; } } bool bOK = false; if (iAction < eRemoteEditActionPostMoveOffset) { bOK = g_pQueueCoordinator->GetQueueEditor()->EditList( iNrIDEntries > 0 ? &cIDList : NULL, iNrNameEntries > 0 ? &cNameList : NULL, (QueueEditor::EMatchMode)iMatchMode, bSmartOrder, (QueueEditor::EEditAction)iAction, iOffset, szText); } else { bOK = g_pPrePostProcessor->QueueEditList(&cIDList, (PrePostProcessor::EEditAction)iAction, iOffset, szText); } free(pBuf); if (bOK) { SendBoolResponse(true, "Edit-Command completed successfully"); } else { #ifndef HAVE_REGEX_H if ((QueueEditor::EMatchMode)iMatchMode == QueueEditor::mmRegEx) { SendBoolResponse(false, "Edit-Command failed: the program was compiled without RegEx-support"); return; } #endif SendBoolResponse(false, "Edit-Command failed"); } } void PostQueueBinCommand::Execute() { SNZBPostQueueRequest PostQueueRequest; if (!ReceiveRequest(&PostQueueRequest, sizeof(PostQueueRequest))) { return; } SNZBPostQueueResponse PostQueueResponse; memset(&PostQueueResponse, 0, sizeof(PostQueueResponse)); PostQueueResponse.m_MessageBase.m_iSignature = htonl(NZBMESSAGE_SIGNATURE); PostQueueResponse.m_MessageBase.m_iStructSize = htonl(sizeof(PostQueueResponse)); PostQueueResponse.m_iEntrySize = htonl(sizeof(SNZBPostQueueResponseEntry)); char* buf = NULL; int bufsize = 0; // Make a data structure and copy all the elements of the list into it PostQueue* pPostQueue = g_pQueueCoordinator->LockQueue()->GetPostQueue(); int NrEntries = pPostQueue->size(); // calculate required buffer size bufsize = NrEntries * sizeof(SNZBPostQueueResponseEntry); for (PostQueue::iterator it = pPostQueue->begin(); it != pPostQueue->end(); it++) { PostInfo* pPostInfo = *it; bufsize += strlen(pPostInfo->GetNZBInfo()->GetFilename()) + 1; bufsize += strlen(pPostInfo->GetInfoName()) + 1; bufsize += strlen(pPostInfo->GetNZBInfo()->GetDestDir()) + 1; bufsize += strlen(pPostInfo->GetProgressLabel()) + 1; // align struct to 4-bytes, needed by ARM-processor (and may be others) bufsize += bufsize % 4 > 0 ? 4 - bufsize % 4 : 0; } time_t tCurTime = time(NULL); buf = (char*) malloc(bufsize); char* bufptr = buf; for (PostQueue::iterator it = pPostQueue->begin(); it != pPostQueue->end(); it++) { PostInfo* pPostInfo = *it; SNZBPostQueueResponseEntry* pPostQueueAnswer = (SNZBPostQueueResponseEntry*) bufptr; pPostQueueAnswer->m_iID = htonl(pPostInfo->GetID()); pPostQueueAnswer->m_iStage = htonl(pPostInfo->GetStage()); pPostQueueAnswer->m_iStageProgress = htonl(pPostInfo->GetStageProgress()); pPostQueueAnswer->m_iFileProgress = htonl(pPostInfo->GetFileProgress()); pPostQueueAnswer->m_iTotalTimeSec = htonl((int)(pPostInfo->GetStartTime() ? tCurTime - pPostInfo->GetStartTime() : 0)); pPostQueueAnswer->m_iStageTimeSec = htonl((int)(pPostInfo->GetStageTime() ? tCurTime - pPostInfo->GetStageTime() : 0)); pPostQueueAnswer->m_iNZBFilenameLen = htonl(strlen(pPostInfo->GetNZBInfo()->GetFilename()) + 1); pPostQueueAnswer->m_iInfoNameLen = htonl(strlen(pPostInfo->GetInfoName()) + 1); pPostQueueAnswer->m_iDestDirLen = htonl(strlen(pPostInfo->GetNZBInfo()->GetDestDir()) + 1); pPostQueueAnswer->m_iProgressLabelLen = htonl(strlen(pPostInfo->GetProgressLabel()) + 1); bufptr += sizeof(SNZBPostQueueResponseEntry); strcpy(bufptr, pPostInfo->GetNZBInfo()->GetFilename()); bufptr += ntohl(pPostQueueAnswer->m_iNZBFilenameLen); strcpy(bufptr, pPostInfo->GetInfoName()); bufptr += ntohl(pPostQueueAnswer->m_iInfoNameLen); strcpy(bufptr, pPostInfo->GetNZBInfo()->GetDestDir()); bufptr += ntohl(pPostQueueAnswer->m_iDestDirLen); strcpy(bufptr, pPostInfo->GetProgressLabel()); bufptr += ntohl(pPostQueueAnswer->m_iProgressLabelLen); // align struct to 4-bytes, needed by ARM-processor (and may be others) if ((size_t)bufptr % 4 > 0) { pPostQueueAnswer->m_iProgressLabelLen = htonl(ntohl(pPostQueueAnswer->m_iProgressLabelLen) + 4 - (size_t)bufptr % 4); memset(bufptr, 0, 4 - (size_t)bufptr % 4); //suppress valgrind warning "uninitialized data" bufptr += 4 - (size_t)bufptr % 4; } } g_pQueueCoordinator->UnlockQueue(); PostQueueResponse.m_iNrTrailingEntries = htonl(NrEntries); PostQueueResponse.m_iTrailingDataLength = htonl(bufsize); // Send the request answer m_pConnection->Send((char*) &PostQueueResponse, sizeof(PostQueueResponse)); // Send the data if (bufsize > 0) { m_pConnection->Send(buf, bufsize); } free(buf); } void WriteLogBinCommand::Execute() { SNZBWriteLogRequest WriteLogRequest; if (!ReceiveRequest(&WriteLogRequest, sizeof(WriteLogRequest))) { return; } char* pRecvBuffer = (char*)malloc(ntohl(WriteLogRequest.m_iTrailingDataLength) + 1); if (!m_pConnection->Recv(pRecvBuffer, ntohl(WriteLogRequest.m_iTrailingDataLength))) { error("invalid request"); free(pRecvBuffer); return; } bool OK = true; switch ((Message::EKind)ntohl(WriteLogRequest.m_iKind)) { case Message::mkDetail: detail(pRecvBuffer); break; case Message::mkInfo: info(pRecvBuffer); break; case Message::mkWarning: warn(pRecvBuffer); break; case Message::mkError: error(pRecvBuffer); break; case Message::mkDebug: debug(pRecvBuffer); break; default: OK = false; } SendBoolResponse(OK, OK ? "Message added to log" : "Invalid message-kind"); free(pRecvBuffer); } void ScanBinCommand::Execute() { SNZBScanRequest ScanRequest; if (!ReceiveRequest(&ScanRequest, sizeof(ScanRequest))) { return; } bool bSyncMode = ntohl(ScanRequest.m_bSyncMode); g_pScanner->ScanNZBDir(bSyncMode); SendBoolResponse(true, bSyncMode ? "Scan-Command completed" : "Scan-Command scheduled successfully"); } void HistoryBinCommand::Execute() { SNZBHistoryRequest HistoryRequest; if (!ReceiveRequest(&HistoryRequest, sizeof(HistoryRequest))) { return; } SNZBHistoryResponse HistoryResponse; memset(&HistoryResponse, 0, sizeof(HistoryResponse)); HistoryResponse.m_MessageBase.m_iSignature = htonl(NZBMESSAGE_SIGNATURE); HistoryResponse.m_MessageBase.m_iStructSize = htonl(sizeof(HistoryResponse)); HistoryResponse.m_iEntrySize = htonl(sizeof(SNZBHistoryResponseEntry)); char* buf = NULL; int bufsize = 0; // Make a data structure and copy all the elements of the list into it DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue(); // calculate required buffer size for nzbs int iNrEntries = pDownloadQueue->GetHistoryList()->size(); bufsize += iNrEntries * sizeof(SNZBHistoryResponseEntry); for (HistoryList::iterator it = pDownloadQueue->GetHistoryList()->begin(); it != pDownloadQueue->GetHistoryList()->end(); it++) { HistoryInfo* pHistoryInfo = *it; char szNicename[1024]; pHistoryInfo->GetName(szNicename, sizeof(szNicename)); bufsize += strlen(szNicename) + 1; // align struct to 4-bytes, needed by ARM-processor (and may be others) bufsize += bufsize % 4 > 0 ? 4 - bufsize % 4 : 0; } buf = (char*) malloc(bufsize); char* bufptr = buf; // write nzb entries for (HistoryList::iterator it = pDownloadQueue->GetHistoryList()->begin(); it != pDownloadQueue->GetHistoryList()->end(); it++) { HistoryInfo* pHistoryInfo = *it; SNZBHistoryResponseEntry* pListAnswer = (SNZBHistoryResponseEntry*) bufptr; pListAnswer->m_iID = htonl(pHistoryInfo->GetID()); pListAnswer->m_iKind = htonl((int)pHistoryInfo->GetKind()); pListAnswer->m_tTime = htonl((int)pHistoryInfo->GetTime()); char szNicename[1024]; pHistoryInfo->GetName(szNicename, sizeof(szNicename)); pListAnswer->m_iNicenameLen = htonl(strlen(szNicename) + 1); if (pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo) { NZBInfo* pNZBInfo = pHistoryInfo->GetNZBInfo(); unsigned long iSizeHi, iSizeLo; Util::SplitInt64(pNZBInfo->GetSize(), &iSizeHi, &iSizeLo); pListAnswer->m_iSizeLo = htonl(iSizeLo); pListAnswer->m_iSizeHi = htonl(iSizeHi); pListAnswer->m_iFileCount = htonl(pNZBInfo->GetFileCount()); pListAnswer->m_iParStatus = htonl(pNZBInfo->GetParStatus()); pListAnswer->m_iScriptStatus = htonl(pNZBInfo->GetScriptStatuses()->CalcTotalStatus()); } else if (pHistoryInfo->GetKind() == HistoryInfo::hkUrlInfo) { UrlInfo* pUrlInfo = pHistoryInfo->GetUrlInfo(); pListAnswer->m_iUrlStatus = htonl(pUrlInfo->GetStatus()); } bufptr += sizeof(SNZBHistoryResponseEntry); strcpy(bufptr, szNicename); bufptr += ntohl(pListAnswer->m_iNicenameLen); // align struct to 4-bytes, needed by ARM-processor (and may be others) if ((size_t)bufptr % 4 > 0) { pListAnswer->m_iNicenameLen = htonl(ntohl(pListAnswer->m_iNicenameLen) + 4 - (size_t)bufptr % 4); memset(bufptr, 0, 4 - (size_t)bufptr % 4); //suppress valgrind warning "uninitialized data" bufptr += 4 - (size_t)bufptr % 4; } } g_pQueueCoordinator->UnlockQueue(); HistoryResponse.m_iNrTrailingEntries = htonl(iNrEntries); HistoryResponse.m_iTrailingDataLength = htonl(bufsize); // Send the request answer m_pConnection->Send((char*) &HistoryResponse, sizeof(HistoryResponse)); // Send the data if (bufsize > 0) { m_pConnection->Send(buf, bufsize); } free(buf); } void DownloadUrlBinCommand::Execute() { SNZBDownloadUrlRequest DownloadUrlRequest; if (!ReceiveRequest(&DownloadUrlRequest, sizeof(DownloadUrlRequest))) { return; } URL url(DownloadUrlRequest.m_szURL); if (!url.IsValid()) { char tmp[1024]; snprintf(tmp, 1024, "Url %s is not valid", DownloadUrlRequest.m_szURL); tmp[1024-1] = '\0'; SendBoolResponse(true, tmp); return; } UrlInfo* pUrlInfo = new UrlInfo(); pUrlInfo->SetURL(DownloadUrlRequest.m_szURL); pUrlInfo->SetNZBFilename(DownloadUrlRequest.m_szNZBFilename); pUrlInfo->SetCategory(DownloadUrlRequest.m_szCategory); pUrlInfo->SetPriority(ntohl(DownloadUrlRequest.m_iPriority)); pUrlInfo->SetAddTop(ntohl(DownloadUrlRequest.m_bAddFirst)); pUrlInfo->SetAddPaused(ntohl(DownloadUrlRequest.m_bAddPaused)); g_pUrlCoordinator->AddUrlToQueue(pUrlInfo, ntohl(DownloadUrlRequest.m_bAddFirst)); info("Request: Queue url %s", DownloadUrlRequest.m_szURL); char tmp[1024]; snprintf(tmp, 1024, "Url %s added to queue", DownloadUrlRequest.m_szURL); tmp[1024-1] = '\0'; SendBoolResponse(true, tmp); } void UrlQueueBinCommand::Execute() { SNZBUrlQueueRequest UrlQueueRequest; if (!ReceiveRequest(&UrlQueueRequest, sizeof(UrlQueueRequest))) { return; } SNZBUrlQueueResponse UrlQueueResponse; memset(&UrlQueueResponse, 0, sizeof(UrlQueueResponse)); UrlQueueResponse.m_MessageBase.m_iSignature = htonl(NZBMESSAGE_SIGNATURE); UrlQueueResponse.m_MessageBase.m_iStructSize = htonl(sizeof(UrlQueueResponse)); UrlQueueResponse.m_iEntrySize = htonl(sizeof(SNZBUrlQueueResponseEntry)); char* buf = NULL; int bufsize = 0; // Make a data structure and copy all the elements of the list into it UrlQueue* pUrlQueue = g_pQueueCoordinator->LockQueue()->GetUrlQueue(); int NrEntries = pUrlQueue->size(); // calculate required buffer size bufsize = NrEntries * sizeof(SNZBUrlQueueResponseEntry); for (UrlQueue::iterator it = pUrlQueue->begin(); it != pUrlQueue->end(); it++) { UrlInfo* pUrlInfo = *it; bufsize += strlen(pUrlInfo->GetURL()) + 1; bufsize += strlen(pUrlInfo->GetNZBFilename()) + 1; // align struct to 4-bytes, needed by ARM-processor (and may be others) bufsize += bufsize % 4 > 0 ? 4 - bufsize % 4 : 0; } buf = (char*) malloc(bufsize); char* bufptr = buf; for (UrlQueue::iterator it = pUrlQueue->begin(); it != pUrlQueue->end(); it++) { UrlInfo* pUrlInfo = *it; SNZBUrlQueueResponseEntry* pUrlQueueAnswer = (SNZBUrlQueueResponseEntry*) bufptr; pUrlQueueAnswer->m_iID = htonl(pUrlInfo->GetID()); pUrlQueueAnswer->m_iURLLen = htonl(strlen(pUrlInfo->GetURL()) + 1); pUrlQueueAnswer->m_iNZBFilenameLen = htonl(strlen(pUrlInfo->GetNZBFilename()) + 1); bufptr += sizeof(SNZBUrlQueueResponseEntry); strcpy(bufptr, pUrlInfo->GetURL()); bufptr += ntohl(pUrlQueueAnswer->m_iURLLen); strcpy(bufptr, pUrlInfo->GetNZBFilename()); bufptr += ntohl(pUrlQueueAnswer->m_iNZBFilenameLen); // align struct to 4-bytes, needed by ARM-processor (and may be others) if ((size_t)bufptr % 4 > 0) { pUrlQueueAnswer->m_iNZBFilenameLen = htonl(ntohl(pUrlQueueAnswer->m_iNZBFilenameLen) + 4 - (size_t)bufptr % 4); memset(bufptr, 0, 4 - (size_t)bufptr % 4); //suppress valgrind warning "uninitialized data" bufptr += 4 - (size_t)bufptr % 4; } } g_pQueueCoordinator->UnlockQueue(); UrlQueueResponse.m_iNrTrailingEntries = htonl(NrEntries); UrlQueueResponse.m_iTrailingDataLength = htonl(bufsize); // Send the request answer m_pConnection->Send((char*) &UrlQueueResponse, sizeof(UrlQueueResponse)); // Send the data if (bufsize > 0) { m_pConnection->Send(buf, bufsize); } free(buf); } nzbget-12.0+dfsg/BinRpc.h000066400000000000000000000061161226450633000151770ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2005 Bo Cordes Petersen * Copyright (C) 2007-2009 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 530 $ * $Date: 2012-12-30 16:27:38 +0100 (Sun, 30 Dec 2012) $ * */ #ifndef BINRPC_H #define BINRPC_H #include "Connection.h" #include "MessageBase.h" class BinRpcProcessor { private: SNZBRequestBase m_MessageBase; Connection* m_pConnection; void Dispatch(); public: BinRpcProcessor(); void Execute(); void SetConnection(Connection* pConnection) { m_pConnection = pConnection; } }; class BinCommand { protected: Connection* m_pConnection; SNZBRequestBase* m_pMessageBase; bool ReceiveRequest(void* pBuffer, int iSize); void SendBoolResponse(bool bSuccess, const char* szText); public: virtual ~BinCommand() {} virtual void Execute() = 0; void SetConnection(Connection* pConnection) { m_pConnection = pConnection; } void SetMessageBase(SNZBRequestBase* pMessageBase) { m_pMessageBase = pMessageBase; } }; class DownloadBinCommand: public BinCommand { public: virtual void Execute(); }; class ListBinCommand: public BinCommand { public: virtual void Execute(); }; class LogBinCommand: public BinCommand { public: virtual void Execute(); }; class PauseUnpauseBinCommand: public BinCommand { public: virtual void Execute(); }; class EditQueueBinCommand: public BinCommand { public: virtual void Execute(); }; class SetDownloadRateBinCommand: public BinCommand { public: virtual void Execute(); }; class DumpDebugBinCommand: public BinCommand { public: virtual void Execute(); }; class ShutdownBinCommand: public BinCommand { public: virtual void Execute(); }; class ReloadBinCommand: public BinCommand { public: virtual void Execute(); }; class VersionBinCommand: public BinCommand { public: virtual void Execute(); }; class PostQueueBinCommand: public BinCommand { public: virtual void Execute(); }; class WriteLogBinCommand: public BinCommand { public: virtual void Execute(); }; class ScanBinCommand: public BinCommand { public: virtual void Execute(); }; class HistoryBinCommand: public BinCommand { public: virtual void Execute(); }; class DownloadUrlBinCommand: public BinCommand { public: virtual void Execute(); }; class UrlQueueBinCommand: public BinCommand { public: virtual void Execute(); }; #endif nzbget-12.0+dfsg/COPYING000066400000000000000000000431311226450633000147020ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. nzbget-12.0+dfsg/ChangeLog000066400000000000000000002616751226450633000154400ustar00rootroot00000000000000nzbget-12.0: - added RSS feeds support: - new options "FeedX.Name", "FeedX.URL", "FeedX.Filter", "FeedX.Interval", "FeedX.PauseNzb", "FeedX.Category", "FeedX.Priority" (section "Rss Feeds"); - new option "FeedHistory" (section "Download Queue"); - button "Preview Feed" on settings tab near each feed definition; - new toolbar button "Feeds" on downloads tab with menu to view feeds or fetch new nzbs from all feeds (the button is visible only if there are feeds defined in settings); - new dialog to see feed content showing status of each item (new, fetched, backlog) with ability to manually fetch selected items; - powerful filters for RSS feeds; - new dialog to build filters in web-interface with instant preview; - added download health monitoring: - health indicates download status, whether the file is damaged and how much; - 100% health means no download errors occurred; 0% means all articles failed; - there is also a critical health which is calculated for each nzb-file based on number and size of par-files; - if during download the health goes down below 100% a health badge appears near download name indicating the necessity of par-repair; the indicator can be orange (repair may be possible) or red (unrepairable) if the health goes down below critical health; - new option "HealthCheck" to define what to do with unhealthy (unrepairable) downloads (pause, delete, none); - health and critical health are displayed in download-edit dialog; health is displayed in history dialog; if download was aborted (HealthCheck=delete) this is indicated in history dialog; - health allows to determine download status for downloads which have unpack and/or par-check disabled; for such downloads the status in history is shown based on health: success (health=100%), damaged (health > critical) or failure (health < critical); - par-check is now automatically started for downloads having health below 100%; this works independently of unpack (even if unpack is disabled); - for downloads having health less than critical health no par-check is performed (it would fail); Instead the par-check status is set to "failure" automatically saving time of actual par-check; - new fields "Health" and "CriticalHealth" are returned by RPC-Method "listgroups"; - new fields "Health", "CriticalHealth", "Deleted" and "HealthDeleted" are returned by RPC-Method "history"; - new parameters "NZBPP_HEALTH" and "NZBPP_CRITICALHEALTH" are passed to pp-scripts; - added collecting of server usage statistical data for each download: - number of successful and failed article downloads per news server; - new page in history dialog shows collected statistics; - new fields in RPC-method "history": ServerStats (array), TotalArticles, SuccessArticles, FailedArticles; - new env. vars passed to pp-scripts: NZBPP_TOTALARTICLES, NZBPP_SUCCESSARTICLES, NZBPP_FAILEDARTICLES and per used news server: NZBPP_SERVERX_SUCCESSARTICLES, NZBPP_SERVERX_FAILEDARTICLES; - also new env.var HEALTHDELETED; - added smart duplicates feature: - mostly for use with RSS feeds; - automatic detection of duplicate nzb-files to avoid download of duplicates; - nzb-files can be also manually marked as duplicates; - if download fails - automatically choose another release (duplicate); - if download succeeds all remaining duplicates are skipped (not downloaded); - download items have new properties to tune duplicate handling behavior: duplicate key, duplicate score and duplicate mode; - if download was deleted by duplicate check its status in the history is shown as "DUPE"; - new actions "GroupSetDupeKey", "GroupSetDupeScore", "GroupSetDupeMode", "HistorySetDupeKey", "HistorySetDupeScore", "HistorySetDupeMode", "HistoryMarkBad" and "HistoryMarkGood" of RPC-command "editqueue"; new actions "B" and "G" of command "--edit/-E" for history items (subcommand "H"); - when deleting downloads from queue there are three options now: "move to history", "move to history as duplicate" and "delete without history tracking"; - new actions "GroupDupeDelete", "GroupFinalDelete" and "HistorySetDupeBackup" in RPC-method "editqueue"; - RPC-commands "listgroups", "postqueue" and "history" now return more info about nzb-item (many new fields); - removed option "MergeNzb" because it conflicts with duplicate handling, items can be merged manually if necessary; - automatic detection of exactly same nzb-files (same content) coming from different sources (different RSS feeds etc.); individual files (inside nzb-file) having extensions listed in option "ExtCleanupDisk" are excluded from content comparison (unless these are par2-files, which are never excluded); - when history item expires (as defined by option "KeepHistory") and the duplicate check is active (option "DupeCheck") the item is not completely deleted from history; instead the amount of stored data reduces to minimum required for duplicate check (about 200 bytes vs 2000 bytes for full history item); - such old history items are not shown in web-interface by default (to avoid transferring of large amount of history items); - new button "Hidden" in web-interface to show hidden history items; the items are marked with badge "hidden"; - RPC-method "editqueue" has now two actions to delete history records: "HistoryDelete", "HistoryFinal"; action "HistoryDelete" which has existed before now hides records, already hidden records are ignored; - added functions "Mark as Bad" and "Mark as Good" for history items; - duplicate properties (dupekey, dupescore and dupemode) can now be viewed and changed in download-edit-dialog and history-edit-dialog via new button "Dupe"; - for full documentation see http://nzbget.sourceforge.net/RSS#Duplicates; - created NZBGet.app - NZBGet is now a user friendly Mac OSX application with easy installation and seamless integration into OS UI: works in background, is controlled from a web-browser, few important functions are accessible via menubar icon; - better Windows package: - unrar is included; - several options are set to better defaults; - all paths are set as relative paths to program directory; the program can be started after installation without editing anything in config; - included two new batch-files: - nzbget-start.bat - starts program in normal mode (dos box); - nzbget-recovery-mode.bat - starts with empty password (dos box); - both batch files open browser window with correct address; - config-file template is stored in nzbget.conf.template; - nzbget.conf is not included in the package. When the program is started for the first time (using one of batch files) the template config file is copied into nzbget.conf; - updates will be easy in the future: to update the program all files from newer archive must be extracted over old files. Since the archive doesn't have nzbget.conf, the existing config is kept unmodified. The template config file will be updated; - added file README-WINDOWS.txt with small instructions; - version string now includes revision number (like "r789"); - added automatic updates: - new button "Check for updates" on settings tab of web-interface, in section "SYSTEM", initiates check and shows dialog allowing to install new version; - it is possible to choose between stable, testing and development branches; - this feature is for end-users using binary packages created and updated by maintainers, who need to write an update script specific for platform; - the script is then called by NZBGet when user clicks on install-button; - the script must download and install new version; - for more info visit http://nzbget.sourceforge.net/Packaging; - news servers can now be temporarily disabled via speed limit dialog without reloading of the program: - new option "ServerX.Active" to disable servers via settings; - new option "ServerX.Name" to use for logging and in UI; - changed the way how option "Unpack" works: - instead of enabling/disabling the unpacker as a whole, it now defines the initial value of post-processing parameter "Unpack" for nzb-file when it is added to queue; - this makes it now possible to disable Unpack globally but still enable it for selected nzb-files; - new option "CategoryX.Unpack" to set unpack on a per category basis; - combined all footer buttons into one button "Actions" with menu: - in download-edit-dialog: "Pause/Resume", "Delete" and "Cancel Post-Processing"; - in history-dialog: "Delete", "Post-Process Again" and "Download Remaining Files (Return to Queue)"; - DirectNZB headers X-DNZB-MoreInfo and X-DNZB-Details are now processed when downloading URLs and the links "More Info" and "Details" are shown in download-edit-dialog and in history-dialog in Actions menu; - program can now be stopped via web-interface: new button "shutdown" in section "SYSTEM"; - added menu "View" to settings page which allows to switch to "Compact Mode" when option descriptions are hidden; - added confirmation dialog by leaving settings page if there are unsaved changes; - downloads manually deleted from queue are shown with status "deleted" in the history (instead of "unknown"); - all table columns except "Name" now have fixed widths to avoid annoying layout changes especially during post-processing when long status messages are displayed in the name-column; - added filter buttons to messages tab (info, warning, etc.); - added automatic par-renaming of extracted files if archive includes par-files; - added support for http redirects when fetching URLs; - added new command "Download again" for history items; new action "HistoryRedownload" of RPC-method "editqueue"; for controlling via command line: new action "A" of subcommand "H" of command "--edit/-E"; - download queue is now saved in a more safe way to avoid potential loss of queue if the program crashes during saving of queue; - destination directory for option "CategoryX.DestDir" is not checked/created on program start anymore (only when a download starts for that category); this helps when certain categories are configured for external disks, which are not always connected; - added new option "CategoryX.Aliases" to configure category name matching with nzb-sites; especially useful with rss feeds; - in RPC-Method "appendurl" parameter "addtop" adds nzb to the top of the main download queue (not only to the top of the URL queue); - new logo (thanks to dogzipp for the logo); - added support for metatag "password" in nzb-files; - pp-scripts which move files can now inform the program about new location by printing text "[NZB] FINALDIR=/path/to/files"; the final path is then shown in history dialog instead of download path; - new env-var "NZBPP_FINALDIR" passed to pp-scripts; - pp-scripts can now set post-processing parameters by printing command "[NZB] NZBPR_varname=value"; this allows scripts which are executed sooner to pass data for scripts executed later; - added new option "AuthorizedIP" to set the list of IP-addresses which may connect without authorization; - new option "ParRename" to force par-renaming as a first post-processing step (active by default); this saves an unpack attempt and is even more useful if unpack is disabled; - post-processing progress label is now automatically trimmed if it doesn't fill into one line; this avoids layout breaking if the text is too long; - reversed the order of priorities in comboboxes in dialogs: the highest priority - at the top, the lowest - at the bottom; - small changes in button captions: edit dialogs called from settings page (choose script, choose order, build rss filter) now have buttons "Discard/Apply" instead of "Close/Save"; in all other dialogs button "Close" renamed to "Cancel" unless it was the only button in dialog; - small change in css: slightly reduced the max height of modal dialogs to better work on notebooks; - options "DeleteCleanupDisk" and "NzbCleanupDisk" are now active by default (in the example config file); - extended add-dialog with options "Add paused" and "Disable duplicate check"; - source nzb-files are now deleted when download-item leaves queue and history (option "NzbCleanupDisk"); - when deleting downloads from queue the messages about deleted individual files are now printed as "detail" instead of "info"; - failed article downloads are now logged as "detail" instead of "warning" to reduce number of warnings for downloads removed from server (DMCA); one warning is printed for a file with a summary of number of failed downloads for the file; - tuned algorithm calculating maximum threads limit to allow more threads for backup server connections (related to option "TreadLimit" removed in v11); this may sometimes increase speed when backup servers were used; - by adding nzb-file to queue via RPC-methods "append" and "appendurl" the actual format of the file is checked and if nzb-format is detected the file is added even if it does not have .nzb extension; - added new option "UrlForce" to allow URL-downloads (including fetching of RSS feeds and nzb-files from feeds) even if download is paused; the option is active by default; - destination directory for option "DestDir" is not checked/created on program start anymore (only when a download starts); this helps when DestDir is mounted to a network drive which is not available on program start; - added special handling for files ".AppleDouble" and ".DS_Store" during unpack to avoid problems on NAS having support for AFP protocol (used on Mac OSX); - history records with failed script status are now shown as "PP-FAILURE" in history list (instead of just "FAILURE"); - option "DiskSpace" now checks space on "InterDir" in addition to "DestDir"; - support for rar-archives with non-standard extensions is now limited to file extensions consisting of digits; this is to avoid extracting of rar-archives having non-rar extensions on purpose (example: .cbr); - if option "ParRename" is disabled (not recommended) unpacker does not initiate par-rename anymore, instead the full par-verify is performed then; - for external script the exec-permissions are now added automatically; this makes the installation of pp-scripts and other scripts easier; - option "InterDir" is now active by default; - when option "InterDir" is used the intermediate destination directory names now include unique numbers to avoid several downloads with same name to use the same directory and interfere with each other; - when option "UnpackCleanupDisk" is active all archive files are now deleted from download directory without relying on output printed by unrar; this solves issues with non-ascii-characters in archive file names on some platforms and especially in combination with rar5; - improved handling of non-ascii characters in file names on windows; - added support for rar5-format when checking signatures of archives with non-standard file extensions; - small restructure in settings order: - combined sections "REMOTE CONTROL" and "PERMISSIONS" into one section with name "SECURITY"; - moved sections "CATEGORIES" and "RSS FEEDS" higher in the section list; - improved par-check: if main par2-file is corrupted and can not be loaded other par2-files are downloaded and then used as replacement for main par2-file; - if unpack did not find archive files the par-check is not requested anymore if par-rename was already done; - better handling of obfuscated nzb-files containing multiple files with same names; removed option "StrictParName" which was not working good with obfuscated files; if more par-files are required for repair the files with strict names are tried first and then other par-files; - added new scheduler commands "ActivateServer", "DeactivateServer" and "FetchFeed"; combined options "TaskX.DownloadRate" and "TaskX.Process" into one option "TaskX.Param", also used by new commands; - added status filter buttons to history page; - if unpack fails with write error (usually because of not enough space on disk) this is shown as status "Unpack: space" in web-interface; this unpack-status is handled as "success" by duplicate handling (no download of other duplicate); also added new unpack-status "wrong password" (only for rar5-archives); env.var. NZBPP_UNPACKSTATUS has two new possible values: 3 (write error) and 4 (wrong password); updated pp-script "EMail.py" to support new unpack-statuses; - fixed a potential seg. fault in a commonly used function; - added new option "TimeCorrection" to adjust conversion from system time to local time (solves issues with scheduler when using a binary compiled for other platform); - NZBIDs are now generated with more care avoiding numbering holes possible in previous versions; - fixed: invalid "Offset" passed to RPC-method "editqueue" or command line action "-E/--edit" could crash the program; - fixed: crash after downloading of an URL (happen only on certain systems); - fixed: restoring of settings didn't work for multi-sections (servers, categories, etc.) if they were empty; - fixed: choosing local files didn't work in Opera; - fixed: certain characters printed by pp-scripts could crash the program; - fixed: malformed nzb-file could cause a memory leak; - fixed: when a duplicate file was detected in collection it was automatically deleted (if option DupeCheck is active) but the total size of collection was not updated; - when deleting individual files the total count of files in collection was not updated; - fixed: when multiple nzb-files were added via URL (rss including) at the same time the info about category and priority could get lost for some of files; - fixed: if unpack fails the created destination directory was not automatically removed (only if option "InterDir" was active); - fixed scrolling to the top of page happening by clicking on items in downloads/history lists and on action-buttons in edit-download and history dialogs; - fixed potential buffer overflow in remote client; - improved error reporting when creation of temporary output file fails; - fixed: when deleting download, if all remaining queued files are par2-files the disk cleanup should not be performed, but it was sometimes; - fixed a potential problem in incorrect using of one library function. nzbget-11.0: - reworked concept of post-processing scripts: - multiple scripts can be assigned to each nzb-file; - all assigned scripts are executed after the nzb-file is downloaded and internally processed (unpack, repair); - option is obsolete; - new option sets directory where all pp-scripts must be stored; - new option sets the default list of pp-scripts to be assigned to nzb-file when it's added to queue; - new option to set the default list of pp-scripts on a category basis; - the execution order of pp-scripts can be set using new option ; - there are no separate configuration files for pp-scripts; - configuration options and pp-parameters are defined in the pp-scripts; - script configuration options are saved in nzbget configuration file (nzbget.conf); - changed parameters list of RPC-methods and ; - new RPC-method returns configuration descriptions for the program and for all pp-scripts; - configuration of all scripts can be done in web-interface; - the pp-scripts assigned to a particular nzb-file can be viewed and changed in web-interface on page in the edit download dialog; - option renamed to (the old name is still recognized); - new option to define the location of template configuration file (in previous versions it must be always stored in ); - history dialog shows status of every script; - the old example post-processing script replaced with two new scripts: - EMail.py - sends E-Mail notification; - Logger.py - saves the full post-processing log of the job into file _postprocesslog.txt; - both pp-scripts are written in python and work on Windows too (in addition to Linux, Mac, etc.); - added possibility to set post-processing parameters for history items: - pp-parameters can now be viewed and changed in history dialog in web-interface; - useful before post-processing again; - new action in RPC-method ; - new action in remote command <--edit/-E> for history items (subcommand ); - added new feature which creates new download from selected files of source download; - new command in web-interface in edit download dialog on page ; - new action in remote command <--edit/-E>; - new action in JSON-/XML-RPC method ; - added support for manual par-check: - if option is set to and a damaged download is detected the program downloads all par2-files but doesn't perform par-check; the user must perform par-check/repair manually then (possibly on another, faster computer); - old values of option renamed to and respectively; - when set to all par2-files are always downloaded; - removed option since its functionality is now covered by option ; - result of par-check can now have new value ; - field in RPC-method can have new value ; - parameter for pp-script can have new value <4 = manual repair necessary>; - when download is resumed in web-interface the option is respected and all par2-files are resumed (not only main par2-file); - automatic deletion of backup-source files after successful par-repair; important when repairing renamed rar-files since this could cause failure during unpack; - par-checker and renamer now add messages into the log of pp-item (like unpack- and pp-scripts-messages); these message now appear in the log created by scripts Logger.py and EMail.py; - when a nzb-file is added via web-interface or via remote call the file is now put into incoming nzb-directory (option "NzbDir") and then scanned; this has two advantages over the old behavior when the file was parsed directly in memory: - the file serves as a backup for troubleshootings; - the file is processed by nzbprocess-script (if defined in option "NzbProcess") making the pre-processing much easier; - new env-var parameters are passed to NzbProcess-script: NZBNP_NZBNAME, NZBNP_CATEGORY, NZBNP_PRIORITY, NZBNP_TOP, NZBNP_PAUSED; - new commands for use in NzbProcess-scripts: "[NZB] TOP=1" to add nzb to the top of queue and "[NZB] PAUSED=1" to add nzb-file in paused state; - reworked post-processor queue: - only one job is created for each nzb-file; no more separate jobs are created for par-collections within one nzb-file; - option removed; a post-processing script is called only once per nzb-file, this behavior cannot be altered anymore; - with a new feature individual par-collections can be processed separately in a more effective way than before - improved unicode (utf8) support: - non-ascii characters are now correctly transferred via JSON-RPC; - correct displaying of nzb-names and paths in web-interface; - it is now possible to use non-ascii characters on settings page for option values (such as paths or category names); - improved unicode support in XML-RPC and JSON-RPC; - if username and password are defined for a news-server the authentication is now forced (in previous versions the authentication was performed only if requested by server); needed for servers supporting both anonymous (restricted) and authorized (full access) accounts; - added option to automatically delete unwanted files (with specified extensions or names) after successful par-check or unpack; - improvement in JSON-/XML-RPC: - all ID fields including NZBID are now persistent and remain their values after restart; - this allows for third-party software to identify nzb-files by ID; - method now returns ID of NZB-file in the field ; - in versions up to 0.8.0 the field was used to identify history items in the edit-commands , , ; since version 9 field is used for this purpose; in versions 9-10 field still existed and had the same value as field for compatibility with version 0.8.0; the compatibility is not provided anymore; this change was needed to provide a consistent using of field across all RPC-methods; - added support for rar-files with non-standard extensions (such as .001, etc.); - added functions to backup and restore settings from web-interface; when restoring it's possible to choose what sections to restore (for example only news servers settings or only settings of a certain pp-script) or restore the whole configuration; - new option "ControlUsername" to define login user name (if you don't like default username "nzbget"); - if a communication error occurs in web-interface, it retries multiple times before giving up with an error message; - the maximum number of download threads are now managed automatically taking into account the number of allowed connections to news servers; removed option ; - pp-scripts terminated with unknown status are now considered failed (status=FAILURE instead of status=UNKNOWN); - new parameter (env. var) is passed to pp_scripts and contains an internal ID of NZB-file; - improved thread synchronisation to avoid (short-time) lockings of the program during creation of destination files; - more detailed error message if a directory could not be created (, , etc.); the message includes error text reported by OS such as or similar; - when unpacking the unpack start time is now measured after receiving of unrar copyright message; this provides better unpack time estimation in a case when user uses unpack-script to do some things before executing unrar (for example sending Wake-On-Lan message to the destination NAS); it works with unrar only, it's not possible with 7-Zip because it buffers printed messages; - when the program is reloaded, a message with version number is printed like on start; - configuration can now be saved in web-interface even if there were no changes made but if obsolete or invalid options were detected in the config file; the saving removes invalid entries from config file; - option can now be set to en empty value to disable authentication; useful if nzbget works behind other web-server with its own authentication; - when deleting downloads via web-interface a proper hint regarding deleting of already downloaded files from disk depending on option is displayed; - if a news-server returns empty or bad article (this may be caused by errors on the news server), the program tries again from the same or other servers (in previous versions the article was marked as failed without other download attempts); - when a nzb-file whose name ends with ".queued" is added via web- interface the ".queued"-part is automatically removed; - small improvement in multithread synchronization of download queue; - added link to catalog of pp-scripts to web-interface; - updated forum URL in about dialog in web-interface; - small correction in a log-message: removed from message ; - removed option "ProcessLogKind"; scripts should use prefixes ([INFO], [DETAIL], etc); messages printed without prefixes are added as [INFO]; - removed option "AppendNzbDir"; if it was disabled that caused problems in par-checker and unpacker; the option is now assumed always active; - removed option "RenameBroken"; it caused problems in par-checker (the option existed since early program versions before the par-check was added); - configure-script now defines "SIGCHLD_HANDLER" by default on all systems including BSD; this eliminates the need of configure- parameter "--enable-sigchld-handler" on 64-Bit BSD; the trade-off: 32-Bit BSD now requires "--disable-sigchld-handler"; - improved configure-script: defining of symbol "FILE_OFFSET_BITS=64", required on some systems, is not necessary anymore; - fixed: in the option "NzbAddedProcess" the env-var parameter with nzb-name was passed in "NZBNA_NAME", should be "NZBNA_NZBNAME"; the old parameter name "NZBNA_NAME" is still supported for compatibility; - fixed: download time in statistics were incorrect if the computer was put into standby (thanks Frank Kuypers for the patch); - fixed: when option was active and the download after unpack contained rar-file with the same name as one of original files (sometimes happen with included subtitles) the original rar-file was kept with name <.rar_duplicate1> even if the option was active; - fixed: failed to read download queue from disk if post-processing queue was not empty; - fixed: when a duplicate file was detected during download the program could hang; - fixed: symbol must be defined in project settings; defining it in didn't work properly (Windows only); - fixed: crash when adding malformed nzb-files with certain structure (Windows only); - fixed: by deleting of a partially downloaded nzb-file from queue, when the option was active, the file <_brokenlog.txt> was not deleted preventing the directory from automatic deletion; - fixed: if an error occurs when a RPC-client or web-browser communicates with nzbget the program could crash; - fixed: if the last file of collection was detected as duplicate after the download of the first article the file was deleted from queue (that's OK) but the post-processing was not triggered (that's a bug); - fixed: support for splitted files (.001, .002, etc.) were broken. nzbget-10.2: - fixed potential segfault which could happen with file paths longer than 1024 characters; - fixed: when options and were both active, a restart or reload of the program during download may cause damaged files in the active download; - increased width of speed indication ui-element to avoid layout breaking on some linux-browsers; - fixed a race condition in unpacker which could lead to a segfault (although the chances were low because the code wasn't executed often). nzbget-10.1: - fixed: articles with decoding errors (incomplete or damaged posts) caused infinite retry-loop in downloader. nzbget-10.0: - added built-in unpack: - rar and 7-zip formats are supported (via external Unrar and 7-Zip executables); - new options , , , , ; - web-interface now shows progress and estimated time during unpack (rar only; for 7-Zip progress is not available due to limitations of 7-Zip); - when built-in unpack is enabled, the post-processing script is called after unpack and possibly par-check/repair (if needed); - for nzb-files containing multiple collections (par-sets) the post-processing script is called only once, after the last par-set; - new parameter passed to post-processing script; - if the option is enabled the post-processing- script is called after each par-set (as in previous versions); - example post-processing script updated: removed unrar-code, added check for unpack status; - new field in result of RPC-method ; - history-dialog in web-interface shows three status: par-status, unpack-status, script-status; - with two built-in special post-processing parameters <*Unpack:> and <*Unpack:Password> the unpack can be disabled for individual nzb-file or the password can be set; - built-in special post-processing parameters can be set via web- interface on page (when built-in unpack is enabled); - added support for HTTPS to the built-in web-server (web-interface and XML/JSON-RPC): - new options , , and ; - module completely rewritten with support for server- side sockets, newer versions of GnuTLS, proper thread lockings in OpenSSL; - improved the automatic par-scan (option ) to significantly reduce the verify-time in some common cases with renamed rar-files: - the extra files are scanned in an optimized order; - the scan stops when all missings files are found; - added fast renaming of intentionally misnamed (rar-) files: - the new renaming algorithm doesn't require full par-scan and restores original filenames in just a few seconds, even on very slow computers (NAS, media players, etc.); - the fast renaming is performed automatically when requested by the built-in unpacker (option must be active); - added new option to put intermediate files during download into a separate directory (instead of storing them directly in destination directory (option ): - when nzb-file is completely (successfully) downloaded, repaired (if neccessary) and unpacked the files are moved to destination directory (option or ); - intermediate directory can significantly improve unpack performance if it is located on a separate physical hard drive; - added new option to manually select cipher for encrypted communication with news server: - manually choosing a faster cipher (such as ) can significantly improve performance (if CPU is a limiting factor); - major improvements in news-server/connection management (main and fill servers): - if download of article fails, the program tries all servers of the same level before trying higher level servers; - this ensures that fill servers are used only if all main servers fail; - this makes the configuring of multiple servers much easier than before: in most cases the simple configuration of level 0 for all main servers and level 1 for all fill servers suffices; - in previous versions the level was increased immediately after the first tried server of the level failed; to make sure all main servers were tried before downloading from fill servers it was required to create complex server configurations with duplicates; these configurations were still not as effective as now; - do not reconnect on
errors since this doesn't help but unnecessary increases CPU load and network traffic; - removed option ; it's not required anymore; - new option allows more flexible configuration of news servers when using multiple accounts on the same server; with this option it's also possible to imitate the old server management behavior regarding levels; - news servers configuration is now less error-prone: - the option is not required to start from <0> and when several news servers are configured the Levels can be any integers - the program sorts the servers and corrects the Levels to 0,1,2,etc. automatically if needed; - when option is set to <0> the server is ignored (in previous version such a server could cause hanging when the program was trying to go to the next level); - if no news servers are defined (or all definitions are invalid) a warning is printed to inform that the download is not possible; - categories can now have their own destination directories; new option ; - new feature in web-interface; new XML-/JSON-RPC method ; - improved the handling of hanging connections: if a connection hangs longer than defined by option the program tries to gracefully close connection first (this is new); if it still hangs after the download thread is terminated as a last resort (as in previous versions); - added automatic speed meter recalibration to recover after possible synchronization errors which can occur when the option is not active; this makes the default (less accurate but fast) speed meter almost as good as the accurate one; important when speed throttling is active; - when the par-checked requests more par-files, they get an extra priority and are downloaded before other files regardless of their priorities; this is needed to avoid hanging of par-checker-job if a file with a higher priority gets added to queue during par-check; - when post-processing-parameters are passed to the post-processing script a second version of each parameter with a normalized parameter- name is passed in addition to the original parameter name; in the normalized name the special characters <*> and <:> are replaced with <_> and all characters are passed in upper case; this is important for internal post-processing-parameters (*Unpack:=yes/no) which include special characters; - warning now is not printed when the connection was aborted before the request signature was read; - changed formatting of remaining time for post-processing to short format (as used for remaining download time); - added link to article to settings tab on web- interface; - removed hint from history dialog since it caused more questions than helped; - changed default value for option to ; most news servers nowadays do not require joining the group and many servers do not keep headers for many groups making the join-command fail even if the articles still can be successfully downloaded; - small change in example post-processing script: message are now printed only if ts-files really existed; - improved configure-script: - libs which are added via pkgconfig are now put into LIBS instead of LDFLAGS - improves compatibility with newer Linux linkers; - OpenSSL libs/includes are now added using pkgconfig to better handle dependencies; - additional check for libcrypto (part of OpenSSL) ensures the library is added to linker command even if pkgconfig is not used; - adding of local files via web-interface now works in IE10; - if an obsolete option is found in the config file a warning is printed instead of an error and the program is not paused anymore; - fixed: the reported line numbers for configuration errors were sometimes inaccurate; - fixed warning ; - fixed: some XML-/JSON-RPC methods may return negative values for file sizes between 2-4GB; this had also influence on web-interface. - fixed: if an external program (unrar, pp-script, etc.) could not be started, the execute-function has returned code 255 although the code -1 were expected in this case; this could break designed post- processing flow; - fixed: some characters with codes below 32 were not properly encoded in JSON-RPC; sometimes output from unrar contained such characters and could break web-interface; - fixed: special characters (quotation marks, etc.) in unpack password and in configuration options were not displayed properly and could be discarded on saving; nzbget-9.1: - added full par-scan feature needed to par-check/repair files which were renamed after creation of par-files: - new option to activate full par-scan (always or automatic); the automatic full par-scan activates if missing files are detected during par-check, this avoids unnecessary full scan for normal (not renamed) par sets; - improved the post-processing script to better handle renamed rar-files; - replaced a browser error message when trying to add local files in IE9 with a better message dialog; nzbget-9.0: - changed version naming scheme by removing the leading zero: current version is now called 9.0 instead of 0.9.0 (it's really the 9th major version of the program); - added built-in web-interface: - completely new designed and written from scratch; - doesn't require a separate web-server; - doesn't require PHP; - 100% Javascript application; the built-in web-server hosts only static files; the javascript app communicates with NZBGet via JSON-RPC; - very efficient usage of server resources (CPU and memory); - easy installation. Since neither a separate web-server nor PHP are needed the installation of new web-interface is very easy. Actually it is performed automatically when you "make install" or "ipkg install nzbget"; - modern look: better layout, popup dialogs, nice animations, hi-def icons; - built-in phone-theme (activates automatically); - combined view for "currently downloading", "queued", "currently processing" and "queued for processing"; - renaming of nzb-files; - multiselect with multiedit or merge of downloads; - fast paging in the lists (downloads, history, messages); - search box for filtering in the lists (downloads, history, messages) and in settings; - adding nzb-files to download queue was improved in several ways: - add multiple files at once. The "select files dialog" allows to select multiple files; - add files using drag and drop. Just drop the files from your file manager directly into the web-browser; - add files via URLs. Put the URL and NZBGet downloads the nzb-file and adds it to download queue automatically; - the priority of nzb-file can now be set when adding local-files or URLs; - the history can be cleared completely or selected items can be removed; - file mode is now nzb-file related; - added the ability to queue URLs: - the program automatically downloads nzb-files from given URLs and put them to download queue. - when multiple URLs are added in a short time, they are put into a special URL-queue. - the number of simultaneous URL-downloads are controlled via new option UrlConnections. - with the new option ReloadUrlQueue can be controlled if the URL-queue should be reloaded after the program is restarted (if the URL-queue was not empty). - new switch <-U> for remote-command <--append/-A> to queue an URL. - new subcommand <-U> in the remote command <--list/-L> prints the current URL-queue. - if URL-download fails, the URL is moved into history. - with subcommand <-R> of command <--edit> the failed URL can be returned to URL-queue for redownload. - the remote command <--list/-L> for history can now print the infos for URL history items. - new XML/JSON-RPC command to add an URL or multiple URLs for download. - new XML/JSON-RPC command returns the items from the URL-queue. - the XML/JSON-RPC command was extended to provide infos about URL history items. - the URL-queue obeys the pause-state of download queue. - the URL-downloads support HTTP and HTTPS protocols; - added new field to nzb-info-object. - it is initially set to the cleaned up name of the nzb-file. - the renaming of the group changes this field. - all RPC-methods related to nzb-object return the new field, the old field is now deprecated. - the option now checks the -field instead of (the latter is not changed when the nzb is renamed). - new env-var-parameter for post-processing script; - added options and for remote command <--edit/-E>. With these options the name of group or file can be used in edit-command instead of file ID; - added support for regular expressions (POSIX ERE Syntax) in remote commands <--list/-L> and <--edit/-E> using new subcommands and ; - improved performance of RPC-command ; - added new command to RPC-method to set the order of individual files in the group; - added gzip-support to built-in web-server (including RPC); - added processing of http-request in RPC-server for better support of cross domain requests; - renamed example configuration file and postprocessing script to make the installation easier; - improved the automatic installation () to install all necessary files (not only the binary as it was before); - improved handling of configuration errors: the program now does not terminate on errors but rather logs all of them and uses default option values; - added new XML/JSON-RPC methods , and ; - with active option the NZB considered completed even if there are paused non-par-files (the paused non-par-files are treated the same way as paused par-files): as a result the reprocessable script is called; - added subcommand to remote command <-S/--scan> to scan synchronously (wait until scan completed); - added parameter to XML/JSON-RPC method ; - the command in web-interface now waits for completing of scan before reporting the status; - added remote command <--reload/-O> and JSON/XML-RPC method to reload configuration from disk and reintialize the program; the reload can be performed from web-interface; - JSON/XML-RPC method extended with parameter ; - categories available in web-interface are now configured in program configuration file (nzbget.conf) and can be managed via web-interface on settings page; - updated descriptions in example configuration file; - changes in configuration file: - renamed options , and to , and to avoid confusion with news-server options , and ; - the old option names are still recognized and are automatically renamed when the configuration is saved from web-interface; - also renamed option <$MAINDIR> to ; - extended remote command <--append/-A> with optional parameters: - - adds the file/URL to the top of queue; -

- pauses added files; - - sets category for added nzb-file/URL; - - sets nzb filename for added URL; - the old switches <--category/-K> and <--top/-T> are deprecated but still supported for compatibility; - renamed subcommand of command <--edit/-E> to (the old subcommand is still supported for compatibility); - added new option to setup a script called after a nzb-file is added to queue; - added debug messages for speed meter; - improved the startup script so it can be directly used in without modifications; - fixed: after renaming of a group, the new name was not displayed by remote commands <-L G> and <-C in curses mode>; - fixed incompatibility with OpenSLL 1.0 (thanks to OpenWRT team for the patch); - fixed: RPC-method could return wrong results if the log was filtered with options ; - fixed: free disk space calculated incorrectly on some OSes; - fixed: unrar failure was not always properly detected causing the post-processing to delete not yet unpacked rar-files; - fixed compilation error on recent linux versions; - fixed compilation error on older systems; nzbget-0.8.0: - added priorities; new action for remote command <--edit/-E> to set priorities for groups or individual files; new actions and of RPC-command ; remote command <--list/-L> prints priorities and indicates files or groups being downloaded; ncurses-frontend prints priorities and indicates files or groups being download; new command to set priority of nzb-file from nzbprocess-script; RPC-commands and return priorities and indicate files or groups being downloaded; - added renaming of groups; new subcommand for command <--edit/-E>; new action for RPC-method ; - added new option , which enables syncronisation in speed meter; that makes the indicated speed more accurate by eliminating measurement errors possible due thread conflicts; thanks to anonymous nzbget user for the patch; - improved the parsing of filename from article subject; - option now efficiently works on Windows with NTFS partitions; - added URL-based-authentication as alternative to HTTP-header authentication for XML- and JSON-RPC; - fixed: nzb-files containing umlauts and other special characters could not be parsed - replaced XML-Reader with SAX-Parser - only on POSIX (not on Windows); - fixed incorrect displaying of group sizes bigger than 4GB on many 64-bit OSes; - fixed a bug causing error on decoding of input data in JSON-RPC; - fixed a compilation error on some windows versions; - fixed: par-repair could fail when the filenames were not correctly parsed from article subjects; - fixed a compatibility issue with OpenBSD (and possibly other BSD based systems); added the automatic configuring of required signal handling logic to better support BSD without breaking the compatibility with certain Linux systems; - corrected the address of Free Software Foundation in copyright notice. nzbget-0.7.0: - added history: new option , new remote subcommand for commands (list history entries) and (delete history entries, return history item, postprocess history item), new RPC-command and subcommands , , for command ; - added support for JSON-P (extension of JSON-RPC); - changed the result code returning status for postprocessing script from <1> to <94> (needed to show the proper script status in history); - improved the detection of new files in incoming nzb directory: now the scanner does not rely on system datum, but tracks the changing of file sizes during a last few () seconds instead; - improvements in example postprocessing script: 1) if download contains only par2-files the script do not delete them during cleanup; 2) if download contains only nzb-files the script moves them to incoming nzb-directory for further download; - improved formatting of groups and added time info in curses output mode; - added second pause register, which is independent of main pause-state and therfore is intended for usage from external scripts; that allows to pause download without interfering with options and and scheduler tasks and - they all work with first (default) pause register; new subcommand for commands <--pause/-P> and <--unpause/-U>; new RPC-command and ; existing RPC-commands und renamed to and ; new field in result struct for RPC-command ; existing fields and renamed to and ; old RPC-commands and fields still exist for compatibility; the status output of command <--list/-L> indicates the state of second pause register; key

in curses-frontend can unpause second pause-register; - nzbprocess-script (option ) can now set category and post-processing parameters for nzb-file; - redesigned server pool and par-checker to avoid using of semaphores (which are very platform specific); - added subcommand to remote commands <--pause/-P> and <--unpause/-U> to pause/unpause the scanning of incoming nzb-directory; - added commands and for scheduler option ; - added remote commands and for XML-/JSON-RPC; - command now not only pauses the post-processing queue but also pauses the current post-processing job (par-job or script-job); however the script-job can be paused only after the next line printed to screen; - improved error reporting while parsing nzb-files; - added field to NZBInfo; the field is now returned by XML-/JSON-RPC methods , and ; - improvements in configure script; - added support for platforms without IPv6 (they do not have ); - debug-messages generated on early stages during initializing are now printed to screen/log-file; - messages about obsolete options are now printed to screen/log-file; - imporved example postprocessing script: added support for external configuration file, postprocessing parameters and configuration via web-interface; - option now can contain parameters which must be passed to the script; - added pausing/resuming for post-processor queue; added new modifier to remote commands <--pause/-P> and <--unpause/-U>; added new commands and to XML-/JSON-RPC; extended output of remote command <--list/-L> to indicate paused state of post-processor queue; extended command of XML-/JSON-RPC with field ; - changed the command line syntax for requesting of post-processor queue from <-O> to <-L O> for consistency with other post-queue related commands (<-P O>, <-U O> and <-E O>); - improved example post-processing script: added support for delayed par-check (try unrar first, par-repair if unrar failed); - added modifier to command <-E/--edit> for editing of post-processor-queue; following subcommands are supported: <+/-offset>, , , ; subcommand supports deletion of queued post-jobs and active job as well; deletion of active job means the cancelling of par-check/repair or terminating of post-processing-script (including child processes of the script); updated remote-server to support new edit-subcommands in XML/JSON-RPC; - extended the syntax of option in two ways: 1) it now accepts multiple comma-separated values; 2) an asterix as hours-part means ; - added svn revision number to version string (commands <-v> and <-V>, startup log entry); svn revision is automatically read from svn-repository on each build; - added estimated remaining time and better distinguishing of server state in command <--list/-L>; - added new return code (93) for post-processing script to indicate successful processing; that results in cleaning up of download queue if option is active; - added readonly options , and for usage in processing scripts (options are available as environment variables , and ); - renamed ParStatus constant to for a consistence with ScriptStatus constant , that also affects the results of RPC-command ; - added a new return code <95/POSTPROCESS_NONE> for post-processing scripts for cases when pp-script skips all post-processing work (typically upon a user's request via a pp-parameter); modified the example post-processing script to return the new code instead of a error code when a pp-parameter was set to ; - added field to result of RPC-Command and fields and for command ; - in and output-modes the download speed is now printed with one decimal digit when the speed is lower than 10 KB/s; - improvement in example post-processing script: added check for existence of and command ; - added shell batch file for windows (nzbget-shell.bat); thanks to orbisvicis (orbisvicis@users.sourceforge.net) for the script; - added debian style init script (nzbgetd); thanks to orbisvicis (orbisvicis@users.sourceforge.net) for the script; - added the returning of a proper HTTP error code if the authorization was failed on RPC-calls; thanks to jdembski (jdembski@users.sourceforge.net) for the patch; - changed the sleep-time during the throttling of bandwidth from 200ms to 10ms in order to achieve better uniformity; - modified example postprocessing script to not use the command , which is not always available; thanks to Ger Teunis for the patch; - improved example post-processing script: added the check for existence of destination directory to return a proper ERROR-code (important for reprocessing of history items); - by saving the queue to disk now using relative paths for the list of compeled files to reduce the file's size; - eliminated few compiler warnings on GCC; - fixed: when option was specified and nzbget was started as root, the lockfile was not removed; - fixed: nothing was downloaded when the option was set to <0>; - fixed: base64 decoding function used by RPC-method sometimes failed, in particular when called from Ruby-language; - fixed: JSON-RPC-commands failed, if parameters were placed before method name in the request; - fixed: RPC-method did not work properly on Posix systems (it worked only on Windows); - fixed compilation error when using native curses library on OpenSolaris; - fixed linking error on OpenSolaris when using GnuTLS; - fixed: option did not work; - fixed: seg. fault in service mode on program start (Windows only); - fixed: environment block was not passed correctly to child process, what could result in seg faults (windows only); - fixed: returning the postprocessing exit code <92 - par-check all collections> when there were no par-files results in endless calling of postprocessing script; - fixed compatibility issues with OS/2. nzbget-0.6.0: - added scheduler; new options , , , and ; - added support for postprocess-parameters; new subcommand of remote command to add/modify pp-parameter for group (nzb-file); new XML-/JSON-RPC-subcommand of method for the same purpose; updated example configuration file and example postprocess-script to indicate new method of passing arguments via environment variables; - added subcommands , and to command line switch <-L/--list>, which prints list of files, groups or only status info respectively; extended binary communication protocol to transfer nzb-infos in addition to file-infos; - added new subcommand to edit-command for merging of two (or more) groups (useful after adding pars from a separate nzb-file); - added option to automatically merge nzb-files with the same filename (useful by adding pars from a different source); - added script-processing of files in incoming directory to allow automatic unpacking and queueing of compressed nzb-files; new option ; - added the printing of post-process-parameters for groups in command <--list G>; - added the printing of nzbget version into the log-file on start; - added option to automatically delete already downloaded files from disk if nzb-file was deleted from queue (the download was cancelled); - added option to define the max time allowed for par-repair; - added command <--scan/-S> to execute the scan of nzb-directory on remote server; - changed the method to pass arguments to postprocess/nzbprocess: now using environment variables (old method is still supported for compatibility with existing scripts); - added the passing of nzbget-options to postprocess/nzbprocess scripts as environment variables; - extended the communication between nzbget and post-process-script: collections are now detected even if parcheck is disabled; - added support for delayed par-check/repair: post-process-script can request par-check/repair using special exit codes to repair current collection or all collections; - implemented the normalizing of option names and values in option list; the command <-p> also prints normalized names and values now; that makes the parsing of output of command <-p> for external scripts easier; - replaced option with new option which is now used by all scripts (PostProcess, NzbProcess, TaskX.Process); - improved entering to paused state on connection errors (do not retry failed downloads if pause was activated); - improved error reporting on decoding failures; - improved compatibility of yenc-decoder; - improved the speed of deleting of groups from download queue (by avoiding the saving of queue after the deleting of each individual file); - updated configure-script for better compatibility with FreeBSD; - cleaning up of download queue (option ) and deletion of source nzb-file (option ) after par-repair now works also if par-repair was cancelled (option ); since required par-files were already downloaded the repair in an external tool is possible; - added workaround to avoid hangs in child processes (by starting of postprocess or nzbprocess), observed on uClibC based systems; - fixed: TLS/SSL didn't work in standalone mode; - fixed compatibility issues with Mac OS X; - fixed: not all necessary par2-files were unpaused on first request for par-blocks (although harmless, because additional files were unpaused later anyway); - fixed small memory leak appeared if process-script could not be started; - fixed: configure-script could not detect the right syntax for function on OpenSolaris. - fixed: files downloaded with disabled decoder (option decode=no) sometimes were malformed and could not be decoded; - fixed: empty string parameters did not always work in XML-RPC. nzbget-0.5.1: - improved the check of server responses to prevent unnecessary retrying if the article does not exist on server; - fixed: seg.fault in standalone mode if used without specifying the category (e.g. without switch <-K>); - fixed: download speed indicator could report not-null values in standby-mode (when paused); - fixed: parameter in JSON/XML-RPC was not properly decoded by server, making the setting of a nested category (containing slash or backslash character) via nzbgetweb not possible; nzbget-0.5.0: - added TLS/SSL-support for encrypted communication with news-servers; - added IPv6-support for communication with news-servers as well as for communication between nzbget-server and nzbget-client; - added support for categories to organize downloaded files; - new option to create the subdirectory for each category; - new switch <-K> for usage with switch <-A> to define a category during the adding a file to download queue; - new command in switch <-E> to change the category of nzb-file in download queue; the already downloaded files are automatically moved to new directory if the option is active; - new parameter in XML-/JSON-RPC-command to allow the changing of category via those protocols; - new parameter in a call to post-process-script with category name; - scanning of subdirectories inside incoming nzb-directory to automatically assign category names; nested categories are supported; - added option to connect to servers, that do not accept -command; - added example post-process script for unraring of downloaded files (POSIX only); - added options and useful on slow CPUs; - added option to delete source nzb-file after successful download and parcheck; - switch <-P> can now be used together with switches <-s> and <-D> to start server/daemon in paused state; - changed the type of messages logged in a case of connection errors from to to provide better error reporting; - now using OS-specific line-endings in log-file and brokenlog-file: LF on Posix and CRLF on Windows; - added detection of adjusting of system clock to correct uptime/download time (for NAS-devices, that do not have internal clock and set time from internet after booting, while nzbget may be already running); - added the printing of stack on segmentation faults (if configured with <--enable-debug>, POSIX only); - added option for better debugging on Linux in a case of abnormal program termination; - fixed: configure-script could not automatically find libsigc++ on 64-bit systems; - few other small fixes; nzbget-0.4.1: - to avoid accidental deletion of file in curses-frontend the key now must be pressed in uppercase; - options and in news-server's configuration are now optional; - added the server's name to the detail-log-message, displayed on the start of article's download; - added the option to help to post-process-scripts, which make par-check/-repair on it's own; - improved download-speed-meter: it uses now a little bit less cpu and calculates the speed for the last 30 seconds (instead of 5 seconds), providing better accuracy; Thanks to ydrol for the patch; - reduced CPU-usage in curses-outputmode; Thanks to ydrol for the patch ; - fixed: line-endings in windows-style (CR-LF) in config-file were not read properly; - fixed: trailing spaces in nzb-filenames (before the file's extension) caused errors on windows. Now they will be trimmed; - fixed: XML-RPC and JSON-RPC did not work on Big-Endian-CPUs (ARM, PPC, etc), preventing the using of web-interface; - fixed: umask-option did not allow to enable write-permissions for and ; - fixed: in curses-outputmode the remote-client showed on first screen-update only one item of queue; - fixed: edit-commands with negative offset did not work via XML-RPC (but worked via JSON-RPC); - fixed incompatibility issues with gcc 4.3; Thanks to Paul Bredbury for the patch; - fixed: segmentation fault if a file listed in nzb-file does not have any segments (articles); nzbget-0.4.0: - added the support for XML-RPC and JSON-RPC to easier control the server from other applications; - added web-interface - it is available for download from NZBGet-project's home page as a separate package "web-interface"; - added the automatic cleaning up of the download queue (deletion of unneeded paused par-files) after successful par-check/repair - new option ; - added option to allow to filter the (not so important) log-messages from articles' downloads (they have now the type instead of ); - added the gathering of progress-information during par-check; it is available via XML-RPC or JSON-RPC; it is also showed in web-interface; - improvements in internal decoder: added support for yEnc-files without ypart-statement (sometimes used for small files); added support for UU-format; - removed support for uulib-decoder (it did not work well anyway); - replaced the option with the option ; - added detection of errors and (special case for NNTPCache-server) to consider them as connect-errors (and therefore not count as retries); - added check for incomplete articles (also mostly for NNTPCache-server) to differ such errors from CrcErrors (better error reporting); - improved error-reporting on moving of completed files from tmp- to dst-directory and added code to move files across drives if renaming fails; - improved handling of nzb-files with multiple collections in par-checker; - improved the parchecker: added the detection and processing of files splitted after parring; - added the queueing of post-process-scripts and waiting for script's completion before starting of a next job in postprocessor (par-job or script) to provide more balanced cpu utilization; - added the redirecting of post-process-script's output to log; new option to specify the default message-kind for unformatted log-messages; - added the returning of script-output by command via XML-RPC and JSON-RPC; the script-output is also showed in web-interface; - added the saving and restoring of the post-processor-queue (if server was stopped before all items were processed); new option ; - added new parameter to postprocess-script to indicate if any of par-jobs for the same nzb-file failed; - added remote command (switch O/--post) to request the post-processor-queue from server; - added remote command (switch -W/--write) to write messages to server's log; - added option to automatically pause the download on low disk space; - fixed few incompatibility-issues with unslung-platform on nslu2 (ARM); - fixed: articles with trailing text after binary data caused the decode failures and the reporting of CRC-errors; - fixed: dupecheck could cause seg.faults when all articles for a file failed; - fixed: by dupe-checking of files contained in nzb-file the files with the same size were ignored (not deleted from queue); - updated libpar2-patch for msvc to fix a segfault in libpar2 (windows only); - fixed: by registering the service on windows the fullpath to nzbget.exe was not always added to service's exename, making the registered service unusable; - fixed: the pausing of a group could cause the start of post-processing for that group; - fixed: too many info-messages could be printed during par-check (appeared on posix only); 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) decreases 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 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 newsgroup-file is added to download queue. Each file can therefore be managed separately (paused, deleted or moved); - Current queue state is saved after every change (file is completed or the queue is changed - entries paused, deleted or moved). The state is saved on disk using internal format, which allows fast loading on next program start (no need to parse xml-files again); - The remaining download-size is updated after every article is completed to indicate the correct remaining size and time for total files in queue; - Downloaded articles, which are saved in temp-directory, can be reused on next program start, if the file was not completed (option "continuepartial" in config-file); - Along with uulib the program has internal decoder for yEnc-format. This decoder was necessary, because uulib is so slow, that it prevents using of the program on not so powerful systems like linux-routers (MIPSEL CPU 200 MHz). The new decoder is very fast. It is controlled over option "decoder" in config-file; - The decoder can be completely disabled. In this case all downloaded articles are saved in unaltered form and can be joined with an external program; UUDeview is one of them; - If download of article fails, the program attempts to download it again so many times, what the option "retries" in config-file says. This works even if no servers with level higher than "0" defined. After each retry the next server-level is used, if there are no more levels, the program switches to level "0" again. The pause between retries can be set with config-option "retryinterval"; - If despite of a stated connection-timeout (it can be changed via config-option "connectiontimeout") connection hangs, the program tries to cancel the connection (after "terminatetimeout" seconds). If it doesn't work the download thread is killed and the article will be redownloaded in a new thread. This ensures, that there are no long-time hanging connections and all articles are downloaded, when a time to rejoin file comes; - Automatic par-checking and repairing. Only reuired par-files are downloaded. The program uses libpar2 and does not require any external tools. The big advantage of library is, that it allows to continue par-check after new par-blocks were downloaded. This were not possible with external par2cmdline-tool; - There is a daemon-mode now (command-line switch "-D" (--daemon)). In this mode a lock-file (default location "/tmp/nzbget.lock", can be changed via option "lockfile") contains PID of daemon; - The format of configuration-file was changed from xml to more common text-format. It allows also using of variables like "tempdir=${MAINDIR}/tmp"; - Any option of config-file can be overwritten via command-line switch "-o" (--option). This includes also the definition of servers. This means that the program can now be started without a configuration-file at all (all required options must be passed via command-line); - The command-line switches were revised. The dedicated switches to change options in config-file were eliminated, since any option can now be changed via switch "-o" (--option); - If the name of configuration-file was not passed via command-line the program search it in following locations: "~/.nzbget", "/etc/nzbget.conf", "/usr/etc/nzbget.conf", "/usr/local/etc/nzbget.conf", "/opt/etc/nzbget.conf"; - The new command-line switch "-n" (--noconfigfile) prevents the loading of a config-file. All required config-options must be passed via command-line (switch "-o" (--option)); - To start the program in server mode either "-s" (--server) or "-D" (--daemon) switch must be used. If the program started without any parameters it prints help-screen. There is no a dedicated switch to start in a standalone mode. If switches "-s" and "-D" are omitted and none of client-request-switches used the standalone mode is default. This usage of switches is more common to programs like "wget". To add a file to server's download queue use switch "-A" (--append) and a name of nzb-file as last command-line parameter; - There is a new switch "-Q" (--quit) to gracefully stop server. BTW the SIGKIL-signal is now handled appropriately, so "killall nzbget" is also OK, where "killall -9 nzbget" terminates server immediately (helpful if it hangs, but it shouldn't); - With new switch "-T" (--top) the file will be added to the top of download queue. Use it with switch "-A" (--append); - The download queue can be edited via switch "-E" (--edit). It is possible to pause, unpause, delete and move files in queue. The IDs of file(s) to be affected are passed via switch "-I" (fileid), either one ID or a range in a form "IDForm-IDTo". This also means, that every file in queue have ID now; - The switch "-L" (--list) prints IDs of files consequently. It prints also name, size, percentage of completing and state (paused or not) of each file. Plus summary info: number of files, total remaining size and size of paused files, server state (paused or running), number of threads on server, current speed limit; - With new switch "-G" (--log) the last N lines printed to server's screen-log, can be printed on client. The max number of lines which can be returned from servers depends on option "logbuffersize"; - The redesigned Frontends (known as outputmodes "loggable", "colored" and "curses") can connect to (remote) server and behave as if you were running server-instance of program itself (command-line switch "-C" (--connect)). The log-output updates constantly and even all control-functions in ncurses-mode works: pause/unpause server, set download rate limit, edit of queue (pause/unpause, delete, move entries). The number of connected clients is not limited. The "outputmode" on a client can be set independently from server. The client-mode is especially useful if the server runs as a daemon; - The writing to log-file can be disabled via option "createlog". The location of log-file controls the option "log-file"; - Switch "-p" (--printconfig) prints the name of configuration file being used and all option/value-pairs, taking into account all used "-o" (--option) - switches; - The communication protocol between server and client was optimized to minimize the size of transferred data. Instead of fixing the size for Filenames in data-structures to 512 bytes only in fact used data are transferred; - Extensions in ncurses-outputmode: scrolling in queue-list works better, navigation in queue with keys Up, Down, PgUp, PgDn, Home, End. Keys to move entries are "U" (move up), "N" (move down), "T" (move to top), "B" (move to bottom). "P" to pause/unpause file. The size, percentage of completing and state (paused or not) for every file is printed. The header of queue shows number of total files, number of unpaused files and size for all and unpaused files. Better using of screen estate space - no more empty lines and separate header for status (total seven lines gain). The messages are printed on several lines (if they not fill in one line) without trimming now; - configure.ac-file updated to work with recent versions of autoconf/automake. There are new configure-options now: "--disable-uulib" to compile the program without uulib; "--disable-ncurses" to disable ncurses-support (eliminates necessity of ncurses-libs), useful on embedded systems with little resources; "--disable-parcheck" to compile without par-check; - The algorithm for parsing of nzb-files now uses XMLReader instead of DOM-Parser to minimize memory usage (no mor needs to build complete DOM-tree in memory). Thanks to Thierry MERLE for the patch; - The log-file contains now thread-ID for all entry-types and additionally for debug-entries: filename, line number and function's name of source code, where the message was printed. Debug-messages can be disabled in config-file (option "debugtarget") like other messages; - The program is now compatible with windows. Project file for MS Visual C++ 2005 is included. Use "nzbget -install" and "nzbget -remove" to install/remove NZBGet-Service. Servers and clients can run on diferrent operating systems; - Improved compatibility with POSIX systems; Tested on: - Linux Debian 3.1 on x86; - Linux BusyBox with uClibc on MIPSEL; - PC-BSD 1.4 (based on FreeBSD 6.2) on x86; - 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. nzbget-0.2.3 - Fixed problem with losing connection to newsserver after too long idle time - Added functionality for dumping lots of debug info nzbget-0.2.2 - Added Florian Penzkofers fix for FreeBSD, exchanging base functionality in SingleServerPool.cpp with a more elegant solution - Added functionality for forcing answer to reloading queue upon startup of server + use -y option to force from command-line + use "reloadqueue" option in nzbget.cfg to control behavior - Added nzbget.cfg options to control where info, warnings and errors get directed to (either screen, log or both) - Added option "createbrokenfilelog" in nzbget.cfg nzbget-0.2.1 - Changed and extended the TCP client/server interface - Added timeout on sockets which prevents certain types of nzbget hanging - Added Kristian Hermansen's patch for renaming broken files nzbget-0.2.0 - Moved 0.1.2-alt4 to a official release as 0.2.0 - Small fixes nzbget-0.1.2-alt4 - implemented tcp/ip communication between client & server (removing the rather defunct System V IPC) - added queue editing functionality in server-mode nzbget-0.1.2-alt1 - added new ncurses frontend - added server/client-mode (using System V IPC) - added functionality for queueing download requests nzbget-0.1.2 - performance-improvements - commandline-options - fixes nzbget-0.1.1 - new output - fixes nzbget-0.1.0a - compiling-fixes nzbget-0.1.0 - initial release nzbget-12.0+dfsg/ColoredFrontend.cpp000066400000000000000000000111501226450633000174360ustar00rootroot00000000000000/* * This file if part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2010 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 544 $ * $Date: 2013-01-18 22:36:17 +0100 (Fri, 18 Jan 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #ifndef WIN32 #include #endif #include "nzbget.h" #include "ColoredFrontend.h" #include "Util.h" ColoredFrontend::ColoredFrontend() { m_bSummary = true; m_bNeedGoBack = false; #ifdef WIN32 m_hConsole = GetStdHandle(STD_OUTPUT_HANDLE); #endif } void ColoredFrontend::BeforePrint() { if (m_bNeedGoBack) { // go back one line #ifdef WIN32 CONSOLE_SCREEN_BUFFER_INFO BufInfo; GetConsoleScreenBufferInfo(m_hConsole, &BufInfo); BufInfo.dwCursorPosition.Y--; SetConsoleCursorPosition(m_hConsole, BufInfo.dwCursorPosition); #else printf("\r\033[1A"); #endif m_bNeedGoBack = false; } } void ColoredFrontend::PrintStatus() { char tmp[1024]; char timeString[100]; timeString[0] = '\0'; int iCurrentDownloadSpeed = m_bStandBy ? 0 : m_iCurrentDownloadSpeed; if (iCurrentDownloadSpeed > 0 && !(m_bPauseDownload || m_bPauseDownload2)) { long long remain_sec = (long long)(m_lRemainingSize / iCurrentDownloadSpeed); int h = (int)(remain_sec / 3600); int m = (int)((remain_sec % 3600) / 60); int s = (int)(remain_sec % 60); sprintf(timeString, " (~ %.2d:%.2d:%.2d)", h, m, s); } char szDownloadLimit[128]; if (m_iDownloadLimit > 0) { sprintf(szDownloadLimit, ", Limit %.0f KB/s", (float)m_iDownloadLimit / 1024.0); } else { szDownloadLimit[0] = 0; } char szPostStatus[128]; if (m_iPostJobCount > 0) { sprintf(szPostStatus, ", %i post-job%s", m_iPostJobCount, m_iPostJobCount > 1 ? "s" : ""); } else { szPostStatus[0] = 0; } #ifdef WIN32 char* szControlSeq = ""; #else printf("\033[s"); const char* szControlSeq = "\033[K"; #endif snprintf(tmp, 1024, " %d threads, %.*f KB/s, %.2f MB remaining%s%s%s%s%s\n", m_iThreadCount, (iCurrentDownloadSpeed >= 10*1024 ? 0 : 1), (float)iCurrentDownloadSpeed / 1024.0, (float)(Util::Int64ToFloat(m_lRemainingSize) / 1024.0 / 1024.0), timeString, szPostStatus, m_bPauseDownload || m_bPauseDownload2 ? (m_bStandBy ? ", Paused" : ", Pausing") : "", szDownloadLimit, szControlSeq); tmp[1024-1] = '\0'; printf("%s", tmp); m_bNeedGoBack = true; } void ColoredFrontend::PrintMessage(Message * pMessage) { #ifdef WIN32 switch (pMessage->GetKind()) { case Message::mkDebug: SetConsoleTextAttribute(m_hConsole, 8); printf("[DEBUG]"); break; case Message::mkError: SetConsoleTextAttribute(m_hConsole, 4); printf("[ERROR]"); break; case Message::mkWarning: SetConsoleTextAttribute(m_hConsole, 5); printf("[WARNING]"); break; case Message::mkInfo: SetConsoleTextAttribute(m_hConsole, 2); printf("[INFO]"); break; case Message::mkDetail: SetConsoleTextAttribute(m_hConsole, 2); printf("[DETAIL]"); break; } SetConsoleTextAttribute(m_hConsole, 7); char* msg = strdup(pMessage->GetText()); CharToOem(msg, msg); printf(" %s\n", msg); free(msg); #else const char* msg = pMessage->GetText(); switch (pMessage->GetKind()) { case Message::mkDebug: printf("[DEBUG] %s\033[K\n", msg); break; case Message::mkError: printf("\033[31m[ERROR]\033[39m %s\033[K\n", msg); break; case Message::mkWarning: printf("\033[35m[WARNING]\033[39m %s\033[K\n", msg); break; case Message::mkInfo: printf("\033[32m[INFO]\033[39m %s\033[K\n", msg); break; case Message::mkDetail: printf("\033[32m[DETAIL]\033[39m %s\033[K\n", msg); break; } #endif } void ColoredFrontend::PrintSkip() { #ifdef WIN32 printf(".....\n"); #else printf(".....\033[K\n"); #endif } void ColoredFrontend::BeforeExit() { if (IsRemoteMode()) { printf("\n"); } } nzbget-12.0+dfsg/ColoredFrontend.h000066400000000000000000000026401226450633000171070ustar00rootroot00000000000000/* * This file if part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 397 $ * $Date: 2011-05-24 14:52:41 +0200 (Tue, 24 May 2011) $ * */ #ifndef COLOREDFRONTEND_H #define COLOREDFRONTEND_H #include "LoggableFrontend.h" #include "Log.h" class ColoredFrontend : public LoggableFrontend { private: bool m_bNeedGoBack; #ifdef WIN32 HANDLE m_hConsole; #endif protected: virtual void BeforePrint(); virtual void PrintMessage(Message* pMessage); virtual void PrintStatus(); virtual void PrintSkip(); virtual void BeforeExit(); public: ColoredFrontend(); }; #endif nzbget-12.0+dfsg/Connection.cpp000066400000000000000000000417211226450633000164550ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 911 $ * $Date: 2013-11-24 20:29:52 +0100 (Sun, 24 Nov 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 // SKIP_DEFAULT_WINDOWS_HEADERS prevents the including of , which includes "winsock.h", // but we need "winsock2.h" here (they conflicts with each other) #define SKIP_DEFAULT_WINDOWS_HEADERS #include "win32.h" #endif #include #include #include #ifdef WIN32 #include #include #else #include #include #include #include #include #include #include #endif #include "nzbget.h" #include "Connection.h" #include "Log.h" static const int CONNECTION_READBUFFER_SIZE = 1024; #ifndef HAVE_GETADDRINFO #ifndef HAVE_GETHOSTBYNAME_R Mutex* Connection::m_pMutexGetHostByName = NULL; #endif #endif void Connection::Init() { debug("Initializing global connection data"); #ifdef WIN32 WSADATA wsaData; int err = WSAStartup(MAKEWORD(2, 0), &wsaData); if (err != 0) { error("Could not initialize socket library"); return; } if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE( wsaData.wVersion ) != 0) { error("Could not initialize socket library"); WSACleanup(); return; } #endif #ifndef DISABLE_TLS TLSSocket::Init(); #endif #ifndef HAVE_GETADDRINFO #ifndef HAVE_GETHOSTBYNAME_R m_pMutexGetHostByName = new Mutex(); #endif #endif } void Connection::Final() { debug("Finalizing global connection data"); #ifdef WIN32 WSACleanup(); #endif #ifndef DISABLE_TLS TLSSocket::Final(); #endif #ifndef HAVE_GETADDRINFO #ifndef HAVE_GETHOSTBYNAME_R delete m_pMutexGetHostByName; #endif #endif } Connection::Connection(const char* szHost, int iPort, bool bTLS) { debug("Creating Connection"); m_szHost = NULL; m_iPort = iPort; m_bTLS = bTLS; m_szCipher = NULL; m_eStatus = csDisconnected; m_iSocket = INVALID_SOCKET; m_iBufAvail = 0; m_iTimeout = 60; m_bSuppressErrors = true; m_szReadBuf = (char*)malloc(CONNECTION_READBUFFER_SIZE + 1); #ifndef DISABLE_TLS m_pTLSSocket = NULL; m_bTLSError = false; #endif if (szHost) { m_szHost = strdup(szHost); } } Connection::Connection(SOCKET iSocket, bool bTLS) { debug("Creating Connection"); m_szHost = NULL; m_iPort = 0; m_bTLS = bTLS; m_szCipher = NULL; m_eStatus = csConnected; m_iSocket = iSocket; m_iBufAvail = 0; m_iTimeout = 60; m_bSuppressErrors = true; m_szReadBuf = (char*)malloc(CONNECTION_READBUFFER_SIZE + 1); #ifndef DISABLE_TLS m_pTLSSocket = NULL; m_bTLSError = false; #endif } Connection::~Connection() { debug("Destroying Connection"); Disconnect(); free(m_szHost); free(m_szCipher); free(m_szReadBuf); #ifndef DISABLE_TLS delete m_pTLSSocket; #endif } void Connection::SetSuppressErrors(bool bSuppressErrors) { m_bSuppressErrors = bSuppressErrors; #ifndef DISABLE_TLS if (m_pTLSSocket) { m_pTLSSocket->SetSuppressErrors(bSuppressErrors); } #endif } void Connection::SetCipher(const char* szCipher) { free(m_szCipher); m_szCipher = szCipher ? strdup(szCipher) : NULL; } bool Connection::Connect() { debug("Connecting"); if (m_eStatus == csConnected) { return true; } bool bRes = DoConnect(); if (bRes) { m_eStatus = csConnected; } else { DoDisconnect(); } return bRes; } bool Connection::Disconnect() { debug("Disconnecting"); if (m_eStatus == csDisconnected) { return true; } bool bRes = DoDisconnect(); m_eStatus = csDisconnected; m_iSocket = INVALID_SOCKET; m_iBufAvail = 0; return bRes; } bool Connection::Bind() { debug("Binding"); if (m_eStatus == csListening) { return true; } #ifdef HAVE_GETADDRINFO struct addrinfo addr_hints, *addr_list, *addr; char iPortStr[sizeof(int) * 4 + 1]; // is enough to hold any converted int memset(&addr_hints, 0, sizeof(addr_hints)); addr_hints.ai_family = AF_UNSPEC; // Allow IPv4 or IPv6 addr_hints.ai_socktype = SOCK_STREAM, addr_hints.ai_flags = AI_PASSIVE; // For wildcard IP address sprintf(iPortStr, "%d", m_iPort); int res = getaddrinfo(m_szHost, iPortStr, &addr_hints, &addr_list); if (res != 0) { error("Could not resolve hostname %s", m_szHost); return false; } m_iSocket = INVALID_SOCKET; for (addr = addr_list; addr != NULL; addr = addr->ai_next) { m_iSocket = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); if (m_iSocket != INVALID_SOCKET) { int opt = 1; setsockopt(m_iSocket, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt)); res = bind(m_iSocket, addr->ai_addr, addr->ai_addrlen); if (res != -1) { // Connection established break; } // Connection failed closesocket(m_iSocket); m_iSocket = INVALID_SOCKET; } } freeaddrinfo(addr_list); #else struct sockaddr_in sSocketAddress; memset(&sSocketAddress, 0, sizeof(sSocketAddress)); sSocketAddress.sin_family = AF_INET; if (!m_szHost || strlen(m_szHost) == 0) { sSocketAddress.sin_addr.s_addr = htonl(INADDR_ANY); } else { sSocketAddress.sin_addr.s_addr = ResolveHostAddr(m_szHost); if (sSocketAddress.sin_addr.s_addr == (unsigned int)-1) { return false; } } sSocketAddress.sin_port = htons(m_iPort); m_iSocket = socket(PF_INET, SOCK_STREAM, 0); if (m_iSocket == INVALID_SOCKET) { ReportError("Socket creation failed for %s", m_szHost, true, 0); return false; } int opt = 1; setsockopt(m_iSocket, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt)); int res = bind(m_iSocket, (struct sockaddr *) &sSocketAddress, sizeof(sSocketAddress)); if (res == -1) { // Connection failed closesocket(m_iSocket); m_iSocket = INVALID_SOCKET; } #endif if (m_iSocket == INVALID_SOCKET) { ReportError("Binding socket failed for %s", m_szHost, true, 0); return false; } if (listen(m_iSocket, 100) < 0) { ReportError("Listen on socket failed for %s", m_szHost, true, 0); return false; } m_eStatus = csListening; return true; } int Connection::WriteLine(const char* pBuffer) { //debug("Connection::WriteLine"); if (m_eStatus != csConnected) { return -1; } int iRes = send(m_iSocket, pBuffer, strlen(pBuffer), 0); return iRes; } bool Connection::Send(const char* pBuffer, int iSize) { debug("Sending data"); if (m_eStatus != csConnected) { return false; } int iBytesSent = 0; while (iBytesSent < iSize) { int iRes = send(m_iSocket, pBuffer + iBytesSent, iSize-iBytesSent, 0); if (iRes <= 0) { return false; } iBytesSent += iRes; } return true; } char* Connection::ReadLine(char* pBuffer, int iSize, int* pBytesRead) { if (m_eStatus != csConnected) { return NULL; } char* pBufPtr = pBuffer; iSize--; // for trailing '0' int iBytesRead = 0; int iBufAvail = m_iBufAvail; // local variable is faster char* szBufPtr = m_szBufPtr; // local variable is faster while (iSize) { if (!iBufAvail) { iBufAvail = recv(m_iSocket, m_szReadBuf, CONNECTION_READBUFFER_SIZE, 0); if (iBufAvail < 0) { ReportError("Could not receive data on socket", NULL, true, 0); break; } else if (iBufAvail == 0) { break; } szBufPtr = m_szReadBuf; m_szReadBuf[iBufAvail] = '\0'; } int len = 0; char* p = (char*)memchr(szBufPtr, '\n', iBufAvail); if (p) { len = (int)(p - szBufPtr + 1); } else { len = iBufAvail; } if (len > iSize) { len = iSize; } memcpy(pBufPtr, szBufPtr, len); pBufPtr += len; szBufPtr += len; iBufAvail -= len; iBytesRead += len; iSize -= len; if (p) { break; } } *pBufPtr = '\0'; m_iBufAvail = iBufAvail > 0 ? iBufAvail : 0; // copy back to member m_szBufPtr = szBufPtr; // copy back to member if (pBytesRead) { *pBytesRead = iBytesRead; } if (pBufPtr == pBuffer) { return NULL; } return pBuffer; } Connection* Connection::Accept() { debug("Accepting connection"); if (m_eStatus != csListening) { return NULL; } SOCKET iSocket = accept(m_iSocket, NULL, NULL); if (iSocket == INVALID_SOCKET && m_eStatus != csCancelled) { ReportError("Could not accept connection", NULL, true, 0); } if (iSocket == INVALID_SOCKET) { return NULL; } Connection* pCon = new Connection(iSocket, m_bTLS); return pCon; } int Connection::TryRecv(char* pBuffer, int iSize) { debug("Receiving data"); memset(pBuffer, 0, iSize); int iReceived = recv(m_iSocket, pBuffer, iSize, 0); if (iReceived < 0) { ReportError("Could not receive data on socket", NULL, true, 0); } return iReceived; } bool Connection::Recv(char * pBuffer, int iSize) { debug("Receiving data (full buffer)"); memset(pBuffer, 0, iSize); char* pBufPtr = (char*)pBuffer; int NeedBytes = iSize; if (m_iBufAvail > 0) { int len = iSize > m_iBufAvail ? m_iBufAvail : iSize; memcpy(pBufPtr, m_szBufPtr, len); pBufPtr += len; m_szBufPtr += len; m_iBufAvail -= len; NeedBytes -= len; } // Read from the socket until nothing remains while (NeedBytes > 0) { int iReceived = recv(m_iSocket, pBufPtr, NeedBytes, 0); // Did the recv succeed? if (iReceived <= 0) { ReportError("Could not receive data on socket", NULL, true, 0); return false; } pBufPtr += iReceived; NeedBytes -= iReceived; } return true; } bool Connection::DoConnect() { debug("Do connecting"); m_iSocket = INVALID_SOCKET; #ifdef HAVE_GETADDRINFO struct addrinfo addr_hints, *addr_list, *addr; char iPortStr[sizeof(int) * 4 + 1]; //is enough to hold any converted int memset(&addr_hints, 0, sizeof(addr_hints)); addr_hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ addr_hints.ai_socktype = SOCK_STREAM, sprintf(iPortStr, "%d", m_iPort); int res = getaddrinfo(m_szHost, iPortStr, &addr_hints, &addr_list); if (res != 0) { ReportError("Could not resolve hostname %s", m_szHost, true, 0); return false; } for (addr = addr_list; addr != NULL; addr = addr->ai_next) { bool bLastAddr = !addr->ai_next; m_iSocket = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); if (m_iSocket != INVALID_SOCKET) { res = connect(m_iSocket , addr->ai_addr, addr->ai_addrlen); if (res != -1) { // Connection established break; } // Connection failed if (bLastAddr) { ReportError("Connection to %s failed", m_szHost, true, 0); } closesocket(m_iSocket); m_iSocket = INVALID_SOCKET; } else if (bLastAddr) { ReportError("Socket creation failed for %s", m_szHost, true, 0); } } freeaddrinfo(addr_list); if (m_iSocket == INVALID_SOCKET) { return false; } #else struct sockaddr_in sSocketAddress; memset(&sSocketAddress, 0, sizeof(sSocketAddress)); sSocketAddress.sin_family = AF_INET; sSocketAddress.sin_port = htons(m_iPort); sSocketAddress.sin_addr.s_addr = ResolveHostAddr(m_szHost); if (sSocketAddress.sin_addr.s_addr == (unsigned int)-1) { return false; } m_iSocket = socket(PF_INET, SOCK_STREAM, 0); if (m_iSocket == INVALID_SOCKET) { ReportError("Socket creation failed for %s", m_szHost, true, 0); return false; } int res = connect(m_iSocket , (struct sockaddr *) & sSocketAddress, sizeof(sSocketAddress)); if (res == -1) { ReportError("Connection to %s failed", m_szHost, true, 0); closesocket(m_iSocket); m_iSocket = INVALID_SOCKET; return false; } #endif #ifdef WIN32 int MSecVal = m_iTimeout * 1000; int err = setsockopt(m_iSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&MSecVal, sizeof(MSecVal)); #else struct timeval TimeVal; TimeVal.tv_sec = m_iTimeout; TimeVal.tv_usec = 0; int err = setsockopt(m_iSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&TimeVal, sizeof(TimeVal)); #endif if (err != 0) { ReportError("Socket initialization failed for %s", m_szHost, true, 0); } #ifndef DISABLE_TLS if (m_bTLS && !StartTLS(true, NULL, NULL)) { return false; } #endif return true; } bool Connection::DoDisconnect() { debug("Do disconnecting"); if (m_iSocket != INVALID_SOCKET) { #ifndef DISABLE_TLS CloseTLS(); #endif closesocket(m_iSocket); m_iSocket = INVALID_SOCKET; } m_eStatus = csDisconnected; return true; } void Connection::ReadBuffer(char** pBuffer, int *iBufLen) { *iBufLen = m_iBufAvail; *pBuffer = m_szBufPtr; m_iBufAvail = 0; }; void Connection::Cancel() { debug("Cancelling connection"); if (m_iSocket != INVALID_SOCKET) { m_eStatus = csCancelled; int r = shutdown(m_iSocket, SHUT_RDWR); if (r == -1) { ReportError("Could not shutdown connection", NULL, true, 0); } } } void Connection::ReportError(const char* szMsgPrefix, const char* szMsgArg, bool PrintErrCode, int herrno) { #ifndef DISABLE_TLS if (m_bTLSError) { // TLS-Error was already reported m_bTLSError = false; return; } #endif char szErrPrefix[1024]; snprintf(szErrPrefix, 1024, szMsgPrefix, szMsgArg); szErrPrefix[1024-1] = '\0'; if (PrintErrCode) { #ifdef WIN32 int ErrCode = WSAGetLastError(); char szErrMsg[1024]; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, ErrCode, 0, szErrMsg, 1024, NULL); szErrMsg[1024-1] = '\0'; #else const char *szErrMsg = NULL; int ErrCode = herrno; if (herrno == 0) { ErrCode = errno; szErrMsg = strerror(ErrCode); } else { szErrMsg = hstrerror(ErrCode); } #endif if (m_bSuppressErrors) { debug("%s: ErrNo %i, %s", szErrPrefix, ErrCode, szErrMsg); } else { error("%s: ErrNo %i, %s", szErrPrefix, ErrCode, szErrMsg); } } else { if (m_bSuppressErrors) { debug(szErrPrefix); } else { error(szErrPrefix); } } } #ifndef DISABLE_TLS bool Connection::StartTLS(bool bIsClient, const char* szCertFile, const char* szKeyFile) { debug("Starting TLS"); delete m_pTLSSocket; m_pTLSSocket = new TLSSocket(m_iSocket, bIsClient, szCertFile, szKeyFile, m_szCipher); m_pTLSSocket->SetSuppressErrors(m_bSuppressErrors); return m_pTLSSocket->Start(); } void Connection::CloseTLS() { if (m_pTLSSocket) { m_pTLSSocket->Close(); delete m_pTLSSocket; m_pTLSSocket = NULL; } } int Connection::recv(SOCKET s, char* buf, int len, int flags) { int iReceived = 0; if (m_pTLSSocket) { m_bTLSError = false; iReceived = m_pTLSSocket->Recv(buf, len); if (iReceived < 0) { m_bTLSError = true; return -1; } } else { iReceived = ::recv(s, buf, len, flags); } return iReceived; } int Connection::send(SOCKET s, const char* buf, int len, int flags) { int iSent = 0; if (m_pTLSSocket) { m_bTLSError = false; iSent = m_pTLSSocket->Send(buf, len); if (iSent < 0) { m_bTLSError = true; return -1; } return iSent; } else { iSent = ::send(s, buf, len, flags); return iSent; } } #endif #ifndef HAVE_GETADDRINFO unsigned int Connection::ResolveHostAddr(const char* szHost) { unsigned int uaddr = inet_addr(szHost); if (uaddr == (unsigned int)-1) { struct hostent* hinfo; bool err = false; int h_errnop = 0; #ifdef HAVE_GETHOSTBYNAME_R struct hostent hinfobuf; char strbuf[1024]; #ifdef HAVE_GETHOSTBYNAME_R_6 err = gethostbyname_r(szHost, &hinfobuf, strbuf, sizeof(strbuf), &hinfo, &h_errnop); err = err || (hinfo == NULL); // error on null hinfo (means 'no entry') #endif #ifdef HAVE_GETHOSTBYNAME_R_5 hinfo = gethostbyname_r(szHost, &hinfobuf, strbuf, sizeof(strbuf), &h_errnop); err = hinfo == NULL; #endif #ifdef HAVE_GETHOSTBYNAME_R_3 //NOTE: gethostbyname_r with three parameters were not tested struct hostent_data hinfo_data; hinfo = gethostbyname_r((char*)szHost, (struct hostent*)hinfobuf, &hinfo_data); err = hinfo == NULL; #endif #else m_pMutexGetHostByName->Lock(); hinfo = gethostbyname(szHost); err = hinfo == NULL; #endif if (err) { #ifndef HAVE_GETHOSTBYNAME_R m_pMutexGetHostByName->Unlock(); #endif ReportError("Could not resolve hostname %s", szHost, true, h_errnop); return (unsigned int)-1; } memcpy(&uaddr, hinfo->h_addr_list[0], sizeof(uaddr)); #ifndef HAVE_GETHOSTBYNAME_R m_pMutexGetHostByName->Unlock(); #endif } return uaddr; } #endif const char* Connection::GetRemoteAddr() { struct sockaddr_in PeerName; int iPeerNameLength = sizeof(PeerName); if (getpeername(m_iSocket, (struct sockaddr*)&PeerName, (SOCKLEN_T*) &iPeerNameLength) >= 0) { #ifdef WIN32 strncpy(m_szRemoteAddr, inet_ntoa(PeerName.sin_addr), sizeof(m_szRemoteAddr)); #else inet_ntop(AF_INET, &PeerName.sin_addr, m_szRemoteAddr, sizeof(m_szRemoteAddr)); #endif } m_szRemoteAddr[sizeof(m_szRemoteAddr)-1] = '\0'; return m_szRemoteAddr; } nzbget-12.0+dfsg/Connection.h000066400000000000000000000063731226450633000161260ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 633 $ * $Date: 2013-04-17 22:21:46 +0200 (Wed, 17 Apr 2013) $ * */ #ifndef CONNECTION_H #define CONNECTION_H #ifndef HAVE_GETADDRINFO #ifndef HAVE_GETHOSTBYNAME_R #include "Thread.h" #endif #endif #ifndef DISABLE_TLS #include "TLS.h" #endif class Connection { public: enum EStatus { csConnected, csDisconnected, csListening, csCancelled }; protected: char* m_szHost; int m_iPort; SOCKET m_iSocket; bool m_bTLS; char* m_szCipher; char* m_szReadBuf; int m_iBufAvail; char* m_szBufPtr; EStatus m_eStatus; int m_iTimeout; bool m_bSuppressErrors; char m_szRemoteAddr[20]; #ifndef DISABLE_TLS TLSSocket* m_pTLSSocket; bool m_bTLSError; #endif #ifndef HAVE_GETADDRINFO #ifndef HAVE_GETHOSTBYNAME_R static Mutex* m_pMutexGetHostByName; #endif #endif Connection(SOCKET iSocket, bool bTLS); void ReportError(const char* szMsgPrefix, const char* szMsgArg, bool PrintErrCode, int herrno); bool DoConnect(); bool DoDisconnect(); #ifndef HAVE_GETADDRINFO unsigned int ResolveHostAddr(const char* szHost); #endif #ifndef DISABLE_TLS int recv(SOCKET s, char* buf, int len, int flags); int send(SOCKET s, const char* buf, int len, int flags); void CloseTLS(); #endif public: Connection(const char* szHost, int iPort, bool bTLS); virtual ~Connection(); static void Init(); static void Final(); virtual bool Connect(); virtual bool Disconnect(); bool Bind(); bool Send(const char* pBuffer, int iSize); bool Recv(char* pBuffer, int iSize); int TryRecv(char* pBuffer, int iSize); char* ReadLine(char* pBuffer, int iSize, int* pBytesRead); void ReadBuffer(char** pBuffer, int *iBufLen); int WriteLine(const char* pBuffer); Connection* Accept(); void Cancel(); const char* GetHost() { return m_szHost; } int GetPort() { return m_iPort; } bool GetTLS() { return m_bTLS; } const char* GetCipher() { return m_szCipher; } void SetCipher(const char* szCipher); void SetTimeout(int iTimeout) { m_iTimeout = iTimeout; } EStatus GetStatus() { return m_eStatus; } void SetSuppressErrors(bool bSuppressErrors); bool GetSuppressErrors() { return m_bSuppressErrors; } const char* GetRemoteAddr(); #ifndef DISABLE_TLS bool StartTLS(bool bIsClient, const char* szCertFile, const char* szKeyFile); #endif }; #endif nzbget-12.0+dfsg/Decoder.cpp000066400000000000000000000222441226450633000157220ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 911 $ * $Date: 2013-11-24 20:29:52 +0100 (Sun, 24 Nov 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #ifndef WIN32 #include #endif #include "nzbget.h" #include "Decoder.h" #include "Log.h" #include "Util.h" const char* Decoder::FormatNames[] = { "Unknown", "yEnc", "UU" }; unsigned int YDecoder::crc_tab[256]; Decoder::Decoder() { debug("Creating Decoder"); m_szSrcFilename = NULL; m_szDestFilename = NULL; m_szArticleFilename = NULL; } Decoder::~ Decoder() { debug("Destroying Decoder"); free(m_szArticleFilename); } void Decoder::Clear() { free(m_szArticleFilename); m_szArticleFilename = NULL; } Decoder::EFormat Decoder::DetectFormat(const char* buffer, int len) { if (!strncmp(buffer, "=ybegin ", 8)) { return efYenc; } if ((len == 62 || len == 63) && (buffer[62] == '\n' || buffer[62] == '\r') && *buffer == 'M') { return efUx; } if (!strncmp(buffer, "begin ", 6)) { bool bOK = true; buffer += 6; //strlen("begin ") while (*buffer && *buffer != ' ') { char ch = *buffer++; if (ch < '0' || ch > '7') { bOK = false; break; } } if (bOK) { return efUx; } } return efUnknown; } /** * YDecoder: fast implementation of yEnc-Decoder */ void YDecoder::Init() { debug("Initializing global decoder"); crc32gentab(); } void YDecoder::Final() { debug("Finalizing global Decoder"); } YDecoder::YDecoder() { Clear(); } void YDecoder::Clear() { Decoder::Clear(); m_bBody = false; m_bBegin = false; m_bPart = false; m_bEnd = false; m_bCrc = false; m_lExpectedCRC = 0; m_lCalculatedCRC = 0xFFFFFFFF; m_iBegin = 0; m_iEnd = 0xFFFFFFFF; m_iSize = 0; m_iEndSize = 0; m_bAutoSeek = false; m_bNeedSetPos = false; m_bCrcCheck = false; } /* from crc32.c (http://www.koders.com/c/fid699AFE0A656F0022C9D6B9D1743E697B69CE5815.aspx) * * (c) 1999,2000 Krzysztof Dabrowski * (c) 1999,2000 ElysiuM deeZine * Released under GPL (thanks) * * chksum_crc32gentab() -- to a global crc_tab[256], this one will * calculate the crcTable for crc32-checksums. * it is generated to the polynom [..] */ void YDecoder::crc32gentab() { unsigned long crc, poly; int i, j; poly = 0xEDB88320L; for (i = 0; i < 256; i++) { crc = i; for (j = 8; j > 0; j--) { if (crc & 1) { crc = (crc >> 1) ^ poly; } else { crc >>= 1; } } crc_tab[i] = crc; } } /* This is modified version of chksum_crc() from * crc32.c (http://www.koders.com/c/fid699AFE0A656F0022C9D6B9D1743E697B69CE5815.aspx) * (c) 1999,2000 Krzysztof Dabrowski * (c) 1999,2000 ElysiuM deeZine * * chksum_crc() -- to a given block, this one calculates the * crc32-checksum until the length is * reached. the crc32-checksum will be * the result. */ unsigned long YDecoder::crc32m(unsigned long startCrc, unsigned char *block, unsigned int length) { register unsigned long crc = startCrc; for (unsigned long i = 0; i < length; i++) { crc = ((crc >> 8) & 0x00FFFFFF) ^ crc_tab[(crc ^ *block++) & 0xFF]; } return crc; } unsigned int YDecoder::DecodeBuffer(char* buffer) { if (m_bBody && !m_bEnd) { if (!strncmp(buffer, "=yend ", 6)) { m_bEnd = true; char* pb = strstr(buffer, m_bPart ? " pcrc32=" : " crc32="); if (pb) { m_bCrc = true; pb += 7 + (int)m_bPart; //=strlen(" crc32=") or strlen(" pcrc32=") m_lExpectedCRC = strtoul(pb, NULL, 16); } pb = strstr(buffer, " size="); if (pb) { pb += 6; //=strlen(" size=") m_iEndSize = (int)atoi(pb); } 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, (unsigned int)(optr - buffer)); } return (unsigned int)(optr - buffer); } else { if (!m_bPart && !strncmp(buffer, "=ybegin ", 8)) { m_bBegin = true; char* pb = strstr(buffer, " name="); if (pb) { pb += 6; //=strlen(" name=") char* pe; for (pe = pb; *pe != '\0' && *pe != '\n' && *pe != '\r'; pe++) ; free(m_szArticleFilename); m_szArticleFilename = (char*)malloc(pe - pb + 1); strncpy(m_szArticleFilename, pb, pe - pb); m_szArticleFilename[pe - pb] = '\0'; } pb = strstr(buffer, " size="); if (pb) { pb += 6; //=strlen(" size=") m_iSize = (int)atoi(pb); } m_bPart = strstr(buffer, " part="); if (!m_bPart) { m_bBody = true; m_iBegin = 1; m_iEnd = m_iSize; } } else if (m_bPart && !strncmp(buffer, "=ypart ", 7)) { m_bPart = true; m_bBody = true; char* pb = strstr(buffer, " begin="); if (pb) { pb += 7; //=strlen(" begin=") m_iBegin = (int)atoi(pb); } pb = strstr(buffer, " end="); if (pb) { pb += 5; //=strlen(" end=") m_iEnd = (int)atoi(pb); } } } return 0; } bool YDecoder::Write(char* buffer, int len, FILE* outfile) { unsigned int wcnt = DecodeBuffer(buffer); if (wcnt > 0) { if (m_bNeedSetPos) { if (m_iBegin == 0 || m_iEnd == 0xFFFFFFFF || !outfile) { return false; } if (fseek(outfile, m_iBegin - 1, SEEK_SET)) { return false; } m_bNeedSetPos = false; } fwrite(buffer, 1, wcnt, outfile); } return true; } Decoder::EStatus YDecoder::Check() { m_lCalculatedCRC ^= 0xFFFFFFFF; debug("Expected crc32=%x", m_lExpectedCRC); debug("Calculated crc32=%x", m_lCalculatedCRC); if (!m_bBegin) { return eNoBinaryData; } else if (!m_bEnd) { return eArticleIncomplete; } else if (!m_bPart && m_iSize != m_iEndSize) { return eInvalidSize; } else if (m_bCrcCheck && m_bCrc && (m_lExpectedCRC != m_lCalculatedCRC)) { return eCrcError; } return eFinished; } /** * UDecoder: supports UU encoding formats */ UDecoder::UDecoder() { } void UDecoder::Clear() { Decoder::Clear(); m_bBody = false; m_bEnd = false; } /* DecodeBuffer-function uses portions of code from tool UUDECODE by Clem Dye * UUDECODE.c (http://www.bastet.com/uue.zip) * Copyright (C) 1998 Clem Dye * * Released under GPL (thanks) */ #define UU_DECODE_CHAR(c) (c == '`' ? 0 : (((c) - ' ') & 077)) unsigned int UDecoder::DecodeBuffer(char* buffer, int len) { if (!m_bBody) { if (!strncmp(buffer, "begin ", 6)) { char* pb = buffer; pb += 6; //strlen("begin ") // skip file-permissions for (; *pb != ' ' && *pb != '\0' && *pb != '\n' && *pb != '\r'; pb++) ; pb++; // extracting filename char* pe; for (pe = pb; *pe != '\0' && *pe != '\n' && *pe != '\r'; pe++) ; free(m_szArticleFilename); m_szArticleFilename = (char*)malloc(pe - pb + 1); strncpy(m_szArticleFilename, pb, pe - pb); m_szArticleFilename[pe - pb] = '\0'; m_bBody = true; return 0; } else if ((len == 62 || len == 63) && (buffer[62] == '\n' || buffer[62] == '\r') && *buffer == 'M') { m_bBody = true; } } if (m_bBody && (!strncmp(buffer, "end ", 4) || *buffer == '`')) { m_bEnd = true; } if (m_bBody && !m_bEnd) { int iEffLen = UU_DECODE_CHAR(buffer[0]); if (iEffLen > len) { // error; return 0; } char* iptr = buffer; char* optr = buffer; for (++iptr; iEffLen > 0; iptr += 4, iEffLen -= 3) { if (iEffLen >= 3) { *optr++ = UU_DECODE_CHAR (iptr[0]) << 2 | UU_DECODE_CHAR (iptr[1]) >> 4; *optr++ = UU_DECODE_CHAR (iptr[1]) << 4 | UU_DECODE_CHAR (iptr[2]) >> 2; *optr++ = UU_DECODE_CHAR (iptr[2]) << 6 | UU_DECODE_CHAR (iptr[3]); } else { if (iEffLen >= 1) { *optr++ = UU_DECODE_CHAR (iptr[0]) << 2 | UU_DECODE_CHAR (iptr[1]) >> 4; } if (iEffLen >= 2) { *optr++ = UU_DECODE_CHAR (iptr[1]) << 4 | UU_DECODE_CHAR (iptr[2]) >> 2; } } } return (unsigned int)(optr - buffer); } return 0; } bool UDecoder::Write(char* buffer, int len, FILE* outfile) { unsigned int wcnt = DecodeBuffer(buffer, len); if (wcnt > 0) { fwrite(buffer, 1, wcnt, outfile); } return true; } Decoder::EStatus UDecoder::Check() { if (!m_bBody) { return eNoBinaryData; } return eFinished; } nzbget-12.0+dfsg/Decoder.h000066400000000000000000000060231226450633000153640ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2008 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 397 $ * $Date: 2011-05-24 14:52:41 +0200 (Tue, 24 May 2011) $ * */ #ifndef DECODER_H #define DECODER_H class Decoder { public: enum EStatus { eUnknownError, eFinished, eArticleIncomplete, eCrcError, eInvalidSize, eNoBinaryData }; enum EFormat { efUnknown, efYenc, efUx, }; static const char* FormatNames[]; protected: const char* m_szSrcFilename; const char* m_szDestFilename; char* m_szArticleFilename; public: Decoder(); virtual ~Decoder(); virtual EStatus Check() = 0; virtual void Clear(); virtual bool Write(char* buffer, int len, FILE* outfile) = 0; void SetSrcFilename(const char* szSrcFilename) { m_szSrcFilename = szSrcFilename; } void SetDestFilename(const char* szDestFilename) { m_szDestFilename = szDestFilename; } const char* GetArticleFilename() { return m_szArticleFilename; } static EFormat DetectFormat(const char* buffer, int len); }; class YDecoder: public Decoder { protected: static unsigned int crc_tab[256]; bool m_bBegin; bool m_bPart; bool m_bBody; bool m_bEnd; bool m_bCrc; unsigned long m_lExpectedCRC; unsigned long m_lCalculatedCRC; unsigned long m_iBegin; unsigned long m_iEnd; unsigned long m_iSize; unsigned long m_iEndSize; 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 EStatus Check(); virtual void Clear(); virtual bool Write(char* buffer, int len, FILE* outfile); void SetAutoSeek(bool bAutoSeek) { m_bAutoSeek = m_bNeedSetPos = bAutoSeek; } void SetCrcCheck(bool bCrcCheck) { m_bCrcCheck = bCrcCheck; } static void Init(); static void Final(); }; class UDecoder: public Decoder { private: bool m_bBody; bool m_bEnd; unsigned int DecodeBuffer(char* buffer, int len); public: UDecoder(); virtual EStatus Check(); virtual void Clear(); virtual bool Write(char* buffer, int len, FILE* outfile); }; #endif nzbget-12.0+dfsg/DiskState.cpp000066400000000000000000001703521226450633000162540ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 921 $ * $Date: 2013-12-18 21:19:42 +0100 (Wed, 18 Dec 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #include #include #include #include #include #include #include "nzbget.h" #include "DiskState.h" #include "Options.h" #include "Log.h" #include "Util.h" extern Options* g_pOptions; static const char* FORMATVERSION_SIGNATURE = "nzbget diskstate file version "; #ifdef WIN32 // Windows doesn't have standard "vsscanf" // Hack from http://stackoverflow.com/questions/2457331/replacement-for-vsscanf-on-msvc int vsscanf(const char *s, const char *fmt, va_list ap) { // up to max 10 arguments void *a[10]; for (int i = 0; i < sizeof(a)/sizeof(a[0]); i++) { a[i] = va_arg(ap, void*); } return sscanf(s, fmt, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9]); } #endif /* * Standard fscanf scans beoynd current line if the next line is empty. * This wrapper fixes that. */ int DiskState::fscanf(FILE* infile, const char* Format, ...) { char szLine[1024]; if (!fgets(szLine, sizeof(szLine), infile)) { return 0; } va_list ap; va_start(ap, Format); int res = vsscanf(szLine, Format, ap); va_end(ap); return res; } /* Parse signature and return format version number */ int DiskState::ParseFormatVersion(const char* szFormatSignature) { if (strncmp(szFormatSignature, FORMATVERSION_SIGNATURE, strlen(FORMATVERSION_SIGNATURE))) { return 0; } return atoi(szFormatSignature + strlen(FORMATVERSION_SIGNATURE)); } /* Save Download Queue to Disk. * The Disk State consists of file "queue", which contains the order of files, * and of one diskstate-file for each file in download queue. * This function saves file "queue" and files with NZB-info. It does not * save file-infos. * * For safety: * - first save to temp-file (queue.new) * - then delete queue * - then rename queue.new to queue */ bool DiskState::SaveDownloadQueue(DownloadQueue* pDownloadQueue) { debug("Saving queue to disk"); char destFilename[1024]; snprintf(destFilename, 1024, "%s%s", g_pOptions->GetQueueDir(), "queue"); destFilename[1024-1] = '\0'; char tempFilename[1024]; snprintf(tempFilename, 1024, "%s%s", g_pOptions->GetQueueDir(), "queue.new"); tempFilename[1024-1] = '\0'; if (pDownloadQueue->GetFileQueue()->empty() && pDownloadQueue->GetUrlQueue()->empty() && pDownloadQueue->GetPostQueue()->empty() && pDownloadQueue->GetHistoryList()->empty()) { remove(destFilename); return true; } FILE* outfile = fopen(tempFilename, "wb"); if (!outfile) { error("Error saving diskstate: Could not create file %s", tempFilename); return false; } fprintf(outfile, "%s%i\n", FORMATVERSION_SIGNATURE, 42); // save nzb-infos SaveNZBList(pDownloadQueue, outfile); // save file-infos SaveFileQueue(pDownloadQueue, pDownloadQueue->GetFileQueue(), outfile); // save post-queue SavePostQueue(pDownloadQueue, outfile); // save url-queue SaveUrlQueue(pDownloadQueue, outfile); // save history SaveHistory(pDownloadQueue, outfile); // save parked file-infos SaveFileQueue(pDownloadQueue, pDownloadQueue->GetParkedFiles(), outfile); fclose(outfile); // now rename to dest file name remove(destFilename); if (rename(tempFilename, destFilename)) { error("Error saving diskstate: Could not rename file %s to %s", tempFilename, destFilename); return false; } return true; } bool DiskState::LoadDownloadQueue(DownloadQueue* pDownloadQueue) { debug("Loading queue from disk"); bool bOK = false; char fileName[1024]; snprintf(fileName, 1024, "%s%s", g_pOptions->GetQueueDir(), "queue"); fileName[1024-1] = '\0'; FILE* infile = fopen(fileName, "rb"); if (!infile) { error("Error reading diskstate: could not open file %s", fileName); return false; } char FileSignatur[128]; fgets(FileSignatur, sizeof(FileSignatur), infile); int iFormatVersion = ParseFormatVersion(FileSignatur); if (iFormatVersion < 3 || iFormatVersion > 42) { error("Could not load diskstate due to file version mismatch"); fclose(infile); return false; } // load nzb-infos if (!LoadNZBList(pDownloadQueue, infile, iFormatVersion)) goto error; // load file-infos if (!LoadFileQueue(pDownloadQueue, pDownloadQueue->GetFileQueue(), infile, iFormatVersion)) goto error; if (iFormatVersion >= 7) { // load post-queue if (!LoadPostQueue(pDownloadQueue, infile, iFormatVersion)) goto error; } else if (iFormatVersion < 7 && g_pOptions->GetReloadPostQueue()) { // load post-queue created with older version of program LoadOldPostQueue(pDownloadQueue); } if (iFormatVersion >= 15) { // load url-queue if (!LoadUrlQueue(pDownloadQueue, infile, iFormatVersion)) goto error; } if (iFormatVersion >= 9) { // load history if (!LoadHistory(pDownloadQueue, infile, iFormatVersion)) goto error; // load parked file-infos if (!LoadFileQueue(pDownloadQueue, pDownloadQueue->GetParkedFiles(), infile, iFormatVersion)) goto error; } if (iFormatVersion < 29) { CalcCriticalHealth(pDownloadQueue); } bOK = true; error: fclose(infile); if (!bOK) { error("Error reading diskstate for file %s", fileName); } pDownloadQueue->GetNZBInfoList()->ReleaseAll(); NZBInfo::ResetGenID(true); FileInfo::ResetGenID(true); UrlInfo::ResetGenID(true); HistoryInfo::ResetGenID(true); return bOK; } void DiskState::SaveNZBList(DownloadQueue* pDownloadQueue, FILE* outfile) { debug("Saving nzb list to disk"); fprintf(outfile, "%i\n", (int)pDownloadQueue->GetNZBInfoList()->size()); for (NZBInfoList::iterator it = pDownloadQueue->GetNZBInfoList()->begin(); it != pDownloadQueue->GetNZBInfoList()->end(); it++) { NZBInfo* pNZBInfo = *it; fprintf(outfile, "%i\n", pNZBInfo->GetID()); fprintf(outfile, "%s\n", pNZBInfo->GetFilename()); fprintf(outfile, "%s\n", pNZBInfo->GetDestDir()); fprintf(outfile, "%s\n", pNZBInfo->GetFinalDir()); fprintf(outfile, "%s\n", pNZBInfo->GetQueuedFilename()); fprintf(outfile, "%s\n", pNZBInfo->GetName()); fprintf(outfile, "%s\n", pNZBInfo->GetCategory()); fprintf(outfile, "%i,%i,%i\n", (int)pNZBInfo->GetPostProcess(), (int)pNZBInfo->GetDeletePaused(), (int)pNZBInfo->GetManyDupeFiles()); fprintf(outfile, "%i,%i,%i,%i,%i,%i\n", (int)pNZBInfo->GetParStatus(), (int)pNZBInfo->GetUnpackStatus(), (int)pNZBInfo->GetMoveStatus(), (int)pNZBInfo->GetRenameStatus(), (int)pNZBInfo->GetDeleteStatus(), (int)pNZBInfo->GetMarkStatus()); fprintf(outfile, "%i,%i\n", (int)pNZBInfo->GetUnpackCleanedUpDisk(), (int)pNZBInfo->GetHealthPaused()); fprintf(outfile, "%i,%i\n", pNZBInfo->GetFileCount(), pNZBInfo->GetParkedFileCount()); fprintf(outfile, "%u,%u\n", pNZBInfo->GetFullContentHash(), pNZBInfo->GetFilteredContentHash()); unsigned long High1, Low1, High2, Low2, High3, Low3; Util::SplitInt64(pNZBInfo->GetSize(), &High1, &Low1); Util::SplitInt64(pNZBInfo->GetSuccessSize(), &High2, &Low2); Util::SplitInt64(pNZBInfo->GetFailedSize(), &High3, &Low3); fprintf(outfile, "%lu,%lu,%lu,%lu,%lu,%lu\n", High1, Low1, High2, Low2, High3, Low3); Util::SplitInt64(pNZBInfo->GetParSize(), &High1, &Low1); Util::SplitInt64(pNZBInfo->GetParSuccessSize(), &High2, &Low2); Util::SplitInt64(pNZBInfo->GetParFailedSize(), &High3, &Low3); fprintf(outfile, "%lu,%lu,%lu,%lu,%lu,%lu\n", High1, Low1, High2, Low2, High3, Low3); fprintf(outfile, "%i,%i,%i\n", pNZBInfo->GetTotalArticles(), pNZBInfo->GetSuccessArticles(), pNZBInfo->GetFailedArticles()); fprintf(outfile, "%s\n", pNZBInfo->GetDupeKey()); fprintf(outfile, "%i,%i\n", (int)pNZBInfo->GetDupeMode(), pNZBInfo->GetDupeScore()); char DestDirSlash[1024]; snprintf(DestDirSlash, 1023, "%s%c", pNZBInfo->GetDestDir(), PATH_SEPARATOR); int iDestDirLen = strlen(DestDirSlash); fprintf(outfile, "%i\n", (int)pNZBInfo->GetCompletedFiles()->size()); for (NZBInfo::Files::iterator it = pNZBInfo->GetCompletedFiles()->begin(); it != pNZBInfo->GetCompletedFiles()->end(); it++) { char* szFilename = *it; // do not save full path to reduce the size of queue-file if (!strncmp(DestDirSlash, szFilename, iDestDirLen)) { fprintf(outfile, "%s\n", szFilename + iDestDirLen); } else { fprintf(outfile, "%s\n", szFilename); } } fprintf(outfile, "%i\n", (int)pNZBInfo->GetParameters()->size()); for (NZBParameterList::iterator it = pNZBInfo->GetParameters()->begin(); it != pNZBInfo->GetParameters()->end(); it++) { NZBParameter* pParameter = *it; fprintf(outfile, "%s=%s\n", pParameter->GetName(), pParameter->GetValue()); } fprintf(outfile, "%i\n", (int)pNZBInfo->GetScriptStatuses()->size()); for (ScriptStatusList::iterator it = pNZBInfo->GetScriptStatuses()->begin(); it != pNZBInfo->GetScriptStatuses()->end(); it++) { ScriptStatus* pScriptStatus = *it; fprintf(outfile, "%i,%s\n", pScriptStatus->GetStatus(), pScriptStatus->GetName()); } fprintf(outfile, "%i\n", (int)pNZBInfo->GetServerStats()->size()); for (ServerStatList::iterator it = pNZBInfo->GetServerStats()->begin(); it != pNZBInfo->GetServerStats()->end(); it++) { ServerStat* pServerStat = *it; fprintf(outfile, "%i,%i,%i\n", pServerStat->GetServerID(), pServerStat->GetSuccessArticles(), pServerStat->GetFailedArticles()); } NZBInfo::Messages* pMessages = pNZBInfo->LockMessages(); fprintf(outfile, "%i\n", (int)pMessages->size()); for (NZBInfo::Messages::iterator it = pMessages->begin(); it != pMessages->end(); it++) { Message* pMessage = *it; fprintf(outfile, "%i,%i,%s\n", pMessage->GetKind(), (int)pMessage->GetTime(), pMessage->GetText()); } pNZBInfo->UnlockMessages(); } } bool DiskState::LoadNZBList(DownloadQueue* pDownloadQueue, FILE* infile, int iFormatVersion) { debug("Loading nzb list from disk"); int size; char buf[10240]; // load nzb-infos if (fscanf(infile, "%i\n", &size) != 1) goto error; for (int i = 0; i < size; i++) { NZBInfo* pNZBInfo = new NZBInfo(); pNZBInfo->Retain(); pDownloadQueue->GetNZBInfoList()->Add(pNZBInfo); if (iFormatVersion >= 24) { int iID; if (fscanf(infile, "%i\n", &iID) != 1) goto error; pNZBInfo->SetID(iID); } if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' pNZBInfo->SetFilename(buf); if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' pNZBInfo->SetDestDir(buf); if (iFormatVersion >= 27) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' pNZBInfo->SetFinalDir(buf); } if (iFormatVersion >= 5) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' pNZBInfo->SetQueuedFilename(buf); } if (iFormatVersion >= 13) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' if (strlen(buf) > 0) { pNZBInfo->SetName(buf); } } if (iFormatVersion >= 4) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' pNZBInfo->SetCategory(buf); } int iPostProcess = 0, iDeletePaused = 0, iManyDupeFiles = 0; if (iFormatVersion >= 41) { if (fscanf(infile, "%i,%i,%i\n", &iPostProcess, &iDeletePaused, &iManyDupeFiles) != 3) goto error; } else if (iFormatVersion >= 40) { if (fscanf(infile, "%i,%i\n", &iPostProcess, &iDeletePaused) != 2) goto error; } else if (iFormatVersion >= 4) { if (fscanf(infile, "%i\n", &iPostProcess) != 1) goto error; } pNZBInfo->SetPostProcess((bool)iPostProcess); pNZBInfo->SetDeletePaused((bool)iDeletePaused); pNZBInfo->SetManyDupeFiles((bool)iManyDupeFiles); if (iFormatVersion >= 8 && iFormatVersion < 18) { int iParStatus; if (fscanf(infile, "%i\n", &iParStatus) != 1) goto error; pNZBInfo->SetParStatus((NZBInfo::EParStatus)iParStatus); } if (iFormatVersion >= 9 && iFormatVersion < 18) { int iScriptStatus; if (fscanf(infile, "%i\n", &iScriptStatus) != 1) goto error; if (iScriptStatus > 1) iScriptStatus--; pNZBInfo->GetScriptStatuses()->Add("SCRIPT", (ScriptStatus::EStatus)iScriptStatus); } if (iFormatVersion >= 18) { int iParStatus, iUnpackStatus, iScriptStatus, iMoveStatus = 0, iRenameStatus = 0, iDeleteStatus = 0, iMarkStatus = 0; if (iFormatVersion >= 37) { if (fscanf(infile, "%i,%i,%i,%i,%i,%i\n", &iParStatus, &iUnpackStatus, &iMoveStatus, &iRenameStatus, &iDeleteStatus, &iMarkStatus) != 6) goto error; } else if (iFormatVersion >= 35) { if (fscanf(infile, "%i,%i,%i,%i,%i\n", &iParStatus, &iUnpackStatus, &iMoveStatus, &iRenameStatus, &iDeleteStatus) != 5) goto error; } else if (iFormatVersion >= 23) { if (fscanf(infile, "%i,%i,%i,%i\n", &iParStatus, &iUnpackStatus, &iMoveStatus, &iRenameStatus) != 4) goto error; } else if (iFormatVersion >= 21) { if (fscanf(infile, "%i,%i,%i,%i,%i\n", &iParStatus, &iUnpackStatus, &iScriptStatus, &iMoveStatus, &iRenameStatus) != 5) goto error; } else if (iFormatVersion >= 20) { if (fscanf(infile, "%i,%i,%i,%i\n", &iParStatus, &iUnpackStatus, &iScriptStatus, &iMoveStatus) != 4) goto error; } else { if (fscanf(infile, "%i,%i,%i\n", &iParStatus, &iUnpackStatus, &iScriptStatus) != 3) goto error; } pNZBInfo->SetParStatus((NZBInfo::EParStatus)iParStatus); pNZBInfo->SetUnpackStatus((NZBInfo::EUnpackStatus)iUnpackStatus); pNZBInfo->SetMoveStatus((NZBInfo::EMoveStatus)iMoveStatus); pNZBInfo->SetRenameStatus((NZBInfo::ERenameStatus)iRenameStatus); pNZBInfo->SetDeleteStatus((NZBInfo::EDeleteStatus)iDeleteStatus); pNZBInfo->SetMarkStatus((NZBInfo::EMarkStatus)iMarkStatus); if (iFormatVersion < 23) { if (iScriptStatus > 1) iScriptStatus--; pNZBInfo->GetScriptStatuses()->Add("SCRIPT", (ScriptStatus::EStatus)iScriptStatus); } } if (iFormatVersion >= 35) { int iUnpackCleanedUpDisk, bHealthPaused; if (fscanf(infile, "%i,%i\n", &iUnpackCleanedUpDisk, &bHealthPaused) != 2) goto error; pNZBInfo->SetUnpackCleanedUpDisk((bool)iUnpackCleanedUpDisk); pNZBInfo->SetHealthPaused((bool)bHealthPaused); } else if (iFormatVersion >= 28) { int iDeleted, iUnpackCleanedUpDisk, iHealthPaused, iHealthDeleted; if (fscanf(infile, "%i,%i,%i,%i\n", &iDeleted, &iUnpackCleanedUpDisk, &iHealthPaused, &iHealthDeleted) != 4) goto error; pNZBInfo->SetUnpackCleanedUpDisk((bool)iUnpackCleanedUpDisk); pNZBInfo->SetHealthPaused((bool)iHealthPaused); pNZBInfo->SetDeleteStatus(iHealthDeleted ? NZBInfo::dsHealth : iDeleted ? NZBInfo::dsManual : NZBInfo::dsNone); } if (iFormatVersion >= 28) { int iFileCount, iParkedFileCount; if (fscanf(infile, "%i,%i\n", &iFileCount, &iParkedFileCount) != 2) goto error; pNZBInfo->SetFileCount(iFileCount); pNZBInfo->SetParkedFileCount(iParkedFileCount); } else { if (iFormatVersion >= 19) { int iUnpackCleanedUpDisk; if (fscanf(infile, "%i\n", &iUnpackCleanedUpDisk) != 1) goto error; pNZBInfo->SetUnpackCleanedUpDisk((bool)iUnpackCleanedUpDisk); } int iFileCount; if (fscanf(infile, "%i\n", &iFileCount) != 1) goto error; pNZBInfo->SetFileCount(iFileCount); if (iFormatVersion >= 10) { if (fscanf(infile, "%i\n", &iFileCount) != 1) goto error; pNZBInfo->SetParkedFileCount(iFileCount); } } unsigned int iFullContentHash = 0, iFilteredContentHash = 0; if (iFormatVersion >= 34) { if (fscanf(infile, "%u,%u\n", &iFullContentHash, &iFilteredContentHash) != 2) goto error; } else if (iFormatVersion >= 32) { if (fscanf(infile, "%u\n", &iFullContentHash) != 1) goto error; } pNZBInfo->SetFullContentHash(iFullContentHash); pNZBInfo->SetFilteredContentHash(iFilteredContentHash); if (iFormatVersion >= 28) { unsigned long High1, Low1, High2, Low2, High3, Low3; if (fscanf(infile, "%lu,%lu,%lu,%lu,%lu,%lu\n", &High1, &Low1, &High2, &Low2, &High3, &Low3) != 6) goto error; pNZBInfo->SetSize(Util::JoinInt64(High1, Low1)); pNZBInfo->SetSuccessSize(Util::JoinInt64(High2, Low2)); pNZBInfo->SetCurrentSuccessSize(pNZBInfo->GetSuccessSize()); pNZBInfo->SetFailedSize(Util::JoinInt64(High3, Low3)); pNZBInfo->SetCurrentFailedSize(pNZBInfo->GetFailedSize()); if (fscanf(infile, "%lu,%lu,%lu,%lu,%lu,%lu\n", &High1, &Low1, &High2, &Low2, &High3, &Low3) != 6) goto error; pNZBInfo->SetParSize(Util::JoinInt64(High1, Low1)); pNZBInfo->SetParSuccessSize(Util::JoinInt64(High2, Low2)); pNZBInfo->SetParCurrentSuccessSize(pNZBInfo->GetParSuccessSize()); pNZBInfo->SetParFailedSize(Util::JoinInt64(High3, Low3)); pNZBInfo->SetParCurrentFailedSize(pNZBInfo->GetParFailedSize()); } else { unsigned long High, Low; if (fscanf(infile, "%lu,%lu\n", &High, &Low) != 2) goto error; pNZBInfo->SetSize(Util::JoinInt64(High, Low)); } if (iFormatVersion >= 30) { int iTotalArticles, iSuccessArticles, iFailedArticles; if (fscanf(infile, "%i,%i,%i\n", &iTotalArticles, &iSuccessArticles, &iFailedArticles) != 3) goto error; pNZBInfo->SetTotalArticles(iTotalArticles); pNZBInfo->SetSuccessArticles(iSuccessArticles); pNZBInfo->SetFailedArticles(iFailedArticles); } if (iFormatVersion >= 31) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' if (iFormatVersion < 36) ConvertDupeKey(buf, sizeof(buf)); pNZBInfo->SetDupeKey(buf); } int iDupeMode = 0, iDupeScore = 0; if (iFormatVersion >= 39) { if (fscanf(infile, "%i,%i\n", &iDupeMode, &iDupeScore) != 2) goto error; } else if (iFormatVersion >= 31) { int iDupe; if (fscanf(infile, "%i,%i,%i\n", &iDupe, &iDupeMode, &iDupeScore) != 3) goto error; } pNZBInfo->SetDupeMode((EDupeMode)iDupeMode); pNZBInfo->SetDupeScore(iDupeScore); if (iFormatVersion >= 4) { int iFileCount; if (fscanf(infile, "%i\n", &iFileCount) != 1) goto error; for (int i = 0; i < iFileCount; i++) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' // restore full file name. char* szFileName = buf; char FullFileName[1024]; if (!strchr(buf, PATH_SEPARATOR)) { snprintf(FullFileName, 1023, "%s%c%s", pNZBInfo->GetDestDir(), PATH_SEPARATOR, buf); szFileName = FullFileName; } pNZBInfo->GetCompletedFiles()->push_back(strdup(szFileName)); } } if (iFormatVersion >= 6) { int iParameterCount; if (fscanf(infile, "%i\n", &iParameterCount) != 1) goto error; for (int i = 0; i < iParameterCount; i++) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' char* szValue = strchr(buf, '='); if (szValue) { *szValue = '\0'; szValue++; pNZBInfo->GetParameters()->SetParameter(buf, szValue); } } } if (iFormatVersion >= 23) { int iScriptCount; if (fscanf(infile, "%i\n", &iScriptCount) != 1) goto error; for (int i = 0; i < iScriptCount; i++) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' char* szScriptName = strchr(buf, ','); if (szScriptName) { szScriptName++; int iStatus = atoi(buf); if (iStatus > 1 && iFormatVersion < 25) iStatus--; pNZBInfo->GetScriptStatuses()->Add(szScriptName, (ScriptStatus::EStatus)iStatus); } } } if (iFormatVersion >= 30) { int iStatCount; if (fscanf(infile, "%i\n", &iStatCount) != 1) goto error; for (int i = 0; i < iStatCount; i++) { int iServerID, iSuccessArticles, iFailedArticles; if (fscanf(infile, "%i,%i,%i\n", &iServerID, &iSuccessArticles, &iFailedArticles) != 3) goto error; pNZBInfo->GetServerStats()->SetStat(iServerID, iSuccessArticles, iFailedArticles, false); } } if (iFormatVersion >= 11) { int iLogCount; if (fscanf(infile, "%i\n", &iLogCount) != 1) goto error; for (int i = 0; i < iLogCount; i++) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' int iKind, iTime; sscanf(buf, "%i,%i", &iKind, &iTime); char* szText = strchr(buf + 2, ','); if (szText) { szText++; } pNZBInfo->AppendMessage((Message::EKind)iKind, (time_t)iTime, szText); } } if (iFormatVersion < 26) { NZBParameter* pUnpackParameter = pNZBInfo->GetParameters()->Find("*Unpack:", false); if (!pUnpackParameter) { pNZBInfo->GetParameters()->SetParameter("*Unpack:", g_pOptions->GetUnpack() ? "yes" : "no"); } } } if (31 <= iFormatVersion && iFormatVersion < 42) { // due to a bug in r811 (v12-testing) new NZBIDs were generated on each refresh of web-ui // this resulted in very high numbers for NZBIDs // here we renumber NZBIDs in order to keep them low. NZBInfo::ResetGenID(false); int iID = 1; for (NZBInfoList::iterator it = pDownloadQueue->GetNZBInfoList()->begin(); it != pDownloadQueue->GetNZBInfoList()->end(); it++) { NZBInfo* pNZBInfo = *it; pNZBInfo->SetID(iID++); } } return true; error: error("Error reading nzb list from disk"); return false; } void DiskState::SaveFileQueue(DownloadQueue* pDownloadQueue, FileQueue* pFileQueue, FILE* outfile) { debug("Saving file queue to disk"); // save file-infos fprintf(outfile, "%i\n", (int)pFileQueue->size()); for (FileQueue::iterator it = pFileQueue->begin(); it != pFileQueue->end(); it++) { FileInfo* pFileInfo = *it; if (!pFileInfo->GetDeleted()) { int iNZBIndex = FindNZBInfoIndex(pDownloadQueue, pFileInfo->GetNZBInfo()); fprintf(outfile, "%i,%i,%i,%i,%i,%i\n", pFileInfo->GetID(), iNZBIndex, (int)pFileInfo->GetPaused(), (int)pFileInfo->GetTime(), pFileInfo->GetPriority(), (int)pFileInfo->GetExtraPriority()); } } } bool DiskState::LoadFileQueue(DownloadQueue* pDownloadQueue, FileQueue* pFileQueue, FILE* infile, int iFormatVersion) { debug("Loading file queue from disk"); int size; if (fscanf(infile, "%i\n", &size) != 1) goto error; for (int i = 0; i < size; i++) { unsigned int id, iNZBIndex, paused; unsigned int iTime = 0; int iPriority = 0, iExtraPriority = 0; if (iFormatVersion >= 17) { if (fscanf(infile, "%i,%i,%i,%i,%i,%i\n", &id, &iNZBIndex, &paused, &iTime, &iPriority, &iExtraPriority) != 6) goto error; } else if (iFormatVersion >= 14) { if (fscanf(infile, "%i,%i,%i,%i,%i\n", &id, &iNZBIndex, &paused, &iTime, &iPriority) != 5) goto error; } else if (iFormatVersion >= 12) { if (fscanf(infile, "%i,%i,%i,%i\n", &id, &iNZBIndex, &paused, &iTime) != 4) goto error; } else { if (fscanf(infile, "%i,%i,%i\n", &id, &iNZBIndex, &paused) != 3) goto error; } if (iNZBIndex > pDownloadQueue->GetNZBInfoList()->size()) goto error; char fileName[1024]; snprintf(fileName, 1024, "%s%i", g_pOptions->GetQueueDir(), id); fileName[1024-1] = '\0'; FileInfo* pFileInfo = new FileInfo(); bool res = LoadFileInfo(pFileInfo, fileName, true, false); if (res) { pFileInfo->SetID(id); pFileInfo->SetPaused(paused); pFileInfo->SetTime(iTime); pFileInfo->SetPriority(iPriority); pFileInfo->SetExtraPriority(iExtraPriority != 0); pFileInfo->SetNZBInfo(pDownloadQueue->GetNZBInfoList()->at(iNZBIndex - 1)); if (iFormatVersion < 30) { pFileInfo->GetNZBInfo()->SetTotalArticles( pFileInfo->GetNZBInfo()->GetTotalArticles() + pFileInfo->GetTotalArticles()); } pFileQueue->push_back(pFileInfo); } else { delete pFileInfo; } } return true; error: error("Error reading file queue from disk"); return false; } 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::SaveFileInfo(FileInfo* pFileInfo, const char* szFilename) { debug("Saving FileInfo to disk"); FILE* outfile = fopen(szFilename, "wb"); if (!outfile) { error("Error saving diskstate: could not create file %s", szFilename); return false; } fprintf(outfile, "%s%i\n", FORMATVERSION_SIGNATURE, 3); fprintf(outfile, "%s\n", pFileInfo->GetSubject()); fprintf(outfile, "%s\n", pFileInfo->GetFilename()); unsigned long High, Low; Util::SplitInt64(pFileInfo->GetSize(), &High, &Low); fprintf(outfile, "%lu,%lu\n", High, Low); Util::SplitInt64(pFileInfo->GetMissedSize(), &High, &Low); fprintf(outfile, "%lu,%lu\n", High, Low); fprintf(outfile, "%i\n", (int)pFileInfo->GetParFile()); fprintf(outfile, "%i,%i\n", pFileInfo->GetTotalArticles(), pFileInfo->GetMissedArticles()); fprintf(outfile, "%i\n", (int)pFileInfo->GetGroups()->size()); for (FileInfo::Groups::iterator it = pFileInfo->GetGroups()->begin(); it != pFileInfo->GetGroups()->end(); it++) { fprintf(outfile, "%s\n", *it); } fprintf(outfile, "%i\n", (int)pFileInfo->GetArticles()->size()); for (FileInfo::Articles::iterator it = pFileInfo->GetArticles()->begin(); it != pFileInfo->GetArticles()->end(); it++) { ArticleInfo* pArticleInfo = *it; fprintf(outfile, "%i,%i\n", pArticleInfo->GetPartNumber(), pArticleInfo->GetSize()); fprintf(outfile, "%s\n", pArticleInfo->GetMessageID()); } fclose(outfile); return true; } bool DiskState::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::LoadFileInfo(FileInfo* pFileInfo, const char * szFilename, bool bFileSummary, bool bArticles) { debug("Loading FileInfo from disk"); FILE* infile = fopen(szFilename, "rb"); if (!infile) { error("Error reading diskstate: could not open file %s", szFilename); return false; } char buf[1024]; int iFormatVersion = 0; if (fgets(buf, sizeof(buf), infile)) { if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' iFormatVersion = ParseFormatVersion(buf); if (iFormatVersion > 3) { error("Could not load diskstate due to file version mismatch"); goto error; } } else { goto error; } if (iFormatVersion >= 2) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' } if (bFileSummary) pFileInfo->SetSubject(buf); if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' if (bFileSummary) pFileInfo->SetFilename(buf); if (iFormatVersion < 2) { int iFilenameConfirmed; if (fscanf(infile, "%i\n", &iFilenameConfirmed) != 1) goto error; if (bFileSummary) pFileInfo->SetFilenameConfirmed(iFilenameConfirmed); } unsigned long High, Low; if (fscanf(infile, "%lu,%lu\n", &High, &Low) != 2) goto error; if (bFileSummary) pFileInfo->SetSize(Util::JoinInt64(High, Low)); if (bFileSummary) pFileInfo->SetRemainingSize(pFileInfo->GetSize()); if (iFormatVersion >= 2) { if (fscanf(infile, "%lu,%lu\n", &High, &Low) != 2) goto error; if (bFileSummary) pFileInfo->SetMissedSize(Util::JoinInt64(High, Low)); if (bFileSummary) pFileInfo->SetRemainingSize(pFileInfo->GetSize() - pFileInfo->GetMissedSize()); int iParFile; if (fscanf(infile, "%i\n", &iParFile) != 1) goto error; if (bFileSummary) pFileInfo->SetParFile((bool)iParFile); } if (iFormatVersion >= 3) { int iTotalArticles, iMissedArticles; if (fscanf(infile, "%i,%i\n", &iTotalArticles, &iMissedArticles) != 2) goto error; if (bFileSummary) pFileInfo->SetTotalArticles(iTotalArticles); if (bFileSummary) pFileInfo->SetMissedArticles(iMissedArticles); } int size; if (fscanf(infile, "%i\n", &size) != 1) goto error; for (int i = 0; i < size; i++) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' if (bFileSummary) pFileInfo->GetGroups()->push_back(strdup(buf)); } if (fscanf(infile, "%i\n", &size) != 1) goto error; if (iFormatVersion < 3 && bFileSummary) { pFileInfo->SetTotalArticles(size); } if (bArticles) { for (int i = 0; i < size; i++) { int PartNumber, PartSize; if (fscanf(infile, "%i,%i\n", &PartNumber, &PartSize) != 2) goto error; if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' ArticleInfo* pArticleInfo = new ArticleInfo(); pArticleInfo->SetPartNumber(PartNumber); pArticleInfo->SetSize(PartSize); pArticleInfo->SetMessageID(buf); pFileInfo->GetArticles()->push_back(pArticleInfo); } } fclose(infile); return true; error: fclose(infile); error("Error reading diskstate for file %s", szFilename); return false; } void DiskState::SavePostQueue(DownloadQueue* pDownloadQueue, FILE* outfile) { debug("Saving post-queue to disk"); fprintf(outfile, "%i\n", (int)pDownloadQueue->GetPostQueue()->size()); for (PostQueue::iterator it = pDownloadQueue->GetPostQueue()->begin(); it != pDownloadQueue->GetPostQueue()->end(); it++) { PostInfo* pPostInfo = *it; int iNZBIndex = FindNZBInfoIndex(pDownloadQueue, pPostInfo->GetNZBInfo()); fprintf(outfile, "%i,%i\n", iNZBIndex, (int)pPostInfo->GetStage()); fprintf(outfile, "%s\n", pPostInfo->GetInfoName()); } } bool DiskState::LoadPostQueue(DownloadQueue* pDownloadQueue, FILE* infile, int iFormatVersion) { debug("Loading post-queue from disk"); bool bSkipPostQueue = !g_pOptions->GetReloadPostQueue(); int size; char buf[10240]; // load file-infos if (fscanf(infile, "%i\n", &size) != 1) goto error; for (int i = 0; i < size; i++) { PostInfo* pPostInfo = NULL; unsigned int iNZBIndex, iStage, iDummy; if (iFormatVersion < 19) { if (fscanf(infile, "%i,%i,%i,%i\n", &iNZBIndex, &iDummy, &iDummy, &iStage) != 4) goto error; } else if (iFormatVersion < 22) { if (fscanf(infile, "%i,%i,%i,%i\n", &iNZBIndex, &iDummy, &iDummy, &iStage) != 4) goto error; } else { if (fscanf(infile, "%i,%i\n", &iNZBIndex, &iStage) != 2) goto error; } if (iFormatVersion < 18 && iStage > (int)PostInfo::ptVerifyingRepaired) iStage++; if (iFormatVersion < 21 && iStage > (int)PostInfo::ptVerifyingRepaired) iStage++; if (iFormatVersion < 20 && iStage > (int)PostInfo::ptUnpacking) iStage++; if (!bSkipPostQueue) { pPostInfo = new PostInfo(); pPostInfo->SetNZBInfo(pDownloadQueue->GetNZBInfoList()->at(iNZBIndex - 1)); pPostInfo->SetStage((PostInfo::EStage)iStage); } if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' if (!bSkipPostQueue) pPostInfo->SetInfoName(buf); if (iFormatVersion < 22) { // ParFilename, ignore if (!fgets(buf, sizeof(buf), infile)) goto error; } if (!bSkipPostQueue) { pDownloadQueue->GetPostQueue()->push_back(pPostInfo); } } return true; error: error("Error reading diskstate for post-processor queue"); return false; } /* * Loads post-queue created with older versions of nzbget. * Returns true if successful, false if not */ bool DiskState::LoadOldPostQueue(DownloadQueue* pDownloadQueue) { debug("Loading post-queue from disk"); char fileName[1024]; snprintf(fileName, 1024, "%s%s", g_pOptions->GetQueueDir(), "postq"); fileName[1024-1] = '\0'; if (!Util::FileExists(fileName)) { return true; } FILE* infile = fopen(fileName, "rb"); if (!infile) { error("Error reading diskstate: could not open file %s", fileName); return false; } char FileSignatur[128]; fgets(FileSignatur, sizeof(FileSignatur), infile); int iFormatVersion = ParseFormatVersion(FileSignatur); if (iFormatVersion < 3 || iFormatVersion > 7) { error("Could not load diskstate due to file version mismatch"); fclose(infile); return false; } int size; char buf[10240]; int iIntValue; // load file-infos if (fscanf(infile, "%i\n", &size) != 1) goto error; for (int i = 0; i < size; i++) { PostInfo* pPostInfo = new PostInfo(); if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' // find NZBInfo based on NZBFilename NZBInfo* pNZBInfo = NULL; for (NZBInfoList::iterator it = pDownloadQueue->GetNZBInfoList()->begin(); it != pDownloadQueue->GetNZBInfoList()->end(); it++) { NZBInfo* pNZBInfo2 = *it; if (!strcmp(pNZBInfo2->GetFilename(), buf)) { pNZBInfo = pNZBInfo2; break; } } bool bNewNZBInfo = !pNZBInfo; if (bNewNZBInfo) { pNZBInfo = new NZBInfo(); pNZBInfo->Retain(); pDownloadQueue->GetNZBInfoList()->Add(pNZBInfo); pNZBInfo->SetFilename(buf); } pPostInfo->SetNZBInfo(pNZBInfo); if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' if (bNewNZBInfo) { pNZBInfo->SetDestDir(buf); } // ParFilename, ignore if (!fgets(buf, sizeof(buf), infile)) goto error; if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' pPostInfo->SetInfoName(buf); if (iFormatVersion >= 4) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' if (bNewNZBInfo) { pNZBInfo->SetCategory(buf); } } else { if (bNewNZBInfo) { pNZBInfo->SetCategory(""); } } if (iFormatVersion >= 5) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' if (bNewNZBInfo) { pNZBInfo->SetQueuedFilename(buf); } } else { if (bNewNZBInfo) { pNZBInfo->SetQueuedFilename(""); } } int iParCheck; if (fscanf(infile, "%i\n", &iParCheck) != 1) goto error; // ParCheck if (fscanf(infile, "%i\n", &iIntValue) != 1) goto error; pNZBInfo->SetParStatus(iParCheck ? (NZBInfo::EParStatus)iIntValue : NZBInfo::psSkipped); if (iFormatVersion < 7) { // skip old field ParFailed, not used anymore if (fscanf(infile, "%i\n", &iIntValue) != 1) goto error; } if (fscanf(infile, "%i\n", &iIntValue) != 1) goto error; pPostInfo->SetStage((PostInfo::EStage)iIntValue); if (iFormatVersion >= 6) { int iParameterCount; if (fscanf(infile, "%i\n", &iParameterCount) != 1) goto error; for (int i = 0; i < iParameterCount; i++) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' char* szValue = strchr(buf, '='); if (szValue) { *szValue = '\0'; szValue++; if (bNewNZBInfo) { pNZBInfo->GetParameters()->SetParameter(buf, szValue); } } } } pDownloadQueue->GetPostQueue()->push_back(pPostInfo); } fclose(infile); return true; error: fclose(infile); error("Error reading diskstate for file %s", fileName); return false; } void DiskState::SaveUrlQueue(DownloadQueue* pDownloadQueue, FILE* outfile) { debug("Saving url-queue to disk"); fprintf(outfile, "%i\n", (int)pDownloadQueue->GetUrlQueue()->size()); for (UrlQueue::iterator it = pDownloadQueue->GetUrlQueue()->begin(); it != pDownloadQueue->GetUrlQueue()->end(); it++) { UrlInfo* pUrlInfo = *it; SaveUrlInfo(pUrlInfo, outfile); } } bool DiskState::LoadUrlQueue(DownloadQueue* pDownloadQueue, FILE* infile, int iFormatVersion) { debug("Loading url-queue from disk"); bool bSkipUrlQueue = !g_pOptions->GetReloadUrlQueue(); int size; // load url-infos if (fscanf(infile, "%i\n", &size) != 1) goto error; for (int i = 0; i < size; i++) { UrlInfo* pUrlInfo = new UrlInfo(); if (!LoadUrlInfo(pUrlInfo, infile, iFormatVersion)) goto error; if (!bSkipUrlQueue) { pDownloadQueue->GetUrlQueue()->push_back(pUrlInfo); } else { delete pUrlInfo; } } return true; error: error("Error reading diskstate for url-queue"); return false; } void DiskState::SaveUrlInfo(UrlInfo* pUrlInfo, FILE* outfile) { fprintf(outfile, "%i\n", pUrlInfo->GetID()); fprintf(outfile, "%i,%i\n", (int)pUrlInfo->GetStatus(), pUrlInfo->GetPriority()); fprintf(outfile, "%i,%i\n", (int)pUrlInfo->GetAddTop(), pUrlInfo->GetAddPaused()); fprintf(outfile, "%s\n", pUrlInfo->GetURL()); fprintf(outfile, "%s\n", pUrlInfo->GetNZBFilename()); fprintf(outfile, "%s\n", pUrlInfo->GetCategory()); fprintf(outfile, "%s\n", pUrlInfo->GetDupeKey()); fprintf(outfile, "%i,%i\n", (int)pUrlInfo->GetDupeMode(), pUrlInfo->GetDupeScore()); } bool DiskState::LoadUrlInfo(UrlInfo* pUrlInfo, FILE* infile, int iFormatVersion) { char buf[10240]; if (iFormatVersion >= 24) { int iID; if (fscanf(infile, "%i\n", &iID) != 1) goto error; pUrlInfo->SetID(iID); } int iStatus, iPriority; if (fscanf(infile, "%i,%i\n", &iStatus, &iPriority) != 2) goto error; pUrlInfo->SetStatus((UrlInfo::EStatus)iStatus); pUrlInfo->SetPriority(iPriority); if (iFormatVersion >= 16) { int iAddTop, iAddPaused; if (fscanf(infile, "%i,%i\n", &iAddTop, &iAddPaused) != 2) goto error; pUrlInfo->SetAddTop(iAddTop); pUrlInfo->SetAddPaused(iAddPaused); } if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' pUrlInfo->SetURL(buf); if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' pUrlInfo->SetNZBFilename(buf); if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' pUrlInfo->SetCategory(buf); if (iFormatVersion >= 31) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' if (iFormatVersion < 36) ConvertDupeKey(buf, sizeof(buf)); pUrlInfo->SetDupeKey(buf); int iDupeMode, iDupeScore; if (fscanf(infile, "%i,%i\n", &iDupeMode, &iDupeScore) != 2) goto error; pUrlInfo->SetDupeMode((EDupeMode)iDupeMode); pUrlInfo->SetDupeScore(iDupeScore); } return true; error: return false; } void DiskState::SaveDupInfo(DupInfo* pDupInfo, FILE* outfile) { unsigned long High, Low; Util::SplitInt64(pDupInfo->GetSize(), &High, &Low); fprintf(outfile, "%i,%lu,%lu,%u,%u,%i,%i\n", (int)pDupInfo->GetStatus(), High, Low, pDupInfo->GetFullContentHash(), pDupInfo->GetFilteredContentHash(), pDupInfo->GetDupeScore(), (int)pDupInfo->GetDupeMode()); fprintf(outfile, "%s\n", pDupInfo->GetName()); fprintf(outfile, "%s\n", pDupInfo->GetDupeKey()); } bool DiskState::LoadDupInfo(DupInfo* pDupInfo, FILE* infile, int iFormatVersion) { char buf[1024]; int iStatus; unsigned long High, Low; unsigned int iFullContentHash, iFilteredContentHash = 0; int iDupeScore, iDupe = 0, iDupeMode = 0; if (iFormatVersion >= 39) { if (fscanf(infile, "%i,%lu,%lu,%u,%u,%i,%i\n", &iStatus, &High, &Low, &iFullContentHash, &iFilteredContentHash, &iDupeScore, &iDupeMode) != 7) goto error; } else if (iFormatVersion >= 38) { if (fscanf(infile, "%i,%lu,%lu,%u,%u,%i,%i,%i\n", &iStatus, &High, &Low, &iFullContentHash, &iFilteredContentHash, &iDupeScore, &iDupe, &iDupeMode) != 8) goto error; } else if (iFormatVersion >= 37) { if (fscanf(infile, "%i,%lu,%lu,%u,%u,%i,%i\n", &iStatus, &High, &Low, &iFullContentHash, &iFilteredContentHash, &iDupeScore, &iDupe) != 7) goto error; } else if (iFormatVersion >= 34) { if (fscanf(infile, "%i,%lu,%lu,%u,%u,%i\n", &iStatus, &High, &Low, &iFullContentHash, &iFilteredContentHash, &iDupeScore) != 6) goto error; } else { if (fscanf(infile, "%i,%lu,%lu,%u,%i\n", &iStatus, &High, &Low, &iFullContentHash, &iDupeScore) != 5) goto error; } pDupInfo->SetStatus((DupInfo::EStatus)iStatus); pDupInfo->SetFullContentHash(iFullContentHash); pDupInfo->SetFilteredContentHash(iFilteredContentHash); pDupInfo->SetSize(Util::JoinInt64(High, Low)); pDupInfo->SetDupeScore(iDupeScore); pDupInfo->SetDupeMode((EDupeMode)iDupeMode); if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' pDupInfo->SetName(buf); if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' if (iFormatVersion < 36) ConvertDupeKey(buf, sizeof(buf)); pDupInfo->SetDupeKey(buf); return true; error: return false; } void DiskState::SaveHistory(DownloadQueue* pDownloadQueue, FILE* outfile) { debug("Saving history to disk"); fprintf(outfile, "%i\n", (int)pDownloadQueue->GetHistoryList()->size()); for (HistoryList::iterator it = pDownloadQueue->GetHistoryList()->begin(); it != pDownloadQueue->GetHistoryList()->end(); it++) { HistoryInfo* pHistoryInfo = *it; fprintf(outfile, "%i,%i,%i\n", pHistoryInfo->GetID(), (int)pHistoryInfo->GetKind(), (int)pHistoryInfo->GetTime()); if (pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo) { int iNZBIndex = FindNZBInfoIndex(pDownloadQueue, pHistoryInfo->GetNZBInfo()); fprintf(outfile, "%i\n", iNZBIndex); } else if (pHistoryInfo->GetKind() == HistoryInfo::hkUrlInfo) { SaveUrlInfo(pHistoryInfo->GetUrlInfo(), outfile); } else if (pHistoryInfo->GetKind() == HistoryInfo::hkDupInfo) { SaveDupInfo(pHistoryInfo->GetDupInfo(), outfile); } } } bool DiskState::LoadHistory(DownloadQueue* pDownloadQueue, FILE* infile, int iFormatVersion) { debug("Loading history from disk"); int size; if (fscanf(infile, "%i\n", &size) != 1) goto error; for (int i = 0; i < size; i++) { HistoryInfo* pHistoryInfo = NULL; HistoryInfo::EKind eKind = HistoryInfo::hkNZBInfo; int iID = 0; int iTime; if (iFormatVersion >= 33) { int iKind = 0; if (fscanf(infile, "%i,%i,%i\n", &iID, &iKind, &iTime) != 3) goto error; eKind = (HistoryInfo::EKind)iKind; } else { if (iFormatVersion >= 24) { if (fscanf(infile, "%i\n", &iID) != 1) goto error; } if (iFormatVersion >= 15) { int iKind = 0; if (fscanf(infile, "%i\n", &iKind) != 1) goto error; eKind = (HistoryInfo::EKind)iKind; } } if (eKind == HistoryInfo::hkNZBInfo) { unsigned int iNZBIndex; if (fscanf(infile, "%i\n", &iNZBIndex) != 1) goto error; NZBInfo* pNZBInfo = pDownloadQueue->GetNZBInfoList()->at(iNZBIndex - 1); pHistoryInfo = new HistoryInfo(pNZBInfo); if (iFormatVersion < 28 && pNZBInfo->GetParStatus() == 0 && pNZBInfo->GetUnpackStatus() == 0 && pNZBInfo->GetMoveStatus() == 0) { pNZBInfo->SetDeleteStatus(NZBInfo::dsManual); } } else if (eKind == HistoryInfo::hkUrlInfo) { UrlInfo* pUrlInfo = new UrlInfo(); if (!LoadUrlInfo(pUrlInfo, infile, iFormatVersion)) goto error; pHistoryInfo = new HistoryInfo(pUrlInfo); } else if (eKind == HistoryInfo::hkDupInfo) { DupInfo* pDupInfo = new DupInfo(); if (!LoadDupInfo(pDupInfo, infile, iFormatVersion)) goto error; pHistoryInfo = new HistoryInfo(pDupInfo); } if (iFormatVersion >= 24) { pHistoryInfo->SetID(iID); } if (iFormatVersion < 33) { if (fscanf(infile, "%i\n", &iTime) != 1) goto error; } pHistoryInfo->SetTime((time_t)iTime); pDownloadQueue->GetHistoryList()->push_back(pHistoryInfo); } return true; error: error("Error reading diskstate for history"); return false; } int DiskState::FindNZBInfoIndex(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo) { // find index of nzb-info int iNZBIndex = 0; for (unsigned int i = 0; i < pDownloadQueue->GetNZBInfoList()->size(); i++) { iNZBIndex++; if (pDownloadQueue->GetNZBInfoList()->at(i) == pNZBInfo) { break; } } return iNZBIndex; } /* * Deletes whole download queue including history. */ void DiskState::DiscardDownloadQueue() { debug("Discarding queue"); char szFullFilename[1024]; snprintf(szFullFilename, 1024, "%s%s", g_pOptions->GetQueueDir(), "queue"); szFullFilename[1024-1] = '\0'; remove(szFullFilename); DirBrowser dir(g_pOptions->GetQueueDir()); while (const char* filename = dir.Next()) { // delete all files whose names have only characters '0'..'9' bool bOnlyNums = true; for (const char* p = filename; *p != '\0'; p++) { if (!('0' <= *p && *p <= '9')) { bOnlyNums = false; break; } } if (bOnlyNums) { snprintf(szFullFilename, 1024, "%s%s", g_pOptions->GetQueueDir(), filename); szFullFilename[1024-1] = '\0'; remove(szFullFilename); } } } bool DiskState::DownloadQueueExists() { debug("Checking if a saved queue exists on disk"); char fileName[1024]; snprintf(fileName, 1024, "%s%s", g_pOptions->GetQueueDir(), "queue"); fileName[1024-1] = '\0'; return Util::FileExists(fileName); } bool DiskState::DiscardFile(FileInfo* pFileInfo) { // delete diskstate-file for file-info char fileName[1024]; snprintf(fileName, 1024, "%s%i", g_pOptions->GetQueueDir(), pFileInfo->GetID()); fileName[1024-1] = '\0'; remove(fileName); return true; } void DiskState::CleanupTempDir(DownloadQueue* pDownloadQueue) { // build array of IDs of files in queue for faster access int* ids = (int*)malloc(sizeof(int) * (pDownloadQueue->GetFileQueue()->size() + 1)); int* ptr = ids; for (FileQueue::iterator it = pDownloadQueue->GetFileQueue()->begin(); it != pDownloadQueue->GetFileQueue()->end(); it++) { FileInfo* pFileInfo = *it; *ptr++ = pFileInfo->GetID(); } *ptr = 0; // read directory DirBrowser dir(g_pOptions->GetTempDir()); while (const char* filename = dir.Next()) { int id, part; bool del = strstr(filename, ".tmp") || strstr(filename, ".dec") || ((strstr(filename, ".out") && (sscanf(filename, "%i.out", &id) == 1) && !(g_pOptions->GetContinuePartial() && g_pOptions->GetDirectWrite())) || ((sscanf(filename, "%i.%i", &id, &part) == 2) && !g_pOptions->GetContinuePartial())); if (!del) { if ((sscanf(filename, "%i.%i", &id, &part) == 2) || (strstr(filename, ".out") && (sscanf(filename, "%i.out", &id) == 1))) { del = true; ptr = ids; while (*ptr) { if (*ptr == id) { del = false; break; } ptr++; } } } if (del) { char szFullFilename[1024]; snprintf(szFullFilename, 1024, "%s%s", g_pOptions->GetTempDir(), filename); szFullFilename[1024-1] = '\0'; remove(szFullFilename); } } free(ids); } /* For safety: * - first save to temp-file (feeds.new) * - then delete feeds * - then rename feeds.new to feeds */ bool DiskState::SaveFeeds(Feeds* pFeeds, FeedHistory* pFeedHistory) { debug("Saving feeds state to disk"); char destFilename[1024]; snprintf(destFilename, 1024, "%s%s", g_pOptions->GetQueueDir(), "feeds"); destFilename[1024-1] = '\0'; char tempFilename[1024]; snprintf(tempFilename, 1024, "%s%s", g_pOptions->GetQueueDir(), "feeds.new"); tempFilename[1024-1] = '\0'; if (pFeeds->empty() && pFeedHistory->empty()) { remove(destFilename); return true; } FILE* outfile = fopen(tempFilename, "wb"); if (!outfile) { error("Error saving diskstate: Could not create file %s", tempFilename); return false; } fprintf(outfile, "%s%i\n", FORMATVERSION_SIGNATURE, 3); // save status SaveFeedStatus(pFeeds, outfile); // save history SaveFeedHistory(pFeedHistory, outfile); fclose(outfile); // now rename to dest file name remove(destFilename); if (rename(tempFilename, destFilename)) { error("Error saving diskstate: Could not rename file %s to %s", tempFilename, destFilename); return false; } return true; } bool DiskState::LoadFeeds(Feeds* pFeeds, FeedHistory* pFeedHistory) { debug("Loading feeds state from disk"); bool bOK = false; char fileName[1024]; snprintf(fileName, 1024, "%s%s", g_pOptions->GetQueueDir(), "feeds"); fileName[1024-1] = '\0'; if (!Util::FileExists(fileName)) { return true; } FILE* infile = fopen(fileName, "rb"); if (!infile) { error("Error reading diskstate: could not open file %s", fileName); return false; } char FileSignatur[128]; fgets(FileSignatur, sizeof(FileSignatur), infile); int iFormatVersion = ParseFormatVersion(FileSignatur); if (iFormatVersion > 3) { error("Could not load diskstate due to file version mismatch"); fclose(infile); return false; } // load feed status if (!LoadFeedStatus(pFeeds, infile, iFormatVersion)) goto error; // load feed history if (!LoadFeedHistory(pFeedHistory, infile, iFormatVersion)) goto error; bOK = true; error: fclose(infile); if (!bOK) { error("Error reading diskstate for file %s", fileName); } return bOK; } bool DiskState::SaveFeedStatus(Feeds* pFeeds, FILE* outfile) { debug("Saving feed status to disk"); fprintf(outfile, "%i\n", (int)pFeeds->size()); for (Feeds::iterator it = pFeeds->begin(); it != pFeeds->end(); it++) { FeedInfo* pFeedInfo = *it; fprintf(outfile, "%s\n", pFeedInfo->GetUrl()); fprintf(outfile, "%u\n", pFeedInfo->GetFilterHash()); fprintf(outfile, "%i\n", (int)pFeedInfo->GetLastUpdate()); } return true; } bool DiskState::LoadFeedStatus(Feeds* pFeeds, FILE* infile, int iFormatVersion) { debug("Loading feed status from disk"); int size; if (fscanf(infile, "%i\n", &size) != 1) goto error; for (int i = 0; i < size; i++) { char szUrl[1024]; if (!fgets(szUrl, sizeof(szUrl), infile)) goto error; if (szUrl[0] != 0) szUrl[strlen(szUrl)-1] = 0; // remove traling '\n' char szFilter[1024]; if (iFormatVersion == 2) { if (!fgets(szFilter, sizeof(szFilter), infile)) goto error; if (szFilter[0] != 0) szFilter[strlen(szFilter)-1] = 0; // remove traling '\n' } unsigned int iFilterHash = 0; if (iFormatVersion >= 3) { if (fscanf(infile, "%u\n", &iFilterHash) != 1) goto error; } int iLastUpdate = 0; if (fscanf(infile, "%i\n", &iLastUpdate) != 1) goto error; for (Feeds::iterator it = pFeeds->begin(); it != pFeeds->end(); it++) { FeedInfo* pFeedInfo = *it; if (!strcmp(pFeedInfo->GetUrl(), szUrl) && ((iFormatVersion == 1) || (iFormatVersion == 2 && !strcmp(pFeedInfo->GetFilter(), szFilter)) || (iFormatVersion >= 3 && pFeedInfo->GetFilterHash() == iFilterHash))) { pFeedInfo->SetLastUpdate((time_t)iLastUpdate); } } } return true; error: error("Error reading feed status from disk"); return false; } bool DiskState::SaveFeedHistory(FeedHistory* pFeedHistory, FILE* outfile) { debug("Saving feed history to disk"); fprintf(outfile, "%i\n", (int)pFeedHistory->size()); for (FeedHistory::iterator it = pFeedHistory->begin(); it != pFeedHistory->end(); it++) { FeedHistoryInfo* pFeedHistoryInfo = *it; fprintf(outfile, "%i,%i\n", (int)pFeedHistoryInfo->GetStatus(), (int)pFeedHistoryInfo->GetLastSeen()); fprintf(outfile, "%s\n", pFeedHistoryInfo->GetUrl()); } return true; } bool DiskState::LoadFeedHistory(FeedHistory* pFeedHistory, FILE* infile, int iFormatVersion) { debug("Loading feed history from disk"); int size; if (fscanf(infile, "%i\n", &size) != 1) goto error; for (int i = 0; i < size; i++) { int iStatus = 0; int iLastSeen = 0; int r = fscanf(infile, "%i,%i\n", &iStatus, &iLastSeen); if (r != 2) goto error; char szUrl[1024]; if (!fgets(szUrl, sizeof(szUrl), infile)) goto error; if (szUrl[0] != 0) szUrl[strlen(szUrl)-1] = 0; // remove traling '\n' pFeedHistory->Add(szUrl, (FeedHistoryInfo::EStatus)(iStatus), (time_t)(iLastSeen)); } return true; error: error("Error reading feed history from disk"); return false; } // calculate critical health for old NZBs void DiskState::CalcCriticalHealth(DownloadQueue* pDownloadQueue) { std::set oldNZBs; // build list of old NZBs which do not have critical health calculated for (NZBInfoList::iterator it = pDownloadQueue->GetNZBInfoList()->begin(); it != pDownloadQueue->GetNZBInfoList()->end(); it++) { NZBInfo* pNZBInfo = *it; if (pNZBInfo->CalcCriticalHealth() == 1000) { oldNZBs.insert(pNZBInfo); } } for (FileQueue::iterator it = pDownloadQueue->GetFileQueue()->begin(); it != pDownloadQueue->GetFileQueue()->end(); it++) { FileInfo* pFileInfo = *it; NZBInfo* pNZBInfo = pFileInfo->GetNZBInfo(); if (oldNZBs.find(pNZBInfo) != oldNZBs.end()) { debug("Calculating critical health for %s", pNZBInfo->GetName()); 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 bool bParFile = strstr(szLoFileName, ".par2"); pFileInfo->SetParFile(bParFile); if (bParFile) { pNZBInfo->SetParSize(pNZBInfo->GetParSize() + pFileInfo->GetSize()); } } } } bool DiskState::SaveStats(Servers* pServers) { debug("Saving stats to disk"); char destFilename[1024]; snprintf(destFilename, 1024, "%s%s", g_pOptions->GetQueueDir(), "stats"); destFilename[1024-1] = '\0'; char tempFilename[1024]; snprintf(tempFilename, 1024, "%s%s", g_pOptions->GetQueueDir(), "stats.new"); tempFilename[1024-1] = '\0'; if (pServers->empty()) { remove(destFilename); return true; } FILE* outfile = fopen(tempFilename, "wb"); if (!outfile) { error("Error saving diskstate: Could not create file %s", tempFilename); return false; } fprintf(outfile, "%s%i\n", FORMATVERSION_SIGNATURE, 1); // save status SaveServerStats(pServers, outfile); fclose(outfile); // now rename to dest file name remove(destFilename); if (rename(tempFilename, destFilename)) { error("Error saving diskstate: Could not rename file %s to %s", tempFilename, destFilename); return false; } return true; } bool DiskState::LoadStats(Servers* pServers) { debug("Loading stats from disk"); bool bOK = false; char fileName[1024]; snprintf(fileName, 1024, "%s%s", g_pOptions->GetQueueDir(), "stats"); fileName[1024-1] = '\0'; if (!Util::FileExists(fileName)) { return true; } FILE* infile = fopen(fileName, "rb"); if (!infile) { error("Error reading diskstate: could not open file %s", fileName); return false; } char FileSignatur[128]; fgets(FileSignatur, sizeof(FileSignatur), infile); int iFormatVersion = ParseFormatVersion(FileSignatur); if (iFormatVersion > 1) { error("Could not load diskstate due to file version mismatch"); fclose(infile); return false; } if (!LoadServerStats(pServers, infile, iFormatVersion)) goto error; bOK = true; error: fclose(infile); if (!bOK) { error("Error reading diskstate for file %s", fileName); } return bOK; } bool DiskState::SaveServerStats(Servers* pServers, FILE* outfile) { debug("Saving server stats to disk"); fprintf(outfile, "%i\n", (int)pServers->size()); for (Servers::iterator it = pServers->begin(); it != pServers->end(); it++) { NewsServer* pNewsServer = *it; fprintf(outfile, "%s\n", pNewsServer->GetName()); fprintf(outfile, "%s\n", pNewsServer->GetHost()); fprintf(outfile, "%i\n", pNewsServer->GetPort()); fprintf(outfile, "%s\n", pNewsServer->GetUser()); } return true; } /* *************************************************************************************** * Server matching */ class ServerRef { public: int m_iStateID; char* m_szName; char* m_szHost; int m_iPort; char* m_szUser; bool m_bMatched; ~ServerRef(); int GetStateID() { return m_iStateID; } const char* GetName() { return m_szName; } const char* GetHost() { return m_szHost; } int GetPort() { return m_iPort; } const char* GetUser() { return m_szUser; } bool GetMatched() { return m_bMatched; } void SetMatched(bool bMatched) { m_bMatched = bMatched; } }; typedef std::deque ServerRefList; ServerRef::~ServerRef() { free(m_szName); free(m_szHost); free(m_szUser); } enum ECriteria { eName, eHost, ePort, eUser }; void FindCandidates(NewsServer* pNewsServer, ServerRefList* pRefs, ECriteria eCriteria, bool bKeepIfNothing) { ServerRefList originalRefs; originalRefs.insert(originalRefs.begin(), pRefs->begin(), pRefs->end()); int index = 0; for (ServerRefList::iterator it = pRefs->begin(); it != pRefs->end(); ) { ServerRef* pRef = *it; bool bMatch = false; switch(eCriteria) { case eName: bMatch = !strcasecmp(pNewsServer->GetName(), pRef->GetName()); break; case eHost: bMatch = !strcasecmp(pNewsServer->GetHost(), pRef->GetHost()); break; case ePort: bMatch = pNewsServer->GetPort() == pRef->GetPort(); break; case eUser: bMatch = !strcasecmp(pNewsServer->GetUser(), pRef->GetUser()); break; } if (bMatch && !pRef->GetMatched()) { it++; index++; } else { pRefs->erase(it); it = pRefs->begin() + index; } } if (pRefs->size() == 0 && bKeepIfNothing) { pRefs->insert(pRefs->begin(), originalRefs.begin(), originalRefs.end()); } } void MatchServers(Servers* pServers, ServerRefList* pServerRefs) { // Step 1: trying perfect match for (Servers::iterator it = pServers->begin(); it != pServers->end(); it++) { NewsServer* pNewsServer = *it; ServerRefList matchedRefs; matchedRefs.insert(matchedRefs.begin(), pServerRefs->begin(), pServerRefs->end()); FindCandidates(pNewsServer, &matchedRefs, eName, false); FindCandidates(pNewsServer, &matchedRefs, eHost, false); FindCandidates(pNewsServer, &matchedRefs, ePort, false); FindCandidates(pNewsServer, &matchedRefs, eUser, false); if (matchedRefs.size() == 1) { ServerRef* pRef = matchedRefs.front(); pNewsServer->SetStateID(pRef->GetStateID()); pRef->SetMatched(true); } } // Step 2: matching host, port, username ans server-name for (Servers::iterator it = pServers->begin(); it != pServers->end(); it++) { NewsServer* pNewsServer = *it; if (!pNewsServer->GetStateID()) { ServerRefList matchedRefs; matchedRefs.insert(matchedRefs.begin(), pServerRefs->begin(), pServerRefs->end()); FindCandidates(pNewsServer, &matchedRefs, eHost, false); if (matchedRefs.size() > 1) { FindCandidates(pNewsServer, &matchedRefs, eName, true); } if (matchedRefs.size() > 1) { FindCandidates(pNewsServer, &matchedRefs, eUser, true); } if (matchedRefs.size() > 1) { FindCandidates(pNewsServer, &matchedRefs, ePort, true); } if (!matchedRefs.empty()) { ServerRef* pRef = matchedRefs.front(); pNewsServer->SetStateID(pRef->GetStateID()); pRef->SetMatched(true); } } } } /* * END: Server matching *************************************************************************************** */ bool DiskState::LoadServerStats(Servers* pServers, FILE* infile, int iFormatVersion) { debug("Loading server stats from disk"); ServerRefList serverRefs; int size; if (fscanf(infile, "%i\n", &size) != 1) goto error; for (int i = 0; i < size; i++) { char szName[1024]; if (!fgets(szName, sizeof(szName), infile)) goto error; if (szName[0] != 0) szName[strlen(szName)-1] = 0; // remove traling '\n' char szHost[200]; if (!fgets(szHost, sizeof(szHost), infile)) goto error; if (szHost[0] != 0) szHost[strlen(szHost)-1] = 0; // remove traling '\n' int iPort; if (fscanf(infile, "%i\n", &iPort) != 1) goto error; char szUser[100]; if (!fgets(szUser, sizeof(szUser), infile)) goto error; if (szUser[0] != 0) szUser[strlen(szUser)-1] = 0; // remove traling '\n' ServerRef* pRef = new ServerRef(); pRef->m_iStateID = i + 1; pRef->m_szName = strdup(szName); pRef->m_szHost = strdup(szHost); pRef->m_iPort = iPort; pRef->m_szUser = strdup(szUser); pRef->m_bMatched = false; serverRefs.push_back(pRef); } MatchServers(pServers, &serverRefs); for (ServerRefList::iterator it = serverRefs.begin(); it != serverRefs.end(); it++) { delete *it; } debug("******** MATCHING NEWS-SERVERS **********"); for (Servers::iterator it = pServers->begin(); it != pServers->end(); it++) { NewsServer* pNewsServer = *it; debug("Server %i -> %i", pNewsServer->GetID(), pNewsServer->GetStateID()); debug("Server %i.Name: %s", pNewsServer->GetID(), pNewsServer->GetName()); debug("Server %i.Host: %s:%i", pNewsServer->GetID(), pNewsServer->GetHost(), pNewsServer->GetPort()); } return true; error: error("Error reading server stats from disk"); for (ServerRefList::iterator it = serverRefs.begin(); it != serverRefs.end(); it++) { delete *it; } return false; } void DiskState::ConvertDupeKey(char* buf, int bufsize) { if (strncmp(buf, "rageid=", 7)) { return; } int iRageId = atoi(buf + 7); int iSeason = 0; int iEpisode = 0; char* p = strchr(buf + 7, ','); if (p) { iSeason = atoi(p + 1); p = strchr(p + 1, ','); if (p) { iEpisode = atoi(p + 1); } } if (iRageId != 0 && iSeason != 0 && iEpisode != 0) { snprintf(buf, bufsize, "rageid=%i-S%02i-E%02i", iRageId, iSeason, iEpisode); } } nzbget-12.0+dfsg/DiskState.h000066400000000000000000000072721226450633000157210ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 846 $ * $Date: 2013-09-26 21:37:25 +0200 (Thu, 26 Sep 2013) $ * */ #ifndef DISKSTATE_H #define DISKSTATE_H #include "DownloadInfo.h" #include "FeedInfo.h" #include "NewsServer.h" class DiskState { private: int fscanf(FILE* infile, const char* Format, ...); int ParseFormatVersion(const char* szFormatSignature); bool SaveFileInfo(FileInfo* pFileInfo, const char* szFilename); bool LoadFileInfo(FileInfo* pFileInfo, const char* szFilename, bool bFileSummary, bool bArticles); void SaveNZBList(DownloadQueue* pDownloadQueue, FILE* outfile); bool LoadNZBList(DownloadQueue* pDownloadQueue, FILE* infile, int iFormatVersion); void SaveFileQueue(DownloadQueue* pDownloadQueue, FileQueue* pFileQueue, FILE* outfile); bool LoadFileQueue(DownloadQueue* pDownloadQueue, FileQueue* pFileQueue, FILE* infile, int iFormatVersion); void SavePostQueue(DownloadQueue* pDownloadQueue, FILE* outfile); bool LoadPostQueue(DownloadQueue* pDownloadQueue, FILE* infile, int iFormatVersion); bool LoadOldPostQueue(DownloadQueue* pDownloadQueue); void SaveUrlQueue(DownloadQueue* pDownloadQueue, FILE* outfile); bool LoadUrlQueue(DownloadQueue* pDownloadQueue, FILE* infile, int iFormatVersion); void SaveUrlInfo(UrlInfo* pUrlInfo, FILE* outfile); bool LoadUrlInfo(UrlInfo* pUrlInfo, FILE* infile, int iFormatVersion); void SaveDupInfo(DupInfo* pDupInfo, FILE* outfile); bool LoadDupInfo(DupInfo* pDupInfo, FILE* infile, int iFormatVersion); void SaveHistory(DownloadQueue* pDownloadQueue, FILE* outfile); bool LoadHistory(DownloadQueue* pDownloadQueue, FILE* infile, int iFormatVersion); int FindNZBInfoIndex(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo); bool SaveFeedStatus(Feeds* pFeeds, FILE* outfile); bool LoadFeedStatus(Feeds* pFeeds, FILE* infile, int iFormatVersion); bool SaveFeedHistory(FeedHistory* pFeedHistory, FILE* outfile); bool LoadFeedHistory(FeedHistory* pFeedHistory, FILE* infile, int iFormatVersion); void CalcCriticalHealth(DownloadQueue* pDownloadQueue); bool SaveServerStats(Servers* pServers, FILE* outfile); bool LoadServerStats(Servers* pServers, FILE* infile, int iFormatVersion); void ConvertDupeKey(char* buf, int bufsize); public: bool DownloadQueueExists(); bool SaveDownloadQueue(DownloadQueue* pDownloadQueue); bool LoadDownloadQueue(DownloadQueue* pDownloadQueue); bool SaveFile(FileInfo* pFileInfo); bool LoadArticles(FileInfo* pFileInfo); void DiscardDownloadQueue(); bool DiscardFile(FileInfo* pFileInfo); bool SaveFeeds(Feeds* pFeeds, FeedHistory* pFeedHistory); bool LoadFeeds(Feeds* pFeeds, FeedHistory* pFeedHistory); bool SaveStats(Servers* pServers); bool LoadStats(Servers* pServers); void CleanupTempDir(DownloadQueue* pDownloadQueue); }; #endif nzbget-12.0+dfsg/DownloadInfo.cpp000066400000000000000000000551471226450633000167500ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 921 $ * $Date: 2013-12-18 21:19:42 +0100 (Wed, 18 Dec 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #include #include #include #include "nzbget.h" #include "DownloadInfo.h" #include "Options.h" #include "Util.h" extern Options* g_pOptions; int FileInfo::m_iIDGen = 0; int FileInfo::m_iIDMax = 0; int NZBInfo::m_iIDGen = 0; int NZBInfo::m_iIDMax = 0; int PostInfo::m_iIDGen = 0; int PostInfo::m_iIDMax = 0; int UrlInfo::m_iIDGen = 0; int UrlInfo::m_iIDMax = 0; int HistoryInfo::m_iIDGen = 0; int HistoryInfo::m_iIDMax = 0; NZBParameter::NZBParameter(const char* szName) { m_szName = strdup(szName); m_szValue = NULL; } NZBParameter::~NZBParameter() { free(m_szName); free(m_szValue); } void NZBParameter::SetValue(const char* szValue) { free(m_szValue); m_szValue = strdup(szValue); } NZBParameterList::~NZBParameterList() { Clear(); } void NZBParameterList::Clear() { for (iterator it = begin(); it != end(); it++) { delete *it; } clear(); } void NZBParameterList::SetParameter(const char* szName, const char* szValue) { NZBParameter* pParameter = NULL; bool bDelete = !szValue || !*szValue; for (iterator it = begin(); it != end(); it++) { NZBParameter* pLookupParameter = *it; if (!strcmp(pLookupParameter->GetName(), szName)) { if (bDelete) { delete pLookupParameter; erase(it); return; } pParameter = pLookupParameter; break; } } if (bDelete) { return; } if (!pParameter) { pParameter = new NZBParameter(szName); push_back(pParameter); } pParameter->SetValue(szValue); } NZBParameter* NZBParameterList::Find(const char* szName, bool bCaseSensitive) { for (iterator it = begin(); it != end(); it++) { NZBParameter* pParameter = *it; if ((bCaseSensitive && !strcmp(pParameter->GetName(), szName)) || (!bCaseSensitive && !strcasecmp(pParameter->GetName(), szName))) { return pParameter; } } return NULL; } void NZBParameterList::CopyFrom(NZBParameterList* pSourceParameters) { for (iterator it = pSourceParameters->begin(); it != pSourceParameters->end(); it++) { NZBParameter* pParameter = *it; SetParameter(pParameter->GetName(), pParameter->GetValue()); } } ScriptStatus::ScriptStatus(const char* szName, EStatus eStatus) { m_szName = strdup(szName); m_eStatus = eStatus; } ScriptStatus::~ScriptStatus() { free(m_szName); } ScriptStatusList::~ScriptStatusList() { Clear(); } void ScriptStatusList::Clear() { for (iterator it = begin(); it != end(); it++) { delete *it; } clear(); } void ScriptStatusList::Add(const char* szScriptName, ScriptStatus::EStatus eStatus) { push_back(new ScriptStatus(szScriptName, eStatus)); } ScriptStatus::EStatus ScriptStatusList::CalcTotalStatus() { ScriptStatus::EStatus eStatus = ScriptStatus::srNone; for (iterator it = begin(); it != end(); it++) { ScriptStatus* pScriptStatus = *it; // Failure-Status overrides Success-Status if ((pScriptStatus->GetStatus() == ScriptStatus::srSuccess && eStatus == ScriptStatus::srNone) || (pScriptStatus->GetStatus() == ScriptStatus::srFailure)) { eStatus = pScriptStatus->GetStatus(); } } return eStatus; } ServerStat::ServerStat(int iServerID) { m_iServerID = iServerID; m_iSuccessArticles = 0; m_iFailedArticles = 0; } ServerStatList::~ServerStatList() { Clear(); } void ServerStatList::Clear() { for (iterator it = begin(); it != end(); it++) { delete *it; } clear(); } void ServerStatList::SetStat(int iServerID, int iSuccessArticles, int iFailedArticles, bool bAdd) { ServerStat* pServerStat = NULL; for (iterator it = begin(); it != end(); it++) { ServerStat* pServerStat1 = *it; if (pServerStat1->GetServerID() == iServerID) { pServerStat = pServerStat1; break; } } if (!pServerStat) { pServerStat = new ServerStat(iServerID); push_back(pServerStat); } pServerStat->SetSuccessArticles((bAdd ? pServerStat->GetSuccessArticles() : 0) + iSuccessArticles); pServerStat->SetFailedArticles((bAdd ? pServerStat->GetFailedArticles() : 0) + iFailedArticles); } void ServerStatList::Add(ServerStatList* pServerStats) { for (iterator it = pServerStats->begin(); it != pServerStats->end(); it++) { ServerStat* pServerStat = *it; SetStat(pServerStat->GetServerID(), pServerStat->GetSuccessArticles(), pServerStat->GetFailedArticles(), true); } } NZBInfo::NZBInfo(bool bPersistent) { debug("Creating NZBInfo"); m_szFilename = NULL; m_szDestDir = NULL; m_szFinalDir = strdup(""); m_szCategory = strdup(""); m_szName = NULL; m_iFileCount = 0; m_iParkedFileCount = 0; m_lSize = 0; m_lSuccessSize = 0; m_lFailedSize = 0; m_lCurrentSuccessSize = 0; m_lCurrentFailedSize = 0; m_lParSize = 0; m_lParSuccessSize = 0; m_lParFailedSize = 0; m_lParCurrentSuccessSize = 0; m_lParCurrentFailedSize = 0; m_iTotalArticles = 0; m_iSuccessArticles = 0; m_iFailedArticles = 0; m_iRefCount = 0; m_bPostProcess = false; m_eRenameStatus = rsNone; m_eParStatus = psNone; m_eUnpackStatus = usNone; m_eCleanupStatus = csNone; m_eMoveStatus = msNone; m_eDeleteStatus = dsNone; m_eMarkStatus = ksNone; m_bDeleting = false; m_bDeletePaused = false; m_bManyDupeFiles = false; m_bAvoidHistory = false; m_bHealthPaused = false; m_bParCleanup = false; m_bCleanupDisk = false; m_bUnpackCleanedUpDisk = false; m_szQueuedFilename = strdup(""); m_szDupeKey = strdup(""); m_iDupeScore = 0; m_eDupeMode = dmScore; m_iFullContentHash = 0; m_iFilteredContentHash = 0; m_Owner = NULL; m_Messages.clear(); m_iIDMessageGen = 0; m_iID = bPersistent ? ++m_iIDGen : 0; } NZBInfo::~NZBInfo() { debug("Destroying NZBInfo"); free(m_szFilename); free(m_szDestDir); free(m_szFinalDir); free(m_szCategory); free(m_szName); free(m_szQueuedFilename); free(m_szDupeKey); ClearCompletedFiles(); for (Messages::iterator it = m_Messages.begin(); it != m_Messages.end(); it++) { delete *it; } m_Messages.clear(); if (m_Owner) { m_Owner->Remove(this); } } void NZBInfo::Retain() { m_iRefCount++; } void NZBInfo::Release() { m_iRefCount--; if (m_iRefCount <= 0) { delete this; } } void NZBInfo::SetID(int iID) { m_iID = iID; if (m_iIDMax < m_iID) { m_iIDMax = m_iID; } } void NZBInfo::ResetGenID(bool bMax) { if (bMax) { m_iIDGen = m_iIDMax; } else { m_iIDGen = 0; m_iIDMax = 0; } } void NZBInfo::ClearCompletedFiles() { for (Files::iterator it = m_completedFiles.begin(); it != m_completedFiles.end(); it++) { free(*it); } m_completedFiles.clear(); } void NZBInfo::SetDestDir(const char* szDestDir) { free(m_szDestDir); m_szDestDir = strdup(szDestDir); } void NZBInfo::SetFinalDir(const char* szFinalDir) { free(m_szFinalDir); m_szFinalDir = strdup(szFinalDir); } void NZBInfo::SetFilename(const char * szFilename) { free(m_szFilename); m_szFilename = strdup(szFilename); if (!m_szName) { char szNZBNicename[1024]; MakeNiceNZBName(m_szFilename, szNZBNicename, sizeof(szNZBNicename), true); szNZBNicename[1024-1] = '\0'; #ifdef WIN32 WebUtil::AnsiToUtf8(szNZBNicename, 1024); #endif SetName(szNZBNicename); } } void NZBInfo::SetName(const char* szName) { free(m_szName); m_szName = szName ? strdup(szName) : NULL; } void NZBInfo::SetCategory(const char* szCategory) { free(m_szCategory); m_szCategory = strdup(szCategory); } void NZBInfo::SetQueuedFilename(const char * szQueuedFilename) { free(m_szQueuedFilename); m_szQueuedFilename = strdup(szQueuedFilename); } void NZBInfo::SetDupeKey(const char* szDupeKey) { free(m_szDupeKey); m_szDupeKey = strdup(szDupeKey ? szDupeKey : ""); } void NZBInfo::MakeNiceNZBName(const char * szNZBFilename, char * szBuffer, int iSize, bool bRemoveExt) { char postname[1024]; const char* szBaseName = Util::BaseFileName(szNZBFilename); strncpy(postname, szBaseName, 1024); postname[1024-1] = '\0'; if (bRemoveExt) { // wipe out ".nzb" char* p = strrchr(postname, '.'); if (p && !strcasecmp(p, ".nzb")) *p = '\0'; } Util::MakeValidFilename(postname, '_', false); strncpy(szBuffer, postname, iSize); szBuffer[iSize-1] = '\0'; } void NZBInfo::BuildDestDirName() { char szDestDir[1024]; if (Util::EmptyStr(g_pOptions->GetInterDir())) { BuildFinalDirName(szDestDir, 1024); } else { snprintf(szDestDir, 1024, "%s%s.#%i", g_pOptions->GetInterDir(), GetName(), GetID()); szDestDir[1024-1] = '\0'; } #ifdef WIN32 WebUtil::Utf8ToAnsi(szDestDir, 1024); #endif SetDestDir(szDestDir); } void NZBInfo::BuildFinalDirName(char* szFinalDirBuf, int iBufSize) { char szBuffer[1024]; bool bUseCategory = m_szCategory && m_szCategory[0] != '\0'; snprintf(szFinalDirBuf, iBufSize, "%s", g_pOptions->GetDestDir()); szFinalDirBuf[iBufSize-1] = '\0'; if (bUseCategory) { Options::Category *pCategory = g_pOptions->FindCategory(m_szCategory, false); if (pCategory && pCategory->GetDestDir() && pCategory->GetDestDir()[0] != '\0') { snprintf(szFinalDirBuf, iBufSize, "%s", pCategory->GetDestDir()); szFinalDirBuf[iBufSize-1] = '\0'; bUseCategory = false; } } if (g_pOptions->GetAppendCategoryDir() && bUseCategory) { char szCategoryDir[1024]; strncpy(szCategoryDir, m_szCategory, 1024); szCategoryDir[1024 - 1] = '\0'; Util::MakeValidFilename(szCategoryDir, '_', true); snprintf(szBuffer, 1024, "%s%s%c", szFinalDirBuf, szCategoryDir, PATH_SEPARATOR); szBuffer[1024-1] = '\0'; strncpy(szFinalDirBuf, szBuffer, iBufSize); } snprintf(szBuffer, 1024, "%s%s", szFinalDirBuf, GetName()); szBuffer[1024-1] = '\0'; strncpy(szFinalDirBuf, szBuffer, iBufSize); #ifdef WIN32 WebUtil::Utf8ToAnsi(szFinalDirBuf, iBufSize); #endif } int NZBInfo::CalcHealth() { if (m_lCurrentFailedSize == 0 || m_lSize == m_lParSize) { return 1000; } int iHealth = (int)(Util::Int64ToFloat(m_lSize - m_lParSize - (m_lCurrentFailedSize - m_lParCurrentFailedSize)) * 1000.0 / Util::Int64ToFloat(m_lSize - m_lParSize)); if (iHealth == 1000 && m_lCurrentFailedSize - m_lParCurrentFailedSize > 0) { iHealth = 999; } return iHealth; } int NZBInfo::CalcCriticalHealth() { long long lGoodParSize = m_lParSize - m_lParCurrentFailedSize; int iCriticalHealth = (int)(Util::Int64ToFloat(m_lSize - lGoodParSize*2) * 1000.0 / Util::Int64ToFloat(m_lSize - lGoodParSize)); if (lGoodParSize*2 > m_lSize) { iCriticalHealth = 0; } else if (iCriticalHealth == 1000 && m_lParSize > 0) { iCriticalHealth = 999; } return iCriticalHealth; } NZBInfo::Messages* NZBInfo::LockMessages() { m_mutexLog.Lock(); return &m_Messages; } void NZBInfo::UnlockMessages() { m_mutexLog.Unlock(); } void NZBInfo::AppendMessage(Message::EKind eKind, time_t tTime, const char * szText) { if (tTime == 0) { tTime = time(NULL); } m_mutexLog.Lock(); Message* pMessage = new Message(++m_iIDMessageGen, eKind, tTime, szText); m_Messages.push_back(pMessage); m_mutexLog.Unlock(); } void NZBInfoList::Add(NZBInfo* pNZBInfo) { pNZBInfo->m_Owner = this; push_back(pNZBInfo); } void NZBInfoList::Remove(NZBInfo* pNZBInfo) { for (iterator it = begin(); it != end(); it++) { NZBInfo* pNZBInfo2 = *it; if (pNZBInfo2 == pNZBInfo) { erase(it); break; } } } void NZBInfoList::ReleaseAll() { int i = 0; for (iterator it = begin(); it != end(); ) { NZBInfo* pNZBInfo = *it; bool bObjDeleted = pNZBInfo->m_iRefCount == 1; pNZBInfo->Release(); if (bObjDeleted) { it = begin() + i; } else { it++; i++; } } } ArticleInfo::ArticleInfo() { //debug("Creating ArticleInfo"); m_szMessageID = NULL; m_iSize = 0; m_eStatus = aiUndefined; m_szResultFilename = NULL; } ArticleInfo::~ ArticleInfo() { //debug("Destroying ArticleInfo"); free(m_szMessageID); free(m_szResultFilename); } void ArticleInfo::SetMessageID(const char * szMessageID) { free(m_szMessageID); m_szMessageID = strdup(szMessageID); } void ArticleInfo::SetResultFilename(const char * v) { free(m_szResultFilename); m_szResultFilename = strdup(v); } FileInfo::FileInfo() { debug("Creating FileInfo"); m_Articles.clear(); m_Groups.clear(); m_szSubject = NULL; m_szFilename = NULL; m_szOutputFilename = NULL; m_pMutexOutputFile = NULL; m_bFilenameConfirmed = false; m_lSize = 0; m_lRemainingSize = 0; m_lMissedSize = 0; m_lSuccessSize = 0; m_lFailedSize = 0; m_iTotalArticles = 0; m_iMissedArticles = 0; m_iFailedArticles = 0; m_iSuccessArticles = 0; m_tTime = 0; m_bPaused = false; m_bDeleted = false; m_iCompleted = 0; m_bParFile = false; m_bOutputInitialized = false; m_pNZBInfo = NULL; m_iPriority = 0; m_bExtraPriority = false; m_iActiveDownloads = 0; m_bAutoDeleted = false; m_iID = ++m_iIDGen; } FileInfo::~ FileInfo() { debug("Destroying FileInfo"); free(m_szSubject); free(m_szFilename); free(m_szOutputFilename); delete m_pMutexOutputFile; for (Groups::iterator it = m_Groups.begin(); it != m_Groups.end() ;it++) { free(*it); } m_Groups.clear(); ClearArticles(); if (m_pNZBInfo) { m_pNZBInfo->Release(); } } void FileInfo::ClearArticles() { for (Articles::iterator it = m_Articles.begin(); it != m_Articles.end() ;it++) { delete *it; } m_Articles.clear(); } void FileInfo::SetID(int iID) { m_iID = iID; if (m_iIDMax < m_iID) { m_iIDMax = m_iID; } } void FileInfo::ResetGenID(bool bMax) { if (bMax) { m_iIDGen = m_iIDMax; } else { m_iIDGen = 0; m_iIDMax = 0; } } void FileInfo::SetNZBInfo(NZBInfo* pNZBInfo) { if (m_pNZBInfo) { m_pNZBInfo->Release(); } m_pNZBInfo = pNZBInfo; m_pNZBInfo->Retain(); } void FileInfo::SetSubject(const char* szSubject) { m_szSubject = strdup(szSubject); } void FileInfo::SetFilename(const char* szFilename) { free(m_szFilename); m_szFilename = strdup(szFilename); } void FileInfo::MakeValidFilename() { Util::MakeValidFilename(m_szFilename, '_', false); } void FileInfo::LockOutputFile() { m_pMutexOutputFile->Lock(); } void FileInfo::UnlockOutputFile() { m_pMutexOutputFile->Unlock(); } void FileInfo::SetOutputFilename(const char* szOutputFilename) { free(m_szOutputFilename); m_szOutputFilename = strdup(szOutputFilename); } void FileInfo::SetActiveDownloads(int iActiveDownloads) { m_iActiveDownloads = iActiveDownloads; if (m_iActiveDownloads > 0 && !m_pMutexOutputFile) { m_pMutexOutputFile = new Mutex(); } else if (m_iActiveDownloads == 0 && m_pMutexOutputFile) { delete m_pMutexOutputFile; m_pMutexOutputFile = NULL; } } GroupInfo::GroupInfo() { m_iFirstID = 0; m_iLastID = 0; m_iRemainingFileCount = 0; m_iPausedFileCount = 0; m_lRemainingSize = 0; m_lPausedSize = 0; m_iRemainingParCount = 0; m_tMinTime = 0; m_tMaxTime = 0; m_iMinPriority = 0; m_iMaxPriority = 0; m_iActiveDownloads = 0; } GroupInfo::~GroupInfo() { if (m_pNZBInfo) { m_pNZBInfo->Release(); } } GroupQueue::~GroupQueue() { Clear(); } void GroupQueue::Clear() { for (iterator it = begin(); it != end(); it++) { delete *it; } clear(); } PostInfo::PostInfo() { debug("Creating PostInfo"); m_pNZBInfo = NULL; m_szInfoName = NULL; m_bWorking = false; m_bDeleted = false; m_bRequestParCheck = false; m_szProgressLabel = strdup(""); m_iFileProgress = 0; m_iStageProgress = 0; m_tStartTime = 0; m_tStageTime = 0; m_eStage = ptQueued; m_pPostThread = NULL; m_Messages.clear(); m_iIDMessageGen = 0; m_iID = ++m_iIDGen; } PostInfo::~ PostInfo() { debug("Destroying PostInfo"); free(m_szInfoName); free(m_szProgressLabel); for (Messages::iterator it = m_Messages.begin(); it != m_Messages.end(); it++) { delete *it; } m_Messages.clear(); if (m_pNZBInfo) { m_pNZBInfo->Release(); } } void PostInfo::SetNZBInfo(NZBInfo* pNZBInfo) { if (m_pNZBInfo) { m_pNZBInfo->Release(); } m_pNZBInfo = pNZBInfo; m_pNZBInfo->Retain(); } void PostInfo::SetInfoName(const char* szInfoName) { m_szInfoName = strdup(szInfoName); } void PostInfo::SetProgressLabel(const char* szProgressLabel) { free(m_szProgressLabel); m_szProgressLabel = strdup(szProgressLabel); } PostInfo::Messages* PostInfo::LockMessages() { m_mutexLog.Lock(); return &m_Messages; } void PostInfo::UnlockMessages() { m_mutexLog.Unlock(); } void PostInfo::AppendMessage(Message::EKind eKind, const char * szText) { m_mutexLog.Lock(); Message* pMessage = new Message(++m_iIDMessageGen, eKind, time(NULL), szText); m_Messages.push_back(pMessage); while (m_Messages.size() > (unsigned int)g_pOptions->GetLogBufferSize()) { Message* pMessage = m_Messages.front(); delete pMessage; m_Messages.pop_front(); } m_mutexLog.Unlock(); } void DownloadQueue::BuildGroups(GroupQueue* pGroupQueue) { std::map groupMap; for (FileQueue::iterator it = GetFileQueue()->begin(); it != GetFileQueue()->end(); it++) { FileInfo* pFileInfo = *it; GroupInfo *&pGroupInfo = groupMap[pFileInfo->GetNZBInfo()->GetID()]; if (!pGroupInfo) { pGroupInfo = new GroupInfo(); pGroupInfo->m_pNZBInfo = pFileInfo->GetNZBInfo(); pGroupInfo->m_pNZBInfo->Retain(); pGroupInfo->m_iFirstID = pFileInfo->GetID(); pGroupInfo->m_iLastID = pFileInfo->GetID(); pGroupInfo->m_tMinTime = pFileInfo->GetTime(); pGroupInfo->m_tMaxTime = pFileInfo->GetTime(); pGroupInfo->m_iMinPriority = pFileInfo->GetPriority(); pGroupInfo->m_iMaxPriority = pFileInfo->GetPriority(); pGroupQueue->push_back(pGroupInfo); } if (pFileInfo->GetID() < pGroupInfo->GetFirstID()) { pGroupInfo->m_iFirstID = pFileInfo->GetID(); } if (pFileInfo->GetID() > pGroupInfo->GetLastID()) { pGroupInfo->m_iLastID = pFileInfo->GetID(); } if (pFileInfo->GetTime() > 0) { if (pFileInfo->GetTime() < pGroupInfo->GetMinTime()) { pGroupInfo->m_tMinTime = pFileInfo->GetTime(); } if (pFileInfo->GetTime() > pGroupInfo->GetMaxTime()) { pGroupInfo->m_tMaxTime = pFileInfo->GetTime(); } } if (pFileInfo->GetPriority() < pGroupInfo->GetMinPriority()) { pGroupInfo->m_iMinPriority = pFileInfo->GetPriority(); } if (pFileInfo->GetPriority() > pGroupInfo->GetMaxPriority()) { pGroupInfo->m_iMaxPriority = pFileInfo->GetPriority(); } pGroupInfo->m_iActiveDownloads += pFileInfo->GetActiveDownloads(); pGroupInfo->m_iRemainingFileCount++; pGroupInfo->m_lRemainingSize += pFileInfo->GetRemainingSize(); if (pFileInfo->GetPaused()) { pGroupInfo->m_lPausedSize += pFileInfo->GetRemainingSize(); pGroupInfo->m_iPausedFileCount++; } 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_iRemainingParCount++; } } } UrlInfo::UrlInfo() { //debug("Creating UrlInfo"); m_szURL = NULL; m_szNZBFilename = strdup(""); m_szCategory = strdup(""); m_iPriority = 0; m_iDupeScore = 0; m_szDupeKey = strdup(""); m_eDupeMode = dmScore; m_bAddTop = false; m_bAddPaused = false; m_bForce = false; m_eStatus = aiUndefined; m_iID = ++m_iIDGen; } UrlInfo::~ UrlInfo() { free(m_szURL); free(m_szNZBFilename); free(m_szCategory); free(m_szDupeKey); } void UrlInfo::SetURL(const char* szURL) { free(m_szURL); m_szURL = strdup(szURL); } void UrlInfo::SetID(int iID) { m_iID = iID; if (m_iIDMax < m_iID) { m_iIDMax = m_iID; } } void UrlInfo::ResetGenID(bool bMax) { if (bMax) { m_iIDGen = m_iIDMax; } else { m_iIDGen = 0; m_iIDMax = 0; } } void UrlInfo::SetNZBFilename(const char* szNZBFilename) { free(m_szNZBFilename); m_szNZBFilename = strdup(szNZBFilename); } void UrlInfo::SetCategory(const char* szCategory) { free(m_szCategory); m_szCategory = strdup(szCategory); } void UrlInfo::SetDupeKey(const char* szDupeKey) { free(m_szDupeKey); m_szDupeKey = strdup(szDupeKey); } void UrlInfo::GetName(char* szBuffer, int iSize) { MakeNiceName(m_szURL, m_szNZBFilename, szBuffer, iSize); } void UrlInfo::MakeNiceName(const char* szURL, const char* szNZBFilename, char* szBuffer, int iSize) { URL url(szURL); if (strlen(szNZBFilename) > 0) { char szNZBNicename[1024]; NZBInfo::MakeNiceNZBName(szNZBFilename, szNZBNicename, sizeof(szNZBNicename), true); snprintf(szBuffer, iSize, "%s @ %s", szNZBNicename, url.GetHost()); } else { snprintf(szBuffer, iSize, "%s%s", url.GetHost(), url.GetResource()); } szBuffer[iSize-1] = '\0'; } DupInfo::DupInfo() { m_szName = NULL; m_szDupeKey = NULL; m_iDupeScore = 0; m_eDupeMode = dmScore; m_lSize = 0; m_iFullContentHash = 0; m_iFilteredContentHash = 0; m_eStatus = dsUndefined; } DupInfo::~DupInfo() { free(m_szName); free(m_szDupeKey); } void DupInfo::SetName(const char* szName) { free(m_szName); m_szName = strdup(szName); } void DupInfo::SetDupeKey(const char* szDupeKey) { free(m_szDupeKey); m_szDupeKey = strdup(szDupeKey); } HistoryInfo::HistoryInfo(NZBInfo* pNZBInfo) { m_eKind = hkNZBInfo; m_pInfo = pNZBInfo; pNZBInfo->Retain(); m_tTime = 0; m_iID = ++m_iIDGen; } HistoryInfo::HistoryInfo(UrlInfo* pUrlInfo) { m_eKind = hkUrlInfo; m_pInfo = pUrlInfo; m_tTime = 0; m_iID = ++m_iIDGen; } HistoryInfo::HistoryInfo(DupInfo* pDupInfo) { m_eKind = hkDupInfo; m_pInfo = pDupInfo; m_tTime = 0; m_iID = ++m_iIDGen; } HistoryInfo::~HistoryInfo() { if (m_eKind == hkNZBInfo && m_pInfo) { ((NZBInfo*)m_pInfo)->Release(); } else if (m_eKind == hkUrlInfo && m_pInfo) { delete (UrlInfo*)m_pInfo; } else if (m_eKind == hkDupInfo && m_pInfo) { delete (DupInfo*)m_pInfo; } } void HistoryInfo::SetID(int iID) { m_iID = iID; if (m_iIDMax < m_iID) { m_iIDMax = m_iID; } } void HistoryInfo::ResetGenID(bool bMax) { if (bMax) { m_iIDGen = m_iIDMax; } else { m_iIDGen = 0; m_iIDMax = 0; } } void HistoryInfo::GetName(char* szBuffer, int iSize) { if (m_eKind == hkNZBInfo) { strncpy(szBuffer, GetNZBInfo()->GetName(), iSize); szBuffer[iSize-1] = '\0'; } else if (m_eKind == hkUrlInfo) { GetUrlInfo()->GetName(szBuffer, iSize); } else if (m_eKind == hkDupInfo) { strncpy(szBuffer, GetDupInfo()->GetName(), iSize); szBuffer[iSize-1] = '\0'; } else { strncpy(szBuffer, "", iSize); } } nzbget-12.0+dfsg/DownloadInfo.h000066400000000000000000000654251226450633000164150ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 921 $ * $Date: 2013-12-18 21:19:42 +0100 (Wed, 18 Dec 2013) $ * */ #ifndef DOWNLOADINFO_H #define DOWNLOADINFO_H #include #include #include #include "Log.h" #include "Thread.h" class NZBInfo; class DownloadQueue; class ArticleInfo { public: enum EStatus { aiUndefined, aiRunning, aiFinished, aiFailed }; private: int m_iPartNumber; char* m_szMessageID; int m_iSize; EStatus m_eStatus; char* m_szResultFilename; public: ArticleInfo(); ~ArticleInfo(); void SetPartNumber(int s) { m_iPartNumber = s; } int GetPartNumber() { return m_iPartNumber; } const char* GetMessageID() { return m_szMessageID; } void SetMessageID(const char* szMessageID); void SetSize(int s) { m_iSize = s; } int GetSize() { return m_iSize; } EStatus GetStatus() { return m_eStatus; } void SetStatus(EStatus Status) { m_eStatus = Status; } const char* GetResultFilename() { return m_szResultFilename; } void SetResultFilename(const char* v); }; class FileInfo { public: typedef std::vector Articles; typedef std::vector Groups; private: int m_iID; NZBInfo* m_pNZBInfo; Articles m_Articles; Groups m_Groups; char* m_szSubject; char* m_szFilename; long long m_lSize; long long m_lRemainingSize; long long m_lSuccessSize; long long m_lFailedSize; long long m_lMissedSize; int m_iTotalArticles; int m_iMissedArticles; int m_iFailedArticles; int m_iSuccessArticles; time_t m_tTime; bool m_bPaused; bool m_bDeleted; bool m_bFilenameConfirmed; bool m_bParFile; int m_iCompleted; bool m_bOutputInitialized; char* m_szOutputFilename; Mutex* m_pMutexOutputFile; int m_iPriority; bool m_bExtraPriority; int m_iActiveDownloads; bool m_bAutoDeleted; static int m_iIDGen; static int m_iIDMax; public: FileInfo(); ~FileInfo(); int GetID() { return m_iID; } void SetID(int iID); static void ResetGenID(bool bMax); NZBInfo* GetNZBInfo() { return m_pNZBInfo; } void SetNZBInfo(NZBInfo* pNZBInfo); Articles* GetArticles() { return &m_Articles; } Groups* GetGroups() { return &m_Groups; } const char* GetSubject() { return m_szSubject; } void SetSubject(const char* szSubject); const char* GetFilename() { return m_szFilename; } void SetFilename(const char* szFilename); void MakeValidFilename(); bool GetFilenameConfirmed() { return m_bFilenameConfirmed; } void SetFilenameConfirmed(bool bFilenameConfirmed) { m_bFilenameConfirmed = bFilenameConfirmed; } void SetSize(long long lSize) { m_lSize = lSize; m_lRemainingSize = lSize; } long long GetSize() { return m_lSize; } long long GetRemainingSize() { return m_lRemainingSize; } void SetRemainingSize(long long lRemainingSize) { m_lRemainingSize = lRemainingSize; } long long GetMissedSize() { return m_lMissedSize; } void SetMissedSize(long long lMissedSize) { m_lMissedSize = lMissedSize; } long long GetSuccessSize() { return m_lSuccessSize; } void SetSuccessSize(long long lSuccessSize) { m_lSuccessSize = lSuccessSize; } long long GetFailedSize() { return m_lFailedSize; } void SetFailedSize(long long lFailedSize) { m_lFailedSize = lFailedSize; } int GetTotalArticles() { return m_iTotalArticles; } void SetTotalArticles(int iTotalArticles) { m_iTotalArticles = iTotalArticles; } int GetMissedArticles() { return m_iMissedArticles; } void SetMissedArticles(int iMissedArticles) { m_iMissedArticles = iMissedArticles; } int GetFailedArticles() { return m_iFailedArticles; } void SetFailedArticles(int iFailedArticles) { m_iFailedArticles = iFailedArticles; } int GetSuccessArticles() { return m_iSuccessArticles; } void SetSuccessArticles(int iSuccessArticles) { m_iSuccessArticles = iSuccessArticles; } time_t GetTime() { return m_tTime; } void SetTime(time_t tTime) { m_tTime = tTime; } bool GetPaused() { return m_bPaused; } void SetPaused(bool Paused) { m_bPaused = Paused; } bool GetDeleted() { return m_bDeleted; } void SetDeleted(bool Deleted) { m_bDeleted = Deleted; } int GetCompleted() { return m_iCompleted; } void SetCompleted(int iCompleted) { m_iCompleted = iCompleted; } bool GetParFile() { return m_bParFile; } void SetParFile(bool bParFile) { m_bParFile = bParFile; } void ClearArticles(); void LockOutputFile(); void UnlockOutputFile(); const char* GetOutputFilename() { return m_szOutputFilename; } void SetOutputFilename(const char* szOutputFilename); bool GetOutputInitialized() { return m_bOutputInitialized; } void SetOutputInitialized(bool bOutputInitialized) { m_bOutputInitialized = bOutputInitialized; } int GetPriority() { return m_iPriority; } void SetPriority(int iPriority) { m_iPriority = iPriority; } bool GetExtraPriority() { return m_bExtraPriority; } void SetExtraPriority(bool bExtraPriority) { m_bExtraPriority = bExtraPriority; }; int GetActiveDownloads() { return m_iActiveDownloads; } void SetActiveDownloads(int iActiveDownloads); bool GetAutoDeleted() { return m_bAutoDeleted; } void SetAutoDeleted(bool bAutoDeleted) { m_bAutoDeleted = bAutoDeleted; } }; typedef std::deque FileQueue; class GroupInfo { private: NZBInfo* m_pNZBInfo; int m_iFirstID; int m_iLastID; int m_iRemainingFileCount; int m_iPausedFileCount; long long m_lRemainingSize; long long m_lPausedSize; int m_iRemainingParCount; time_t m_tMinTime; time_t m_tMaxTime; int m_iMinPriority; int m_iMaxPriority; int m_iActiveDownloads; friend class DownloadQueue; public: GroupInfo(); ~GroupInfo(); NZBInfo* GetNZBInfo() { return m_pNZBInfo; } int GetFirstID() { return m_iFirstID; } int GetLastID() { return m_iLastID; } long long GetRemainingSize() { return m_lRemainingSize; } long long GetPausedSize() { return m_lPausedSize; } int GetRemainingFileCount() { return m_iRemainingFileCount; } int GetPausedFileCount() { return m_iPausedFileCount; } int GetRemainingParCount() { return m_iRemainingParCount; } time_t GetMinTime() { return m_tMinTime; } time_t GetMaxTime() { return m_tMaxTime; } int GetMinPriority() { return m_iMinPriority; } int GetMaxPriority() { return m_iMaxPriority; } int GetActiveDownloads() { return m_iActiveDownloads; } }; typedef std::deque GroupQueueBase; class GroupQueue : public GroupQueueBase { public: ~GroupQueue(); void Clear(); }; class NZBParameter { private: char* m_szName; char* m_szValue; void SetValue(const char* szValue); friend class NZBParameterList; public: NZBParameter(const char* szName); ~NZBParameter(); const char* GetName() { return m_szName; } const char* GetValue() { return m_szValue; } }; typedef std::deque NZBParameterListBase; class NZBParameterList : public NZBParameterListBase { public: ~NZBParameterList(); void SetParameter(const char* szName, const char* szValue); NZBParameter* Find(const char* szName, bool bCaseSensitive); void Clear(); void CopyFrom(NZBParameterList* pSourceParameters); }; class ScriptStatus { public: enum EStatus { srNone, srFailure, srSuccess }; private: char* m_szName; EStatus m_eStatus; friend class ScriptStatusList; public: ScriptStatus(const char* szName, EStatus eStatus); ~ScriptStatus(); const char* GetName() { return m_szName; } EStatus GetStatus() { return m_eStatus; } }; typedef std::deque ScriptStatusListBase; class ScriptStatusList : public ScriptStatusListBase { public: ~ScriptStatusList(); void Add(const char* szScriptName, ScriptStatus::EStatus eStatus); void Clear(); ScriptStatus::EStatus CalcTotalStatus(); }; class ServerStat { private: int m_iServerID; int m_iSuccessArticles; int m_iFailedArticles; public: ServerStat(int iServerID); int GetServerID() { return m_iServerID; } int GetSuccessArticles() { return m_iSuccessArticles; } void SetSuccessArticles(int iSuccessArticles) { m_iSuccessArticles = iSuccessArticles; } int GetFailedArticles() { return m_iFailedArticles; } void SetFailedArticles(int iFailedArticles) { m_iFailedArticles = iFailedArticles; } }; typedef std::vector ServerStatListBase; class ServerStatList : public ServerStatListBase { public: ~ServerStatList(); void SetStat(int iServerID, int iSuccessArticles, int iFailedArticles, bool bAdd); void Add(ServerStatList* pServerStats); void Clear(); }; enum EDupeMode { dmScore, dmAll, dmForce }; class NZBInfoList; class NZBInfo { public: enum ERenameStatus { rsNone, rsSkipped, rsFailure, rsSuccess }; enum EParStatus { psNone, psSkipped, psFailure, psSuccess, psRepairPossible, psManual }; enum EUnpackStatus { usNone, usSkipped, usFailure, usSuccess, usSpace, usPassword }; enum ECleanupStatus { csNone, csFailure, csSuccess }; enum EMoveStatus { msNone, msFailure, msSuccess }; enum EDeleteStatus { dsNone, dsManual, dsHealth, dsDupe }; enum EMarkStatus { ksNone, ksBad, ksGood }; typedef std::vector Files; typedef std::deque Messages; private: int m_iID; int m_iRefCount; char* m_szFilename; char* m_szName; char* m_szDestDir; char* m_szFinalDir; char* m_szCategory; int m_iFileCount; int m_iParkedFileCount; long long m_lSize; long long m_lSuccessSize; long long m_lFailedSize; long long m_lCurrentSuccessSize; long long m_lCurrentFailedSize; long long m_lParSize; long long m_lParSuccessSize; long long m_lParFailedSize; long long m_lParCurrentSuccessSize; long long m_lParCurrentFailedSize; int m_iTotalArticles; int m_iSuccessArticles; int m_iFailedArticles; Files m_completedFiles; bool m_bPostProcess; ERenameStatus m_eRenameStatus; EParStatus m_eParStatus; EUnpackStatus m_eUnpackStatus; ECleanupStatus m_eCleanupStatus; EMoveStatus m_eMoveStatus; EDeleteStatus m_eDeleteStatus; EMarkStatus m_eMarkStatus; bool m_bDeletePaused; bool m_bManyDupeFiles; char* m_szQueuedFilename; bool m_bDeleting; bool m_bAvoidHistory; bool m_bHealthPaused; bool m_bParCleanup; bool m_bParManual; bool m_bCleanupDisk; bool m_bUnpackCleanedUpDisk; char* m_szDupeKey; int m_iDupeScore; EDupeMode m_eDupeMode; unsigned int m_iFullContentHash; unsigned int m_iFilteredContentHash; NZBInfoList* m_Owner; NZBParameterList m_ppParameters; ScriptStatusList m_scriptStatuses; ServerStatList m_ServerStats; Mutex m_mutexLog; Messages m_Messages; int m_iIDMessageGen; static int m_iIDGen; static int m_iIDMax; friend class NZBInfoList; public: NZBInfo(bool bPersistent = true); ~NZBInfo(); void Retain(); void Release(); int GetID() { return m_iID; } void SetID(int iID); static void ResetGenID(bool bMax); const char* GetFilename() { return m_szFilename; } void SetFilename(const char* szFilename); static void MakeNiceNZBName(const char* szNZBFilename, char* szBuffer, int iSize, bool bRemoveExt); const char* GetDestDir() { return m_szDestDir; } // needs locking (for shared objects) void SetDestDir(const char* szDestDir); // needs locking (for shared objects) const char* GetFinalDir() { return m_szFinalDir; } // needs locking (for shared objects) void SetFinalDir(const char* szFinalDir); // needs locking (for shared objects) const char* GetCategory() { return m_szCategory; } // needs locking (for shared objects) void SetCategory(const char* szCategory); // needs locking (for shared objects) const char* GetName() { return m_szName; } // needs locking (for shared objects) void SetName(const char* szName); // needs locking (for shared objects) int GetFileCount() { return m_iFileCount; } void SetFileCount(int iFileCount) { m_iFileCount = iFileCount; } int GetParkedFileCount() { return m_iParkedFileCount; } void SetParkedFileCount(int iParkedFileCount) { m_iParkedFileCount = iParkedFileCount; } long long GetSize() { return m_lSize; } void SetSize(long long lSize) { m_lSize = lSize; } long long GetSuccessSize() { return m_lSuccessSize; } void SetSuccessSize(long long lSuccessSize) { m_lSuccessSize = lSuccessSize; } long long GetFailedSize() { return m_lFailedSize; } void SetFailedSize(long long lFailedSize) { m_lFailedSize = lFailedSize; } long long GetCurrentSuccessSize() { return m_lCurrentSuccessSize; } void SetCurrentSuccessSize(long long lCurrentSuccessSize) { m_lCurrentSuccessSize = lCurrentSuccessSize; } long long GetCurrentFailedSize() { return m_lCurrentFailedSize; } void SetCurrentFailedSize(long long lCurrentFailedSize) { m_lCurrentFailedSize = lCurrentFailedSize; } long long GetParSize() { return m_lParSize; } void SetParSize(long long lParSize) { m_lParSize = lParSize; } long long GetParSuccessSize() { return m_lParSuccessSize; } void SetParSuccessSize(long long lParSuccessSize) { m_lParSuccessSize = lParSuccessSize; } long long GetParFailedSize() { return m_lParFailedSize; } void SetParFailedSize(long long lParFailedSize) { m_lParFailedSize = lParFailedSize; } long long GetParCurrentSuccessSize() { return m_lParCurrentSuccessSize; } void SetParCurrentSuccessSize(long long lParCurrentSuccessSize) { m_lParCurrentSuccessSize = lParCurrentSuccessSize; } long long GetParCurrentFailedSize() { return m_lParCurrentFailedSize; } void SetParCurrentFailedSize(long long lParCurrentFailedSize) { m_lParCurrentFailedSize = lParCurrentFailedSize; } int GetTotalArticles() { return m_iTotalArticles; } void SetTotalArticles(int iTotalArticles) { m_iTotalArticles = iTotalArticles; } int GetSuccessArticles() { return m_iSuccessArticles; } void SetSuccessArticles(int iSuccessArticles) { m_iSuccessArticles = iSuccessArticles; } int GetFailedArticles() { return m_iFailedArticles; } void SetFailedArticles(int iFailedArticles) { m_iFailedArticles = iFailedArticles; } void BuildDestDirName(); void BuildFinalDirName(char* szFinalDirBuf, int iBufSize); Files* GetCompletedFiles() { return &m_completedFiles; } // needs locking (for shared objects) void ClearCompletedFiles(); bool GetPostProcess() { return m_bPostProcess; } void SetPostProcess(bool bPostProcess) { m_bPostProcess = bPostProcess; } ERenameStatus GetRenameStatus() { return m_eRenameStatus; } void SetRenameStatus(ERenameStatus eRenameStatus) { m_eRenameStatus = eRenameStatus; } EParStatus GetParStatus() { return m_eParStatus; } void SetParStatus(EParStatus eParStatus) { m_eParStatus = eParStatus; } EUnpackStatus GetUnpackStatus() { return m_eUnpackStatus; } void SetUnpackStatus(EUnpackStatus eUnpackStatus) { m_eUnpackStatus = eUnpackStatus; } ECleanupStatus GetCleanupStatus() { return m_eCleanupStatus; } void SetCleanupStatus(ECleanupStatus eCleanupStatus) { m_eCleanupStatus = eCleanupStatus; } EMoveStatus GetMoveStatus() { return m_eMoveStatus; } void SetMoveStatus(EMoveStatus eMoveStatus) { m_eMoveStatus = eMoveStatus; } EDeleteStatus GetDeleteStatus() { return m_eDeleteStatus; } void SetDeleteStatus(EDeleteStatus eDeleteStatus) { m_eDeleteStatus = eDeleteStatus; } EMarkStatus GetMarkStatus() { return m_eMarkStatus; } void SetMarkStatus(EMarkStatus eMarkStatus) { m_eMarkStatus = eMarkStatus; } const char* GetQueuedFilename() { return m_szQueuedFilename; } void SetQueuedFilename(const char* szQueuedFilename); bool GetDeleting() { return m_bDeleting; } void SetDeleting(bool bDeleting) { m_bDeleting = bDeleting; } bool GetDeletePaused() { return m_bDeletePaused; } void SetDeletePaused(bool bDeletePaused) { m_bDeletePaused = bDeletePaused; } bool GetManyDupeFiles() { return m_bManyDupeFiles; } void SetManyDupeFiles(bool bManyDupeFiles) { m_bManyDupeFiles = bManyDupeFiles; } bool GetAvoidHistory() { return m_bAvoidHistory; } void SetAvoidHistory(bool bAvoidHistory) { m_bAvoidHistory = bAvoidHistory; } bool GetHealthPaused() { return m_bHealthPaused; } void SetHealthPaused(bool bHealthPaused) { m_bHealthPaused = bHealthPaused; } bool GetParCleanup() { return m_bParCleanup; } void SetParCleanup(bool bParCleanup) { m_bParCleanup = bParCleanup; } bool GetCleanupDisk() { return m_bCleanupDisk; } void SetCleanupDisk(bool bCleanupDisk) { m_bCleanupDisk = bCleanupDisk; } bool GetUnpackCleanedUpDisk() { return m_bUnpackCleanedUpDisk; } void SetUnpackCleanedUpDisk(bool bUnpackCleanedUpDisk) { m_bUnpackCleanedUpDisk = bUnpackCleanedUpDisk; } NZBParameterList* GetParameters() { return &m_ppParameters; } // needs locking (for shared objects) ScriptStatusList* GetScriptStatuses() { return &m_scriptStatuses; } // needs locking (for shared objects) ServerStatList* GetServerStats() { return &m_ServerStats; } int CalcHealth(); int CalcCriticalHealth(); const char* GetDupeKey() { return m_szDupeKey; } // needs locking (for shared objects) void SetDupeKey(const char* szDupeKey); // needs locking (for shared objects) int GetDupeScore() { return m_iDupeScore; } void SetDupeScore(int iDupeScore) { m_iDupeScore = iDupeScore; } EDupeMode GetDupeMode() { return m_eDupeMode; } void SetDupeMode(EDupeMode eDupeMode) { m_eDupeMode = eDupeMode; } unsigned int GetFullContentHash() { return m_iFullContentHash; } void SetFullContentHash(unsigned int iFullContentHash) { m_iFullContentHash = iFullContentHash; } unsigned int GetFilteredContentHash() { return m_iFilteredContentHash; } void SetFilteredContentHash(unsigned int iFilteredContentHash) { m_iFilteredContentHash = iFilteredContentHash; } void AppendMessage(Message::EKind eKind, time_t tTime, const char* szText); Messages* LockMessages(); void UnlockMessages(); }; typedef std::deque NZBInfoListBase; class NZBInfoList : public NZBInfoListBase { public: void Add(NZBInfo* pNZBInfo); void Remove(NZBInfo* pNZBInfo); void ReleaseAll(); }; class PostInfo { public: enum EStage { ptQueued, ptLoadingPars, ptVerifyingSources, ptRepairing, ptVerifyingRepaired, ptRenaming, ptUnpacking, ptMoving, ptExecutingScript, ptFinished }; typedef std::deque Messages; private: int m_iID; NZBInfo* m_pNZBInfo; char* m_szInfoName; bool m_bWorking; bool m_bDeleted; bool m_bRequestParCheck; EStage m_eStage; char* m_szProgressLabel; int m_iFileProgress; int m_iStageProgress; time_t m_tStartTime; time_t m_tStageTime; Thread* m_pPostThread; Mutex m_mutexLog; Messages m_Messages; int m_iIDMessageGen; static int m_iIDGen; static int m_iIDMax; public: PostInfo(); ~PostInfo(); int GetID() { return m_iID; } NZBInfo* GetNZBInfo() { return m_pNZBInfo; } void SetNZBInfo(NZBInfo* pNZBInfo); const char* GetInfoName() { return m_szInfoName; } void SetInfoName(const char* szInfoName); EStage GetStage() { return m_eStage; } void SetStage(EStage eStage) { m_eStage = eStage; } void SetProgressLabel(const char* szProgressLabel); const char* GetProgressLabel() { return m_szProgressLabel; } int GetFileProgress() { return m_iFileProgress; } void SetFileProgress(int iFileProgress) { m_iFileProgress = iFileProgress; } int GetStageProgress() { return m_iStageProgress; } void SetStageProgress(int iStageProgress) { m_iStageProgress = iStageProgress; } time_t GetStartTime() { return m_tStartTime; } void SetStartTime(time_t tStartTime) { m_tStartTime = tStartTime; } time_t GetStageTime() { return m_tStageTime; } void SetStageTime(time_t tStageTime) { m_tStageTime = tStageTime; } bool GetWorking() { return m_bWorking; } void SetWorking(bool bWorking) { m_bWorking = bWorking; } bool GetDeleted() { return m_bDeleted; } void SetDeleted(bool bDeleted) { m_bDeleted = bDeleted; } bool GetRequestParCheck() { return m_bRequestParCheck; } void SetRequestParCheck(bool bRequestParCheck) { m_bRequestParCheck = bRequestParCheck; } void AppendMessage(Message::EKind eKind, const char* szText); Thread* GetPostThread() { return m_pPostThread; } void SetPostThread(Thread* pPostThread) { m_pPostThread = pPostThread; } Messages* LockMessages(); void UnlockMessages(); }; typedef std::deque PostQueue; typedef std::vector IDList; typedef std::vector NameList; class UrlInfo { public: enum EStatus { aiUndefined, aiRunning, aiFinished, aiFailed, aiRetry, aiScanSkipped, aiScanFailed }; private: int m_iID; char* m_szURL; char* m_szNZBFilename; char* m_szCategory; int m_iPriority; char* m_szDupeKey; int m_iDupeScore; EDupeMode m_eDupeMode; bool m_bAddTop; bool m_bAddPaused; bool m_bForce; EStatus m_eStatus; static int m_iIDGen; static int m_iIDMax; public: UrlInfo(); ~UrlInfo(); int GetID() { return m_iID; } void SetID(int iID); static void ResetGenID(bool bMax); const char* GetURL() { return m_szURL; } // needs locking (for shared objects) void SetURL(const char* szURL); // needs locking (for shared objects) const char* GetNZBFilename() { return m_szNZBFilename; } // needs locking (for shared objects) void SetNZBFilename(const char* szNZBFilename); // needs locking (for shared objects) const char* GetCategory() { return m_szCategory; } // needs locking (for shared objects) void SetCategory(const char* szCategory); // needs locking (for shared objects) int GetPriority() { return m_iPriority; } void SetPriority(int iPriority) { m_iPriority = iPriority; } const char* GetDupeKey() { return m_szDupeKey; } void SetDupeKey(const char* szDupeKey); int GetDupeScore() { return m_iDupeScore; } void SetDupeScore(int iDupeScore) { m_iDupeScore = iDupeScore; } EDupeMode GetDupeMode() { return m_eDupeMode; } void SetDupeMode(EDupeMode eDupeMode) { m_eDupeMode = eDupeMode; } bool GetAddTop() { return m_bAddTop; } void SetAddTop(bool bAddTop) { m_bAddTop = bAddTop; } bool GetAddPaused() { return m_bAddPaused; } void SetAddPaused(bool bAddPaused) { m_bAddPaused = bAddPaused; } void GetName(char* szBuffer, int iSize); // needs locking (for shared objects) static void MakeNiceName(const char* szURL, const char* szNZBFilename, char* szBuffer, int iSize); bool GetForce() { return m_bForce; } void SetForce(bool bForce) { m_bForce = bForce; } EStatus GetStatus() { return m_eStatus; } void SetStatus(EStatus Status) { m_eStatus = Status; } }; typedef std::deque UrlQueue; class DupInfo { public: enum EStatus { dsUndefined, dsSuccess, dsFailed, dsDeleted, dsDupe, dsBad, dsGood }; private: char* m_szName; char* m_szDupeKey; int m_iDupeScore; EDupeMode m_eDupeMode; long long m_lSize; unsigned int m_iFullContentHash; unsigned int m_iFilteredContentHash; EStatus m_eStatus; public: DupInfo(); ~DupInfo(); const char* GetName() { return m_szName; } // needs locking (for shared objects) void SetName(const char* szName); // needs locking (for shared objects) const char* GetDupeKey() { return m_szDupeKey; } // needs locking (for shared objects) void SetDupeKey(const char* szDupeKey); // needs locking (for shared objects) int GetDupeScore() { return m_iDupeScore; } void SetDupeScore(int iDupeScore) { m_iDupeScore = iDupeScore; } EDupeMode GetDupeMode() { return m_eDupeMode; } void SetDupeMode(EDupeMode eDupeMode) { m_eDupeMode = eDupeMode; } long long GetSize() { return m_lSize; } void SetSize(long long lSize) { m_lSize = lSize; } unsigned int GetFullContentHash() { return m_iFullContentHash; } void SetFullContentHash(unsigned int iFullContentHash) { m_iFullContentHash = iFullContentHash; } unsigned int GetFilteredContentHash() { return m_iFilteredContentHash; } void SetFilteredContentHash(unsigned int iFilteredContentHash) { m_iFilteredContentHash = iFilteredContentHash; } EStatus GetStatus() { return m_eStatus; } void SetStatus(EStatus Status) { m_eStatus = Status; } }; class HistoryInfo { public: enum EKind { hkUnknown, hkNZBInfo, hkUrlInfo, hkDupInfo }; private: int m_iID; EKind m_eKind; void* m_pInfo; time_t m_tTime; static int m_iIDGen; static int m_iIDMax; public: HistoryInfo(NZBInfo* pNZBInfo); HistoryInfo(UrlInfo* pUrlInfo); HistoryInfo(DupInfo* pDupInfo); ~HistoryInfo(); int GetID() { return m_iID; } void SetID(int iID); static void ResetGenID(bool bMax); EKind GetKind() { return m_eKind; } NZBInfo* GetNZBInfo() { return (NZBInfo*)m_pInfo; } UrlInfo* GetUrlInfo() { return (UrlInfo*)m_pInfo; } DupInfo* GetDupInfo() { return (DupInfo*)m_pInfo; } void DiscardUrlInfo() { m_pInfo = NULL; } time_t GetTime() { return m_tTime; } void SetTime(time_t tTime) { m_tTime = tTime; } void GetName(char* szBuffer, int iSize); // needs locking (for shared objects) }; typedef std::deque HistoryList; class DownloadQueue { protected: NZBInfoList m_NZBInfoList; FileQueue m_FileQueue; PostQueue m_PostQueue; HistoryList m_HistoryList; FileQueue m_ParkedFiles; UrlQueue m_UrlQueue; public: NZBInfoList* GetNZBInfoList() { return &m_NZBInfoList; } FileQueue* GetFileQueue() { return &m_FileQueue; } PostQueue* GetPostQueue() { return &m_PostQueue; } HistoryList* GetHistoryList() { return &m_HistoryList; } FileQueue* GetParkedFiles() { return &m_ParkedFiles; } UrlQueue* GetUrlQueue() { return &m_UrlQueue; } void BuildGroups(GroupQueue* pGroupQueue); }; class DownloadQueueHolder { public: virtual ~DownloadQueueHolder() {}; virtual DownloadQueue* LockQueue() = 0; virtual void UnlockQueue() = 0; }; #endif nzbget-12.0+dfsg/DupeCoordinator.cpp000066400000000000000000000536171226450633000174660ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 905 $ * $Date: 2013-11-08 22:54:44 +0100 (Fri, 08 Nov 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #ifdef WIN32 #include #else #include #endif #include #include #include "nzbget.h" #include "Options.h" #include "Log.h" #include "Util.h" #include "DiskState.h" #include "NZBFile.h" #include "QueueCoordinator.h" #include "DupeCoordinator.h" extern QueueCoordinator* g_pQueueCoordinator; extern Options* g_pOptions; extern DiskState* g_pDiskState; bool DupeCoordinator::IsDupeSuccess(NZBInfo* pNZBInfo) { bool bFailure = pNZBInfo->GetDeleteStatus() != NZBInfo::dsNone || pNZBInfo->GetMarkStatus() == NZBInfo::ksBad || pNZBInfo->GetParStatus() == NZBInfo::psFailure || pNZBInfo->GetUnpackStatus() == NZBInfo::usFailure || pNZBInfo->GetUnpackStatus() == NZBInfo::usPassword || (pNZBInfo->GetParStatus() == NZBInfo::psSkipped && pNZBInfo->GetUnpackStatus() == NZBInfo::usSkipped && pNZBInfo->CalcHealth() < pNZBInfo->CalcCriticalHealth()); return !bFailure; } bool DupeCoordinator::SameNameOrKey(const char* szName1, const char* szDupeKey1, const char* szName2, const char* szDupeKey2) { bool bHasDupeKeys = !Util::EmptyStr(szDupeKey1) && !Util::EmptyStr(szDupeKey2); return (bHasDupeKeys && !strcmp(szDupeKey1, szDupeKey2)) || (!bHasDupeKeys && !strcmp(szName1, szName2)); } /** Check if the title was already downloaded or is already queued: - if there is a duplicate with exactly same content (via hash-check) in queue or in history - the new item is skipped; - if there is a duplicate marked as good in history - the new item is skipped; - if there is a duplicate with success-status in dup-history but there are no duplicates in recent history - the new item is skipped; - if queue has a duplicate with the same or higher score - the new item is moved to history as dupe-backup; - if queue has a duplicate with lower score - the existing item is moved to history as dupe-backup (unless it is in post-processing stage) and the new item is added to queue; - if queue doesn't have duplicates - the new item is added to queue. */ void DupeCoordinator::NZBFound(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo) { debug("Checking duplicates for %s", pNZBInfo->GetName()); GroupQueue groupQueue; pDownloadQueue->BuildGroups(&groupQueue); // find duplicates in download queue with exactly same content for (GroupQueue::iterator it = groupQueue.begin(); it != groupQueue.end(); it++) { GroupInfo* pGroupInfo = *it; NZBInfo* pGroupNZBInfo = pGroupInfo->GetNZBInfo(); bool bSameContent = (pNZBInfo->GetFullContentHash() > 0 && pNZBInfo->GetFullContentHash() == pGroupNZBInfo->GetFullContentHash()) || (pNZBInfo->GetFilteredContentHash() > 0 && pNZBInfo->GetFilteredContentHash() == pGroupNZBInfo->GetFilteredContentHash()); // if there is a duplicate with exactly same content (via hash-check) // in queue - the new item is skipped if (pGroupNZBInfo != pNZBInfo && bSameContent) { if (!strcmp(pNZBInfo->GetName(), pGroupNZBInfo->GetName())) { warn("Skipping duplicate %s, already queued", pNZBInfo->GetName()); } else { warn("Skipping duplicate %s, already queued as %s", pNZBInfo->GetName(), pGroupNZBInfo->GetName()); } // Flag saying QueueCoordinator to skip nzb-file pNZBInfo->SetDeleteStatus(NZBInfo::dsManual); DeleteQueuedFile(pNZBInfo->GetQueuedFilename()); return; } } // find duplicates in post queue with exactly same content for (PostQueue::iterator it = pDownloadQueue->GetPostQueue()->begin(); it != pDownloadQueue->GetPostQueue()->end(); it++) { PostInfo* pPostInfo = *it; bool bSameContent = (pNZBInfo->GetFullContentHash() > 0 && pNZBInfo->GetFullContentHash() == pPostInfo->GetNZBInfo()->GetFullContentHash()) || (pNZBInfo->GetFilteredContentHash() > 0 && pNZBInfo->GetFilteredContentHash() == pPostInfo->GetNZBInfo()->GetFilteredContentHash()); // if there is a duplicate with exactly same content (via hash-check) // in queue - the new item is skipped; if (bSameContent) { if (!strcmp(pNZBInfo->GetName(), pPostInfo->GetNZBInfo()->GetName())) { warn("Skipping duplicate %s, already queued", pNZBInfo->GetName()); } else { warn("Skipping duplicate %s, already queued as %s", pNZBInfo->GetName(), pPostInfo->GetNZBInfo()->GetName()); } // Flag saying QueueCoordinator to skip nzb-file pNZBInfo->SetDeleteStatus(NZBInfo::dsManual); DeleteQueuedFile(pNZBInfo->GetQueuedFilename()); return; } } // find duplicates in history bool bSkip = false; bool bGood = false; bool bSameContent = false; const char* szDupeName = NULL; // find duplicates in queue having exactly same content // also: nzb-files having duplicates marked as good are skipped // also (only in score mode): nzb-files having success-duplicates in dup-history but don't having duplicates in recent history are skipped for (HistoryList::iterator it = pDownloadQueue->GetHistoryList()->begin(); it != pDownloadQueue->GetHistoryList()->end(); it++) { HistoryInfo* pHistoryInfo = *it; if (pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo && ((pNZBInfo->GetFullContentHash() > 0 && pNZBInfo->GetFullContentHash() == pHistoryInfo->GetNZBInfo()->GetFullContentHash()) || (pNZBInfo->GetFilteredContentHash() > 0 && pNZBInfo->GetFilteredContentHash() == pHistoryInfo->GetNZBInfo()->GetFilteredContentHash()))) { bSkip = true; bSameContent = true; szDupeName = pHistoryInfo->GetNZBInfo()->GetName(); break; } if (pHistoryInfo->GetKind() == HistoryInfo::hkDupInfo && ((pNZBInfo->GetFullContentHash() > 0 && pNZBInfo->GetFullContentHash() == pHistoryInfo->GetDupInfo()->GetFullContentHash()) || (pNZBInfo->GetFilteredContentHash() > 0 && pNZBInfo->GetFilteredContentHash() == pHistoryInfo->GetDupInfo()->GetFilteredContentHash()))) { bSkip = true; bSameContent = true; szDupeName = pHistoryInfo->GetDupInfo()->GetName(); break; } if (pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo && pHistoryInfo->GetNZBInfo()->GetDupeMode() != dmForce && pHistoryInfo->GetNZBInfo()->GetMarkStatus() == NZBInfo::ksGood && SameNameOrKey(pHistoryInfo->GetNZBInfo()->GetName(), pHistoryInfo->GetNZBInfo()->GetDupeKey(), pNZBInfo->GetName(), pNZBInfo->GetDupeKey())) { bSkip = true; bGood = true; szDupeName = pHistoryInfo->GetNZBInfo()->GetName(); break; } if (pHistoryInfo->GetKind() == HistoryInfo::hkDupInfo && pHistoryInfo->GetDupInfo()->GetDupeMode() != dmForce && (pHistoryInfo->GetDupInfo()->GetStatus() == DupInfo::dsGood || (pNZBInfo->GetDupeMode() == dmScore && pHistoryInfo->GetDupInfo()->GetStatus() == DupInfo::dsSuccess && pNZBInfo->GetDupeScore() <= pHistoryInfo->GetDupInfo()->GetDupeScore())) && SameNameOrKey(pHistoryInfo->GetDupInfo()->GetName(), pHistoryInfo->GetDupInfo()->GetDupeKey(), pNZBInfo->GetName(), pNZBInfo->GetDupeKey())) { bSkip = true; bGood = pHistoryInfo->GetDupInfo()->GetStatus() == DupInfo::dsGood; szDupeName = pHistoryInfo->GetDupInfo()->GetName(); break; } } if (!bSameContent && !bGood && pNZBInfo->GetDupeMode() == dmScore) { // nzb-files having success-duplicates in recent history (with different content) are added to history for backup for (HistoryList::iterator it = pDownloadQueue->GetHistoryList()->begin(); it != pDownloadQueue->GetHistoryList()->end(); it++) { HistoryInfo* pHistoryInfo = *it; if (pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo && pHistoryInfo->GetNZBInfo()->GetDupeMode() != dmForce && SameNameOrKey(pHistoryInfo->GetNZBInfo()->GetName(), pHistoryInfo->GetNZBInfo()->GetDupeKey(), pNZBInfo->GetName(), pNZBInfo->GetDupeKey()) && pNZBInfo->GetDupeScore() <= pHistoryInfo->GetNZBInfo()->GetDupeScore() && IsDupeSuccess(pHistoryInfo->GetNZBInfo())) { // Flag saying QueueCoordinator to skip nzb-file pNZBInfo->SetDeleteStatus(NZBInfo::dsDupe); info("Collection %s is duplicate to %s", pNZBInfo->GetName(), pHistoryInfo->GetNZBInfo()->GetName()); return; } } } if (bSkip) { if (!strcmp(pNZBInfo->GetName(), szDupeName)) { warn("Skipping duplicate %s, found in history with %s", pNZBInfo->GetName(), bSameContent ? "exactly same content" : bGood ? "good status" : "success status"); } else { warn("Skipping duplicate %s, found in history %s with %s", pNZBInfo->GetName(), szDupeName, bSameContent ? "exactly same content" : bGood ? "good status" : "success status"); } // Flag saying QueueCoordinator to skip nzb-file pNZBInfo->SetDeleteStatus(NZBInfo::dsManual); DeleteQueuedFile(pNZBInfo->GetQueuedFilename()); return; } // find duplicates in download queue and post-queue and handle both items according to their scores: // only one item remains in queue and another one is moved to history as dupe-backup if (pNZBInfo->GetDupeMode() == dmScore) { // find duplicates in download queue for (GroupQueue::iterator it = groupQueue.begin(); it != groupQueue.end(); it++) { GroupInfo* pGroupInfo = *it; NZBInfo* pGroupNZBInfo = pGroupInfo->GetNZBInfo(); if (pGroupNZBInfo != pNZBInfo && pGroupNZBInfo->GetDupeMode() != dmForce && SameNameOrKey(pGroupNZBInfo->GetName(), pGroupNZBInfo->GetDupeKey(), pNZBInfo->GetName(), pNZBInfo->GetDupeKey())) { // if queue has a duplicate with the same or higher score - the new item // is moved to history as dupe-backup if (pNZBInfo->GetDupeScore() <= pGroupNZBInfo->GetDupeScore()) { // Flag saying QueueCoordinator to skip nzb-file pNZBInfo->SetDeleteStatus(NZBInfo::dsDupe); info("Collection %s is duplicate to %s", pNZBInfo->GetName(), pGroupNZBInfo->GetName()); return; } // if queue has a duplicate with lower score - the existing item is moved // to history as dupe-backup (unless it is in post-processing stage) and // the new item is added to queue else { // unless it is in post-processing stage bool bPostProcess = false; for (PostQueue::iterator it = pDownloadQueue->GetPostQueue()->begin(); it != pDownloadQueue->GetPostQueue()->end(); it++) { PostInfo* pPostInfo = *it; if (pPostInfo->GetNZBInfo() == pGroupNZBInfo) { bPostProcess = true; break; } } if (!bPostProcess) { // the existing queue item is moved to history as dupe-backup info("Moving collection %s with lower duplicate score to history", pGroupNZBInfo->GetName()); pGroupNZBInfo->SetDeleteStatus(NZBInfo::dsDupe); g_pQueueCoordinator->GetQueueEditor()->LockedEditEntry(pDownloadQueue, pGroupInfo->GetLastID(), false, QueueEditor::eaGroupDelete, 0, NULL); } } } } // find duplicates in post queue for (PostQueue::iterator it = pDownloadQueue->GetPostQueue()->begin(); it != pDownloadQueue->GetPostQueue()->end(); it++) { PostInfo* pPostInfo = *it; // if queue has a duplicate with the same or higher score - the new item // is moved to history as dupe-backup; if (pPostInfo->GetNZBInfo()->GetDupeMode() != dmForce && pNZBInfo->GetDupeScore() <= pPostInfo->GetNZBInfo()->GetDupeScore() && SameNameOrKey(pPostInfo->GetNZBInfo()->GetName(), pPostInfo->GetNZBInfo()->GetDupeKey(), pNZBInfo->GetName(), pNZBInfo->GetDupeKey())) { // Flag saying QueueCoordinator to skip nzb-file pNZBInfo->SetDeleteStatus(NZBInfo::dsDupe); info("Collection %s is duplicate to %s", pNZBInfo->GetName(), pPostInfo->GetNZBInfo()->GetName()); return; } } } } /** - if download of an item fails and there are duplicates in history - return the best duplicate from historyto queue for download; - if download of an item completes successfully - nothing extra needs to be done; */ void DupeCoordinator::NZBCompleted(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo) { debug("Processing duplicates for %s", pNZBInfo->GetName()); if (pNZBInfo->GetDupeMode() == dmScore && !IsDupeSuccess(pNZBInfo)) { ReturnBestDupe(pDownloadQueue, pNZBInfo, pNZBInfo->GetName(), pNZBInfo->GetDupeKey()); } } /** Returns the best duplicate from history to download queue. */ void DupeCoordinator::ReturnBestDupe(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, const char* szNZBName, const char* szDupeKey) { // check if history (recent or dup) has other success-duplicates or good-duplicates bool bHistoryDupe = false; int iHistoryScore = 0; for (HistoryList::iterator it = pDownloadQueue->GetHistoryList()->begin(); it != pDownloadQueue->GetHistoryList()->end(); it++) { HistoryInfo* pHistoryInfo = *it; bool bGoodDupe = false; if (pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo && pHistoryInfo->GetNZBInfo()->GetDupeMode() != dmForce && (IsDupeSuccess(pHistoryInfo->GetNZBInfo()) || pHistoryInfo->GetNZBInfo()->GetMarkStatus() == NZBInfo::ksGood) && SameNameOrKey(pHistoryInfo->GetNZBInfo()->GetName(), pHistoryInfo->GetNZBInfo()->GetDupeKey(), szNZBName, szDupeKey)) { if (!bHistoryDupe || pHistoryInfo->GetNZBInfo()->GetDupeScore() > iHistoryScore) { iHistoryScore = pHistoryInfo->GetNZBInfo()->GetDupeScore(); } bHistoryDupe = true; bGoodDupe = pHistoryInfo->GetNZBInfo()->GetMarkStatus() == NZBInfo::ksGood; } if (pHistoryInfo->GetKind() == HistoryInfo::hkDupInfo && pHistoryInfo->GetDupInfo()->GetDupeMode() != dmForce && (pHistoryInfo->GetDupInfo()->GetStatus() == DupInfo::dsSuccess || pHistoryInfo->GetDupInfo()->GetStatus() == DupInfo::dsGood) && SameNameOrKey(pHistoryInfo->GetDupInfo()->GetName(), pHistoryInfo->GetDupInfo()->GetDupeKey(), szNZBName, szDupeKey)) { if (!bHistoryDupe || pHistoryInfo->GetDupInfo()->GetDupeScore() > iHistoryScore) { iHistoryScore = pHistoryInfo->GetDupInfo()->GetDupeScore(); } bHistoryDupe = true; bGoodDupe = pHistoryInfo->GetDupInfo()->GetStatus() == DupInfo::dsGood; } if (bGoodDupe) { // another duplicate with good-status exists - exit without moving other dupes to queue return; } } // check if duplicates exist in post-processing queue bool bPostDupe = false; int iPostScore = 0; for (PostQueue::iterator it = pDownloadQueue->GetPostQueue()->begin(); it != pDownloadQueue->GetPostQueue()->end(); it++) { PostInfo* pPostInfo = *it; if (pPostInfo->GetNZBInfo() != pNZBInfo && pPostInfo->GetNZBInfo()->GetDupeMode() != dmForce && SameNameOrKey(pPostInfo->GetNZBInfo()->GetName(), pPostInfo->GetNZBInfo()->GetDupeKey(), szNZBName, szDupeKey) && (!bPostDupe || pPostInfo->GetNZBInfo()->GetDupeScore() > iPostScore)) { iPostScore = pPostInfo->GetNZBInfo()->GetDupeScore(); bPostDupe = true; } } // check if duplicates exist in download queue GroupQueue groupQueue; pDownloadQueue->BuildGroups(&groupQueue); bool bQueueDupe = false; int iQueueScore = 0; for (GroupQueue::iterator it = groupQueue.begin(); it != groupQueue.end(); it++) { GroupInfo* pGroupInfo = *it; NZBInfo* pGroupNZBInfo = pGroupInfo->GetNZBInfo(); if (pGroupNZBInfo != pNZBInfo && pGroupNZBInfo->GetDupeMode() != dmForce && SameNameOrKey(pGroupNZBInfo->GetName(), pGroupNZBInfo->GetDupeKey(), szNZBName, szDupeKey) && (!bQueueDupe || pGroupNZBInfo->GetDupeScore() > iQueueScore)) { iQueueScore = pGroupNZBInfo->GetDupeScore(); bQueueDupe = true; } } // find dupe-backup with highest score, whose score is also higher than other // success-duplicates and higher than already queued items HistoryInfo* pHistoryDupe = NULL; for (HistoryList::iterator it = pDownloadQueue->GetHistoryList()->begin(); it != pDownloadQueue->GetHistoryList()->end(); it++) { HistoryInfo* pHistoryInfo = *it; if (pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo && pHistoryInfo->GetNZBInfo()->GetDupeMode() != dmForce && pHistoryInfo->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsDupe && pHistoryInfo->GetNZBInfo()->CalcHealth() >= pHistoryInfo->GetNZBInfo()->CalcCriticalHealth() && pHistoryInfo->GetNZBInfo()->GetMarkStatus() != NZBInfo::ksBad && (!bHistoryDupe || pHistoryInfo->GetNZBInfo()->GetDupeScore() > iHistoryScore) && (!bPostDupe || pHistoryInfo->GetNZBInfo()->GetDupeScore() > iPostScore) && (!bQueueDupe || pHistoryInfo->GetNZBInfo()->GetDupeScore() > iQueueScore) && (!pHistoryDupe || pHistoryInfo->GetNZBInfo()->GetDupeScore() > pHistoryDupe->GetNZBInfo()->GetDupeScore()) && SameNameOrKey(pHistoryInfo->GetNZBInfo()->GetName(), pHistoryInfo->GetNZBInfo()->GetDupeKey(), szNZBName, szDupeKey)) { pHistoryDupe = pHistoryInfo; } } // move that dupe-backup from history to download queue if (pHistoryDupe) { info("Found duplicate %s for %s", pHistoryDupe->GetNZBInfo()->GetName(), szNZBName); HistoryRedownload(pDownloadQueue, pHistoryDupe); } } void DupeCoordinator::HistoryMark(DownloadQueue* pDownloadQueue, HistoryInfo* pHistoryInfo, bool bGood) { char szNZBName[1024]; pHistoryInfo->GetName(szNZBName, 1024); info("Marking %s as %s", szNZBName, (bGood ? "good" : "bad")); if (pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo) { pHistoryInfo->GetNZBInfo()->SetMarkStatus(bGood ? NZBInfo::ksGood : NZBInfo::ksBad); } else if (pHistoryInfo->GetKind() == HistoryInfo::hkDupInfo) { pHistoryInfo->GetDupInfo()->SetStatus(bGood ? DupInfo::dsGood : DupInfo::dsBad); } else { error("Could not mark %s as bad: history item has wrong type", szNZBName); return; } if (!g_pOptions->GetDupeCheck() || (pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo && pHistoryInfo->GetNZBInfo()->GetDupeMode() == dmForce) || (pHistoryInfo->GetKind() == HistoryInfo::hkDupInfo && pHistoryInfo->GetDupInfo()->GetDupeMode() == dmForce)) { return; } if (bGood) { // mark as good // moving all duplicates from history to dup-history HistoryCleanup(pDownloadQueue, pHistoryInfo); } else { // mark as bad const char* szDupeKey = pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo ? pHistoryInfo->GetNZBInfo()->GetDupeKey() : pHistoryInfo->GetKind() == HistoryInfo::hkDupInfo ? pHistoryInfo->GetDupInfo()->GetDupeKey() : NULL; ReturnBestDupe(pDownloadQueue, NULL, szNZBName, szDupeKey); } } void DupeCoordinator::HistoryCleanup(DownloadQueue* pDownloadQueue, HistoryInfo* pMarkHistoryInfo) { const char* szDupeKey = pMarkHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo ? pMarkHistoryInfo->GetNZBInfo()->GetDupeKey() : pMarkHistoryInfo->GetKind() == HistoryInfo::hkDupInfo ? pMarkHistoryInfo->GetDupInfo()->GetDupeKey() : NULL; const char* szNZBName = pMarkHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo ? pMarkHistoryInfo->GetNZBInfo()->GetName() : pMarkHistoryInfo->GetKind() == HistoryInfo::hkDupInfo ? pMarkHistoryInfo->GetDupInfo()->GetName() : NULL; bool bChanged = false; int index = 0; // traversing in a reverse order to delete items in order they were added to history // (just to produce the log-messages in a more logical order) for (HistoryList::reverse_iterator it = pDownloadQueue->GetHistoryList()->rbegin(); it != pDownloadQueue->GetHistoryList()->rend(); ) { HistoryInfo* pHistoryInfo = *it; if (pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo && pHistoryInfo->GetNZBInfo()->GetDupeMode() != dmForce && pHistoryInfo->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsDupe && pHistoryInfo != pMarkHistoryInfo && SameNameOrKey(pHistoryInfo->GetNZBInfo()->GetName(), pHistoryInfo->GetNZBInfo()->GetDupeKey(), szNZBName, szDupeKey)) { HistoryTransformToDup(pDownloadQueue, pHistoryInfo, index); index++; it = pDownloadQueue->GetHistoryList()->rbegin() + index; bChanged = true; } else { it++; index++; } } if (bChanged && g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState->SaveDownloadQueue(pDownloadQueue); } } void DupeCoordinator::HistoryTransformToDup(DownloadQueue* pDownloadQueue, HistoryInfo* pHistoryInfo, int rindex) { char szNiceName[1024]; pHistoryInfo->GetName(szNiceName, 1024); // replace history element DupInfo* pDupInfo = new DupInfo(); pDupInfo->SetName(pHistoryInfo->GetNZBInfo()->GetName()); pDupInfo->SetDupeKey(pHistoryInfo->GetNZBInfo()->GetDupeKey()); pDupInfo->SetDupeScore(pHistoryInfo->GetNZBInfo()->GetDupeScore()); pDupInfo->SetDupeMode(pHistoryInfo->GetNZBInfo()->GetDupeMode()); pDupInfo->SetSize(pHistoryInfo->GetNZBInfo()->GetSize()); pDupInfo->SetFullContentHash(pHistoryInfo->GetNZBInfo()->GetFullContentHash()); pDupInfo->SetFilteredContentHash(pHistoryInfo->GetNZBInfo()->GetFilteredContentHash()); pDupInfo->SetStatus( pHistoryInfo->GetNZBInfo()->GetMarkStatus() == NZBInfo::ksGood ? DupInfo::dsGood : pHistoryInfo->GetNZBInfo()->GetMarkStatus() == NZBInfo::ksBad ? DupInfo::dsBad : pHistoryInfo->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsDupe ? DupInfo::dsDupe : pHistoryInfo->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsManual ? DupInfo::dsDeleted : IsDupeSuccess(pHistoryInfo->GetNZBInfo()) ? DupInfo::dsSuccess : DupInfo::dsFailed); HistoryInfo* pNewHistoryInfo = new HistoryInfo(pDupInfo); pNewHistoryInfo->SetTime(pHistoryInfo->GetTime()); (*pDownloadQueue->GetHistoryList())[pDownloadQueue->GetHistoryList()->size() - 1 - rindex] = pNewHistoryInfo; DeleteQueuedFile(pHistoryInfo->GetNZBInfo()->GetQueuedFilename()); delete pHistoryInfo; info("Collection %s removed from history", szNiceName); } nzbget-12.0+dfsg/DupeCoordinator.h000066400000000000000000000037751226450633000171330ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 900 $ * $Date: 2013-11-04 21:59:20 +0100 (Mon, 04 Nov 2013) $ * */ #ifndef DUPECOORDINATOR_H #define DUPECOORDINATOR_H #include #include "DownloadInfo.h" class DupeCoordinator { private: bool IsDupeSuccess(NZBInfo* pNZBInfo); void ReturnBestDupe(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, const char* szNZBName, const char* szDupeKey); void HistoryReturnDupe(DownloadQueue* pDownloadQueue, HistoryInfo* pHistoryInfo); void HistoryCleanup(DownloadQueue* pDownloadQueue, HistoryInfo* pMarkHistoryInfo); bool SameNameOrKey(const char* szName1, const char* szDupeKey1, const char* szName2, const char* szDupeKey2); protected: virtual void HistoryRedownload(DownloadQueue* pDownloadQueue, HistoryInfo* pHistoryInfo) = 0; virtual void DeleteQueuedFile(const char* szQueuedFile) = 0; public: void NZBCompleted(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo); void NZBFound(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo); void HistoryMark(DownloadQueue* pDownloadQueue, HistoryInfo* pHistoryInfo, bool bGood); void HistoryTransformToDup(DownloadQueue* pDownloadQueue, HistoryInfo* pHistoryInfo, int rindex); }; #endif nzbget-12.0+dfsg/FeedCoordinator.cpp000066400000000000000000000447631226450633000174360ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 911 $ * $Date: 2013-11-24 20:29:52 +0100 (Sun, 24 Nov 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #include #ifndef WIN32 #include #include #endif #include "nzbget.h" #include "FeedCoordinator.h" #include "Options.h" #include "WebDownloader.h" #include "Log.h" #include "Util.h" #include "FeedFile.h" #include "FeedFilter.h" #include "UrlCoordinator.h" #include "DiskState.h" extern Options* g_pOptions; extern UrlCoordinator* g_pUrlCoordinator; extern DiskState* g_pDiskState; FeedCoordinator::FeedCacheItem::FeedCacheItem(const char* szUrl, int iCacheTimeSec,const char* szCacheId, time_t tLastUsage, FeedItemInfos* pFeedItemInfos) { m_szUrl = strdup(szUrl); m_iCacheTimeSec = iCacheTimeSec; m_szCacheId = strdup(szCacheId); m_tLastUsage = tLastUsage; m_pFeedItemInfos = pFeedItemInfos; m_pFeedItemInfos->Retain(); } FeedCoordinator::FeedCacheItem::~FeedCacheItem() { free(m_szUrl); free(m_szCacheId); m_pFeedItemInfos->Release(); } FeedCoordinator::FeedCoordinator() { debug("Creating FeedCoordinator"); m_bForce = false; m_bSave = false; m_UrlCoordinatorObserver.m_pOwner = this; g_pUrlCoordinator->Attach(&m_UrlCoordinatorObserver); } FeedCoordinator::~FeedCoordinator() { debug("Destroying FeedCoordinator"); // Cleanup debug("Deleting FeedDownloaders"); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { delete *it; } m_ActiveDownloads.clear(); debug("Deleting Feeds"); for (Feeds::iterator it = m_Feeds.begin(); it != m_Feeds.end(); it++) { delete *it; } m_Feeds.clear(); debug("Deleting FeedCache"); for (FeedCache::iterator it = m_FeedCache.begin(); it != m_FeedCache.end(); it++) { delete *it; } m_FeedCache.clear(); debug("FeedCoordinator destroyed"); } void FeedCoordinator::AddFeed(FeedInfo* pFeedInfo) { m_Feeds.push_back(pFeedInfo); } void FeedCoordinator::Run() { debug("Entering FeedCoordinator-loop"); m_mutexDownloads.Lock(); if (g_pOptions->GetServerMode() && g_pOptions->GetSaveQueue() && g_pOptions->GetReloadQueue()) { g_pDiskState->LoadFeeds(&m_Feeds, &m_FeedHistory); } m_mutexDownloads.Unlock(); int iSleepInterval = 100; int iUpdateCounter = 0; int iCleanupCounter = 60000; while (!IsStopped()) { usleep(iSleepInterval * 1000); iUpdateCounter += iSleepInterval; if (iUpdateCounter >= 1000) { // this code should not be called too often, once per second is OK if (!(g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2()) || m_bForce || g_pOptions->GetUrlForce()) { m_mutexDownloads.Lock(); time_t tCurrent = time(NULL); if ((int)m_ActiveDownloads.size() < g_pOptions->GetUrlConnections()) { m_bForce = false; // check feed list and update feeds for (Feeds::iterator it = m_Feeds.begin(); it != m_Feeds.end(); it++) { FeedInfo* pFeedInfo = *it; if (((pFeedInfo->GetInterval() > 0 && (tCurrent - pFeedInfo->GetLastUpdate() >= pFeedInfo->GetInterval() * 60 || tCurrent < pFeedInfo->GetLastUpdate())) || pFeedInfo->GetFetch()) && pFeedInfo->GetStatus() != FeedInfo::fsRunning) { StartFeedDownload(pFeedInfo, pFeedInfo->GetFetch()); } else if (pFeedInfo->GetFetch()) { m_bForce = true; } } } m_mutexDownloads.Unlock(); } CheckSaveFeeds(); ResetHangingDownloads(); iUpdateCounter = 0; } iCleanupCounter += iSleepInterval; if (iCleanupCounter >= 60000) { // clean up feed history once a minute CleanupHistory(); CleanupCache(); CheckSaveFeeds(); iCleanupCounter = 0; } } // waiting for downloads debug("FeedCoordinator: waiting for Downloads to complete"); bool completed = false; while (!completed) { m_mutexDownloads.Lock(); completed = m_ActiveDownloads.size() == 0; m_mutexDownloads.Unlock(); CheckSaveFeeds(); usleep(100 * 1000); ResetHangingDownloads(); } debug("FeedCoordinator: Downloads are completed"); debug("Exiting FeedCoordinator-loop"); } void FeedCoordinator::Stop() { Thread::Stop(); debug("Stopping UrlDownloads"); m_mutexDownloads.Lock(); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { (*it)->Stop(); } m_mutexDownloads.Unlock(); debug("UrlDownloads are notified"); } void FeedCoordinator::ResetHangingDownloads() { const int TimeOut = g_pOptions->GetTerminateTimeout(); if (TimeOut == 0) { return; } m_mutexDownloads.Lock(); time_t tm = ::time(NULL); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end();) { FeedDownloader* pFeedDownloader = *it; if (tm - pFeedDownloader->GetLastUpdateTime() > TimeOut && pFeedDownloader->GetStatus() == FeedDownloader::adRunning) { debug("Terminating hanging download %s", pFeedDownloader->GetInfoName()); if (pFeedDownloader->Terminate()) { error("Terminated hanging download %s", pFeedDownloader->GetInfoName()); pFeedDownloader->GetFeedInfo()->SetStatus(FeedInfo::fsUndefined); } else { error("Could not terminate hanging download %s", pFeedDownloader->GetInfoName()); } m_ActiveDownloads.erase(it); // it's not safe to destroy pFeedDownloader, because the state of object is unknown delete pFeedDownloader; it = m_ActiveDownloads.begin(); continue; } it++; } m_mutexDownloads.Unlock(); } void FeedCoordinator::LogDebugInfo() { debug(" FeedCoordinator"); debug(" ----------------"); m_mutexDownloads.Lock(); debug(" Active Downloads: %i", m_ActiveDownloads.size()); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { FeedDownloader* pFeedDownloader = *it; pFeedDownloader->LogDebugInfo(); } m_mutexDownloads.Unlock(); } void FeedCoordinator::StartFeedDownload(FeedInfo* pFeedInfo, bool bForce) { debug("Starting new FeedDownloader for %", pFeedInfo->GetName()); FeedDownloader* pFeedDownloader = new FeedDownloader(); pFeedDownloader->SetAutoDestroy(true); pFeedDownloader->Attach(this); pFeedDownloader->SetFeedInfo(pFeedInfo); pFeedDownloader->SetURL(pFeedInfo->GetUrl()); if (strlen(pFeedInfo->GetName()) > 0) { pFeedDownloader->SetInfoName(pFeedInfo->GetName()); } else { char szUrlName[1024]; UrlInfo::MakeNiceName(pFeedInfo->GetUrl(), "", szUrlName, sizeof(szUrlName)); pFeedDownloader->SetInfoName(szUrlName); } pFeedDownloader->SetForce(bForce || g_pOptions->GetUrlForce()); char tmp[1024]; if (pFeedInfo->GetID() > 0) { snprintf(tmp, 1024, "%sfeed-%i.tmp", g_pOptions->GetTempDir(), pFeedInfo->GetID()); } else { snprintf(tmp, 1024, "%sfeed-%i-%i.tmp", g_pOptions->GetTempDir(), (int)time(NULL), rand()); } tmp[1024-1] = '\0'; pFeedDownloader->SetOutputFilename(tmp); pFeedInfo->SetStatus(FeedInfo::fsRunning); pFeedInfo->SetForce(bForce); pFeedInfo->SetFetch(false); m_ActiveDownloads.push_back(pFeedDownloader); pFeedDownloader->Start(); } void FeedCoordinator::Update(Subject* pCaller, void* pAspect) { debug("Notification from FeedDownloader received"); FeedDownloader* pFeedDownloader = (FeedDownloader*) pCaller; if ((pFeedDownloader->GetStatus() == WebDownloader::adFinished) || (pFeedDownloader->GetStatus() == WebDownloader::adFailed) || (pFeedDownloader->GetStatus() == WebDownloader::adRetry)) { FeedCompleted(pFeedDownloader); } } void FeedCoordinator::FeedCompleted(FeedDownloader* pFeedDownloader) { debug("Feed downloaded"); FeedInfo* pFeedInfo = pFeedDownloader->GetFeedInfo(); bool bStatusOK = pFeedDownloader->GetStatus() == WebDownloader::adFinished; if (bStatusOK) { pFeedInfo->SetOutputFilename(pFeedDownloader->GetOutputFilename()); } // delete Download from Queue m_mutexDownloads.Lock(); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { FeedDownloader* pa = *it; if (pa == pFeedDownloader) { m_ActiveDownloads.erase(it); break; } } m_mutexDownloads.Unlock(); if (bStatusOK) { if (!pFeedInfo->GetPreview()) { FeedFile* pFeedFile = FeedFile::Create(pFeedInfo->GetOutputFilename()); remove(pFeedInfo->GetOutputFilename()); m_mutexDownloads.Lock(); if (pFeedFile) { ProcessFeed(pFeedInfo, pFeedFile->GetFeedItemInfos()); delete pFeedFile; } pFeedInfo->SetLastUpdate(time(NULL)); pFeedInfo->SetForce(false); m_bSave = true; m_mutexDownloads.Unlock(); } pFeedInfo->SetStatus(FeedInfo::fsFinished); } else { pFeedInfo->SetStatus(FeedInfo::fsFailed); } } void FeedCoordinator::FilterFeed(FeedInfo* pFeedInfo, FeedItemInfos* pFeedItemInfos) { debug("Filtering feed %s", pFeedInfo->GetName()); FeedFilter* pFeedFilter = NULL; if (pFeedInfo->GetFilter() && strlen(pFeedInfo->GetFilter()) > 0) { pFeedFilter = new FeedFilter(pFeedInfo->GetFilter()); } for (FeedItemInfos::iterator it = pFeedItemInfos->begin(); it != pFeedItemInfos->end(); it++) { FeedItemInfo* pFeedItemInfo = *it; pFeedItemInfo->SetMatchStatus(FeedItemInfo::msAccepted); pFeedItemInfo->SetMatchRule(0); pFeedItemInfo->SetPauseNzb(pFeedInfo->GetPauseNzb()); pFeedItemInfo->SetPriority(pFeedInfo->GetPriority()); pFeedItemInfo->SetAddCategory(pFeedInfo->GetCategory()); pFeedItemInfo->SetDupeScore(0); pFeedItemInfo->SetDupeMode(dmScore); pFeedItemInfo->BuildDupeKey(NULL, NULL); if (pFeedFilter) { pFeedFilter->Match(pFeedItemInfo); } } delete pFeedFilter; } void FeedCoordinator::ProcessFeed(FeedInfo* pFeedInfo, FeedItemInfos* pFeedItemInfos) { debug("Process feed %s", pFeedInfo->GetName()); FilterFeed(pFeedInfo, pFeedItemInfos); bool bFirstFetch = pFeedInfo->GetLastUpdate() == 0; int iAdded = 0; for (FeedItemInfos::iterator it = pFeedItemInfos->begin(); it != pFeedItemInfos->end(); it++) { FeedItemInfo* pFeedItemInfo = *it; if (pFeedItemInfo->GetMatchStatus() == FeedItemInfo::msAccepted) { FeedHistoryInfo* pFeedHistoryInfo = m_FeedHistory.Find(pFeedItemInfo->GetUrl()); FeedHistoryInfo::EStatus eStatus = FeedHistoryInfo::hsUnknown; if (bFirstFetch) { eStatus = FeedHistoryInfo::hsBacklog; } else if (!pFeedHistoryInfo) { DownloadItem(pFeedInfo, pFeedItemInfo); eStatus = FeedHistoryInfo::hsFetched; iAdded++; } if (pFeedHistoryInfo) { pFeedHistoryInfo->SetLastSeen(time(NULL)); } else { m_FeedHistory.Add(pFeedItemInfo->GetUrl(), eStatus, time(NULL)); } } } if (iAdded) { info("%s has %i new item(s)", pFeedInfo->GetName(), iAdded); } else { detail("%s has no new items", pFeedInfo->GetName()); } } void FeedCoordinator::DownloadItem(FeedInfo* pFeedInfo, FeedItemInfo* pFeedItemInfo) { debug("Download %s from %s", pFeedItemInfo->GetUrl(), pFeedInfo->GetName()); UrlInfo* pUrlInfo = new UrlInfo(); pUrlInfo->SetURL(pFeedItemInfo->GetUrl()); // add .nzb-extension if not present char szNZBName[1024]; strncpy(szNZBName, pFeedItemInfo->GetFilename(), 1024); szNZBName[1024-1] = '\0'; char* ext = strrchr(szNZBName, '.'); if (ext && !strcasecmp(ext, ".nzb")) { *ext = '\0'; } char szNZBName2[1024]; snprintf(szNZBName2, 1024, "%s.nzb", szNZBName); Util::MakeValidFilename(szNZBName2, '_', false); if (strlen(szNZBName) > 0) { pUrlInfo->SetNZBFilename(szNZBName2); } pUrlInfo->SetCategory(pFeedItemInfo->GetAddCategory()); pUrlInfo->SetPriority(pFeedItemInfo->GetPriority()); pUrlInfo->SetAddPaused(pFeedItemInfo->GetPauseNzb()); pUrlInfo->SetDupeKey(pFeedItemInfo->GetDupeKey()); pUrlInfo->SetDupeScore(pFeedItemInfo->GetDupeScore()); pUrlInfo->SetDupeMode(pFeedItemInfo->GetDupeMode()); pUrlInfo->SetForce(pFeedInfo->GetForce() || g_pOptions->GetUrlForce()); g_pUrlCoordinator->AddUrlToQueue(pUrlInfo, false); } bool FeedCoordinator::ViewFeed(int iID, FeedItemInfos** ppFeedItemInfos) { if (iID < 1 || iID > (int)m_Feeds.size()) { return false; } FeedInfo* pFeedInfo = m_Feeds.at(iID - 1); return PreviewFeed(pFeedInfo->GetName(), pFeedInfo->GetUrl(), pFeedInfo->GetFilter(), pFeedInfo->GetPauseNzb(), pFeedInfo->GetCategory(), pFeedInfo->GetPriority(), 0, NULL, ppFeedItemInfos); } bool FeedCoordinator::PreviewFeed(const char* szName, const char* szUrl, const char* szFilter, bool bPauseNzb, const char* szCategory, int iPriority, int iCacheTimeSec, const char* szCacheId, FeedItemInfos** ppFeedItemInfos) { debug("Preview feed %s", szName); FeedInfo* pFeedInfo = new FeedInfo(0, szName, szUrl, 0, szFilter, bPauseNzb, szCategory, iPriority); pFeedInfo->SetPreview(true); FeedItemInfos* pFeedItemInfos = NULL; bool bHasCache = false; if (iCacheTimeSec > 0 && *szCacheId != '\0') { m_mutexDownloads.Lock(); for (FeedCache::iterator it = m_FeedCache.begin(); it != m_FeedCache.end(); it++) { FeedCacheItem* pFeedCacheItem = *it; if (!strcmp(pFeedCacheItem->GetCacheId(), szCacheId)) { pFeedCacheItem->SetLastUsage(time(NULL)); pFeedItemInfos = pFeedCacheItem->GetFeedItemInfos(); pFeedItemInfos->Retain(); bHasCache = true; break; } } m_mutexDownloads.Unlock(); } if (!bHasCache) { m_mutexDownloads.Lock(); bool bFirstFetch = true; for (Feeds::iterator it = m_Feeds.begin(); it != m_Feeds.end(); it++) { FeedInfo* pFeedInfo2 = *it; if (!strcmp(pFeedInfo2->GetUrl(), pFeedInfo->GetUrl()) && !strcmp(pFeedInfo2->GetFilter(), pFeedInfo->GetFilter()) && pFeedInfo2->GetLastUpdate() > 0) { bFirstFetch = false; break; } } StartFeedDownload(pFeedInfo, true); m_mutexDownloads.Unlock(); // wait until the download in a separate thread completes while (pFeedInfo->GetStatus() == FeedInfo::fsRunning) { usleep(100 * 1000); } // now can process the feed FeedFile* pFeedFile = NULL; if (pFeedInfo->GetStatus() == FeedInfo::fsFinished) { pFeedFile = FeedFile::Create(pFeedInfo->GetOutputFilename()); } remove(pFeedInfo->GetOutputFilename()); if (!pFeedFile) { delete pFeedInfo; return false; } pFeedItemInfos = pFeedFile->GetFeedItemInfos(); pFeedItemInfos->Retain(); delete pFeedFile; for (FeedItemInfos::iterator it = pFeedItemInfos->begin(); it != pFeedItemInfos->end(); it++) { FeedItemInfo* pFeedItemInfo = *it; pFeedItemInfo->SetStatus(bFirstFetch ? FeedItemInfo::isBacklog : FeedItemInfo::isNew); FeedHistoryInfo* pFeedHistoryInfo = m_FeedHistory.Find(pFeedItemInfo->GetUrl()); if (pFeedHistoryInfo) { pFeedItemInfo->SetStatus((FeedItemInfo::EStatus)pFeedHistoryInfo->GetStatus()); } } } FilterFeed(pFeedInfo, pFeedItemInfos); delete pFeedInfo; if (iCacheTimeSec > 0 && *szCacheId != '\0' && !bHasCache) { FeedCacheItem* pFeedCacheItem = new FeedCacheItem(szUrl, iCacheTimeSec, szCacheId, time(NULL), pFeedItemInfos); m_mutexDownloads.Lock(); m_FeedCache.push_back(pFeedCacheItem); m_mutexDownloads.Unlock(); } *ppFeedItemInfos = pFeedItemInfos; return true; } void FeedCoordinator::FetchFeed(int iID) { debug("FetchFeeds"); m_mutexDownloads.Lock(); for (Feeds::iterator it = m_Feeds.begin(); it != m_Feeds.end(); it++) { FeedInfo* pFeedInfo = *it; if (pFeedInfo->GetID() == iID || iID == 0) { pFeedInfo->SetFetch(true); m_bForce = true; } } m_mutexDownloads.Unlock(); } void FeedCoordinator::UrlCoordinatorUpdate(Subject* pCaller, void* pAspect) { debug("Notification from URL-Coordinator received"); UrlCoordinator::Aspect* pUrlAspect = (UrlCoordinator::Aspect*)pAspect; if (pUrlAspect->eAction == UrlCoordinator::eaUrlCompleted) { m_mutexDownloads.Lock(); FeedHistoryInfo* pFeedHistoryInfo = m_FeedHistory.Find(pUrlAspect->pUrlInfo->GetURL()); if (pFeedHistoryInfo) { pFeedHistoryInfo->SetStatus(FeedHistoryInfo::hsFetched); } else { m_FeedHistory.Add(pUrlAspect->pUrlInfo->GetURL(), FeedHistoryInfo::hsFetched, time(NULL)); } m_bSave = true; m_mutexDownloads.Unlock(); } } bool FeedCoordinator::HasActiveDownloads() { m_mutexDownloads.Lock(); bool bActive = !m_ActiveDownloads.empty(); m_mutexDownloads.Unlock(); return bActive; } void FeedCoordinator::CheckSaveFeeds() { debug("CheckSaveFeeds"); m_mutexDownloads.Lock(); if (m_bSave) { if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState->SaveFeeds(&m_Feeds, &m_FeedHistory); } m_bSave = false; } m_mutexDownloads.Unlock(); } void FeedCoordinator::CleanupHistory() { debug("CleanupHistory"); m_mutexDownloads.Lock(); time_t tOldestUpdate = time(NULL); for (Feeds::iterator it = m_Feeds.begin(); it != m_Feeds.end(); it++) { FeedInfo* pFeedInfo = *it; if (pFeedInfo->GetLastUpdate() < tOldestUpdate) { tOldestUpdate = pFeedInfo->GetLastUpdate(); } } time_t tBorderDate = tOldestUpdate - g_pOptions->GetFeedHistory(); int i = 0; for (FeedHistory::iterator it = m_FeedHistory.begin(); it != m_FeedHistory.end(); ) { FeedHistoryInfo* pFeedHistoryInfo = *it; if (pFeedHistoryInfo->GetLastSeen() < tBorderDate) { detail("Deleting %s from feed history", pFeedHistoryInfo->GetUrl()); delete pFeedHistoryInfo; m_FeedHistory.erase(it); it = m_FeedHistory.begin() + i; m_bSave = true; } else { it++; i++; } } m_mutexDownloads.Unlock(); } void FeedCoordinator::CleanupCache() { debug("CleanupCache"); m_mutexDownloads.Lock(); time_t tCurTime = time(NULL); int i = 0; for (FeedCache::iterator it = m_FeedCache.begin(); it != m_FeedCache.end(); ) { FeedCacheItem* pFeedCacheItem = *it; if (pFeedCacheItem->GetLastUsage() + pFeedCacheItem->GetCacheTimeSec() < tCurTime || pFeedCacheItem->GetLastUsage() > tCurTime) { debug("Deleting %s from feed cache", pFeedCacheItem->GetUrl()); delete pFeedCacheItem; m_FeedCache.erase(it); it = m_FeedCache.begin() + i; } else { it++; i++; } } m_mutexDownloads.Unlock(); } nzbget-12.0+dfsg/FeedCoordinator.h000066400000000000000000000075111226450633000170710ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 906 $ * $Date: 2013-11-12 21:54:45 +0100 (Tue, 12 Nov 2013) $ * */ #ifndef FEEDCOORDINATOR_H #define FEEDCOORDINATOR_H #include #include #include #include "Thread.h" #include "WebDownloader.h" #include "DownloadInfo.h" #include "FeedInfo.h" #include "Observer.h" class FeedDownloader; class FeedCoordinator : public Thread, public Observer, public Subject { public: typedef std::list ActiveDownloads; private: class UrlCoordinatorObserver: public Observer { public: FeedCoordinator* m_pOwner; virtual void Update(Subject* pCaller, void* pAspect) { m_pOwner->UrlCoordinatorUpdate(pCaller, pAspect); } }; class FeedCacheItem { private: char* m_szUrl; int m_iCacheTimeSec; char* m_szCacheId; time_t m_tLastUsage; FeedItemInfos* m_pFeedItemInfos; public: FeedCacheItem(const char* szUrl, int iCacheTimeSec,const char* szCacheId, time_t tLastUsage, FeedItemInfos* pFeedItemInfos); ~FeedCacheItem(); const char* GetUrl() { return m_szUrl; } int GetCacheTimeSec() { return m_iCacheTimeSec; } const char* GetCacheId() { return m_szCacheId; } time_t GetLastUsage() { return m_tLastUsage; } void SetLastUsage(time_t tLastUsage) { m_tLastUsage = tLastUsage; } FeedItemInfos* GetFeedItemInfos() { return m_pFeedItemInfos; } }; typedef std::deque FeedCache; private: Feeds m_Feeds; ActiveDownloads m_ActiveDownloads; FeedHistory m_FeedHistory; Mutex m_mutexDownloads; UrlCoordinatorObserver m_UrlCoordinatorObserver; bool m_bForce; bool m_bSave; FeedCache m_FeedCache; void StartFeedDownload(FeedInfo* pFeedInfo, bool bForce); void FeedCompleted(FeedDownloader* pFeedDownloader); void FilterFeed(FeedInfo* pFeedInfo, FeedItemInfos* pFeedItemInfos); void ProcessFeed(FeedInfo* pFeedInfo, FeedItemInfos* pFeedItemInfos); void DownloadItem(FeedInfo* pFeedInfo, FeedItemInfo* pFeedItemInfo); void ResetHangingDownloads(); void UrlCoordinatorUpdate(Subject* pCaller, void* pAspect); void CleanupHistory(); void CleanupCache(); void CheckSaveFeeds(); public: FeedCoordinator(); virtual ~FeedCoordinator(); virtual void Run(); virtual void Stop(); void Update(Subject* pCaller, void* pAspect); void AddFeed(FeedInfo* pFeedInfo); bool PreviewFeed(const char* szName, const char* szUrl, const char* szFilter, bool bPauseNzb, const char* szCategory, int iPriority, int iCacheTimeSec, const char* szCacheId, FeedItemInfos** ppFeedItemInfos); bool ViewFeed(int iID, FeedItemInfos** ppFeedItemInfos); void FetchFeed(int iID); bool HasActiveDownloads(); Feeds* GetFeeds() { return &m_Feeds; } void LogDebugInfo(); }; class FeedDownloader : public WebDownloader { private: FeedInfo* m_pFeedInfo; public: void SetFeedInfo(FeedInfo* pFeedInfo) { m_pFeedInfo = pFeedInfo; } FeedInfo* GetFeedInfo() { return m_pFeedInfo; } }; #endif nzbget-12.0+dfsg/FeedFile.cpp000066400000000000000000000333241226450633000160210ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 922 $ * $Date: 2013-12-19 21:28:50 +0100 (Thu, 19 Dec 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #ifdef WIN32 #include #import named_guids using namespace MSXML; #else #include #include #include #include #endif #include "nzbget.h" #include "FeedFile.h" #include "Log.h" #include "DownloadInfo.h" #include "Options.h" #include "Util.h" extern Options* g_pOptions; FeedFile::FeedFile(const char* szFileName) { debug("Creating FeedFile"); m_szFileName = strdup(szFileName); m_pFeedItemInfos = new FeedItemInfos(); m_pFeedItemInfos->Retain(); #ifndef WIN32 m_pFeedItemInfo = NULL; m_szTagContent = NULL; m_iTagContentLen = 0; #endif } FeedFile::~FeedFile() { debug("Destroying FeedFile"); // Cleanup free(m_szFileName); m_pFeedItemInfos->Release(); #ifndef WIN32 delete m_pFeedItemInfo; free(m_szTagContent); #endif } void FeedFile::LogDebugInfo() { debug(" FeedFile %s", m_szFileName); } void FeedFile::AddItem(FeedItemInfo* pFeedItemInfo) { m_pFeedItemInfos->Add(pFeedItemInfo); } void FeedFile::ParseSubject(FeedItemInfo* pFeedItemInfo) { // if title has quatation marks we use only part within quatation marks char* p = (char*)pFeedItemInfo->GetTitle(); char* start = strchr(p, '\"'); if (start) { start++; char* end = strchr(start + 1, '\"'); if (end) { int len = (int)(end - start); char* point = strchr(start + 1, '.'); if (point && point < end) { char* filename = (char*)malloc(len + 1); strncpy(filename, start, len); filename[len] = '\0'; char* ext = strrchr(filename, '.'); if (ext && !strcasecmp(ext, ".par2")) { *ext = '\0'; } pFeedItemInfo->SetFilename(filename); free(filename); return; } } } pFeedItemInfo->SetFilename(pFeedItemInfo->GetTitle()); } #ifdef WIN32 FeedFile* FeedFile::Create(const char* szFileName) { CoInitialize(NULL); HRESULT hr; MSXML::IXMLDOMDocumentPtr doc; hr = doc.CreateInstance(MSXML::CLSID_DOMDocument); if (FAILED(hr)) { return NULL; } // Load the XML document file... doc->put_resolveExternals(VARIANT_FALSE); doc->put_validateOnParse(VARIANT_FALSE); doc->put_async(VARIANT_FALSE); // 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); VARIANT_BOOL success = doc->load(v); if (success == VARIANT_FALSE) { _bstr_t r(doc->GetparseError()->reason); const char* szErrMsg = r; error("Error parsing rss feed: %s", szErrMsg); return NULL; } FeedFile* pFile = new FeedFile(szFileName); if (!pFile->ParseFeed(doc)) { delete pFile; pFile = NULL; } return pFile; } void FeedFile::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 FeedFile::ParseFeed(IUnknown* nzb) { MSXML::IXMLDOMDocumentPtr doc = nzb; MSXML::IXMLDOMNodePtr root = doc->documentElement; MSXML::IXMLDOMNodeListPtr itemList = root->selectNodes("/rss/channel/item"); for (int i = 0; i < itemList->Getlength(); i++) { MSXML::IXMLDOMNodePtr node = itemList->Getitem(i); FeedItemInfo* pFeedItemInfo = new FeedItemInfo(); AddItem(pFeedItemInfo); MSXML::IXMLDOMNodePtr tag; MSXML::IXMLDOMNodePtr attr; // Debian 6 tag = node->selectSingleNode("title"); if (!tag) { // bad rss feed return false; } _bstr_t title(tag->Gettext()); pFeedItemInfo->SetTitle(title); ParseSubject(pFeedItemInfo); // Wed, 26 Jun 2013 00:02:54 -0600 tag = node->selectSingleNode("pubDate"); if (tag) { _bstr_t time(tag->Gettext()); time_t unixtime = WebUtil::ParseRfc822DateTime(time); if (unixtime > 0) { pFeedItemInfo->SetTime(unixtime); } } // Movies > HD tag = node->selectSingleNode("category"); if (tag) { _bstr_t category(tag->Gettext()); pFeedItemInfo->SetCategory(category); } // long text tag = node->selectSingleNode("description"); if (tag) { _bstr_t description(tag->Gettext()); pFeedItemInfo->SetDescription(description); } // tag = node->selectSingleNode("enclosure"); if (tag) { attr = tag->Getattributes()->getNamedItem("url"); if (attr) { _bstr_t url(attr->Gettext()); pFeedItemInfo->SetUrl(url); } attr = tag->Getattributes()->getNamedItem("length"); if (attr) { _bstr_t size(attr->Gettext()); long long lSize = atoll(size); pFeedItemInfo->SetSize(lSize); } } if (!pFeedItemInfo->GetUrl()) { // https://nzb.org/fetch/334534ce/4364564564 tag = node->selectSingleNode("link"); if (!tag) { // bad rss feed return false; } _bstr_t link(tag->Gettext()); pFeedItemInfo->SetUrl(link); } // newznab special // if (pFeedItemInfo->GetSize() == 0) { tag = node->selectSingleNode("newznab:attr[@name='size']"); if (tag) { attr = tag->Getattributes()->getNamedItem("value"); if (attr) { _bstr_t size(attr->Gettext()); long long lSize = atoll(size); pFeedItemInfo->SetSize(lSize); } } } // tag = node->selectSingleNode("newznab:attr[@name='imdb']"); if (tag) { attr = tag->Getattributes()->getNamedItem("value"); if (attr) { _bstr_t val(attr->Gettext()); int iVal = atoi(val); pFeedItemInfo->SetImdbId(iVal); } } // tag = node->selectSingleNode("newznab:attr[@name='rageid']"); if (tag) { attr = tag->Getattributes()->getNamedItem("value"); if (attr) { _bstr_t val(attr->Gettext()); int iVal = atoi(val); pFeedItemInfo->SetRageId(iVal); } } // // tag = node->selectSingleNode("newznab:attr[@name='episode']"); if (tag) { attr = tag->Getattributes()->getNamedItem("value"); if (attr) { _bstr_t val(attr->Gettext()); pFeedItemInfo->SetEpisode(val); } } // // tag = node->selectSingleNode("newznab:attr[@name='season']"); if (tag) { attr = tag->Getattributes()->getNamedItem("value"); if (attr) { _bstr_t val(attr->Gettext()); pFeedItemInfo->SetSeason(val); } } MSXML::IXMLDOMNodeListPtr itemList = node->selectNodes("newznab:attr"); for (int i = 0; i < itemList->Getlength(); i++) { MSXML::IXMLDOMNodePtr node = itemList->Getitem(i); MSXML::IXMLDOMNodePtr name = node->Getattributes()->getNamedItem("name"); MSXML::IXMLDOMNodePtr value = node->Getattributes()->getNamedItem("value"); if (name && value) { _bstr_t name(name->Gettext()); _bstr_t val(value->Gettext()); pFeedItemInfo->GetAttributes()->Add(name, val); } } } return true; } #else FeedFile* FeedFile::Create(const char* szFileName) { FeedFile* pFile = new FeedFile(szFileName); xmlSAXHandler SAX_handler = {0}; SAX_handler.startElement = reinterpret_cast(SAX_StartElement); SAX_handler.endElement = reinterpret_cast(SAX_EndElement); SAX_handler.characters = reinterpret_cast(SAX_characters); SAX_handler.error = reinterpret_cast(SAX_error); SAX_handler.getEntity = reinterpret_cast(SAX_getEntity); pFile->m_bIgnoreNextError = false; int ret = xmlSAXUserParseFile(&SAX_handler, pFile, szFileName); if (ret != 0) { error("Failed to parse rss feed"); delete pFile; pFile = NULL; } return pFile; } void FeedFile::Parse_StartElement(const char *name, const char **atts) { ResetTagContent(); if (!strcmp("item", name)) { delete m_pFeedItemInfo; m_pFeedItemInfo = new FeedItemInfo(); } else if (!strcmp("enclosure", name) && m_pFeedItemInfo) { // for (; *atts; atts+=2) { if (!strcmp("url", atts[0])) { char* szUrl = strdup(atts[1]); WebUtil::XmlDecode(szUrl); m_pFeedItemInfo->SetUrl(szUrl); free(szUrl); } else if (!strcmp("length", atts[0])) { long long lSize = atoll(atts[1]); m_pFeedItemInfo->SetSize(lSize); } } } else if (m_pFeedItemInfo && !strcmp("newznab:attr", name) && atts[0] && atts[1] && atts[2] && atts[3] && !strcmp("name", atts[0]) && !strcmp("value", atts[2])) { m_pFeedItemInfo->GetAttributes()->Add(atts[1], atts[3]); // if (m_pFeedItemInfo->GetSize() == 0 && !strcmp("size", atts[1])) { long long lSize = atoll(atts[3]); m_pFeedItemInfo->SetSize(lSize); } // else if (!strcmp("imdb", atts[1])) { m_pFeedItemInfo->SetImdbId(atoi(atts[3])); } // else if (!strcmp("rageid", atts[1])) { m_pFeedItemInfo->SetRageId(atoi(atts[3])); } // // else if (!strcmp("episode", atts[1])) { m_pFeedItemInfo->SetEpisode(atts[3]); } // // else if (!strcmp("season", atts[1])) { m_pFeedItemInfo->SetSeason(atts[3]); } } } void FeedFile::Parse_EndElement(const char *name) { if (!strcmp("item", name)) { // Close the file element, add the new file to file-list AddItem(m_pFeedItemInfo); m_pFeedItemInfo = NULL; } else if (!strcmp("title", name) && m_pFeedItemInfo) { m_pFeedItemInfo->SetTitle(m_szTagContent); ResetTagContent(); ParseSubject(m_pFeedItemInfo); } else if (!strcmp("link", name) && m_pFeedItemInfo && (!m_pFeedItemInfo->GetUrl() || strlen(m_pFeedItemInfo->GetUrl()) == 0)) { m_pFeedItemInfo->SetUrl(m_szTagContent); ResetTagContent(); } else if (!strcmp("category", name) && m_pFeedItemInfo) { m_pFeedItemInfo->SetCategory(m_szTagContent); ResetTagContent(); } else if (!strcmp("description", name) && m_pFeedItemInfo) { m_pFeedItemInfo->SetDescription(m_szTagContent); ResetTagContent(); } else if (!strcmp("pubDate", name) && m_pFeedItemInfo) { time_t unixtime = WebUtil::ParseRfc822DateTime(m_szTagContent); if (unixtime > 0) { m_pFeedItemInfo->SetTime(unixtime); } ResetTagContent(); } } void FeedFile::Parse_Content(const char *buf, int len) { m_szTagContent = (char*)realloc(m_szTagContent, m_iTagContentLen + len + 1); strncpy(m_szTagContent + m_iTagContentLen, buf, len); m_iTagContentLen += len; m_szTagContent[m_iTagContentLen] = '\0'; } void FeedFile::ResetTagContent() { free(m_szTagContent); m_szTagContent = NULL; m_iTagContentLen = 0; } void FeedFile::SAX_StartElement(FeedFile* pFile, const char *name, const char **atts) { pFile->Parse_StartElement(name, atts); } void FeedFile::SAX_EndElement(FeedFile* pFile, const char *name) { pFile->Parse_EndElement(name); } void FeedFile::SAX_characters(FeedFile* pFile, const char * xmlstr, int len) { char* str = (char*)xmlstr; // trim starting blanks int off = 0; for (int i = 0; i < len; i++) { char ch = str[i]; if (ch == ' ' || ch == 10 || ch == 13 || ch == 9) { off++; } else { break; } } int newlen = len - off; // trim ending blanks for (int i = len - 1; i >= off; i--) { char ch = str[i]; if (ch == ' ' || ch == 10 || ch == 13 || ch == 9) { newlen--; } else { break; } } if (newlen > 0) { // interpret tag content pFile->Parse_Content(str + off, newlen); } } void* FeedFile::SAX_getEntity(FeedFile* pFile, const char * name) { xmlEntityPtr e = xmlGetPredefinedEntity((xmlChar* )name); if (!e) { warn("entity not found"); pFile->m_bIgnoreNextError = true; } return e; } void FeedFile::SAX_error(FeedFile* pFile, const char *msg, ...) { if (pFile->m_bIgnoreNextError) { pFile->m_bIgnoreNextError = false; return; } va_list argp; va_start(argp, msg); char szErrMsg[1024]; vsnprintf(szErrMsg, sizeof(szErrMsg), msg, argp); szErrMsg[1024-1] = '\0'; va_end(argp); // remove trailing CRLF for (char* pend = szErrMsg + strlen(szErrMsg) - 1; pend >= szErrMsg && (*pend == '\n' || *pend == '\r' || *pend == ' '); pend--) *pend = '\0'; error("Error parsing rss feed: %s", szErrMsg); } #endif nzbget-12.0+dfsg/FeedFile.h000066400000000000000000000042521226450633000154640ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 922 $ * $Date: 2013-12-19 21:28:50 +0100 (Thu, 19 Dec 2013) $ * */ #ifndef FEEDFILE_H #define FEEDFILE_H #include #include "FeedInfo.h" class FeedFile { private: FeedItemInfos* m_pFeedItemInfos; char* m_szFileName; FeedFile(const char* szFileName); void AddItem(FeedItemInfo* pFeedItemInfo); void ParseSubject(FeedItemInfo* pFeedItemInfo); #ifdef WIN32 bool ParseFeed(IUnknown* nzb); static void EncodeURL(const char* szFilename, char* szURL); #else FeedItemInfo* m_pFeedItemInfo; char* m_szTagContent; int m_iTagContentLen; bool m_bIgnoreNextError; static void SAX_StartElement(FeedFile* pFile, const char *name, const char **atts); static void SAX_EndElement(FeedFile* pFile, const char *name); static void SAX_characters(FeedFile* pFile, const char * xmlstr, int len); static void* SAX_getEntity(FeedFile* pFile, const char * name); static void SAX_error(FeedFile* pFile, const char *msg, ...); void Parse_StartElement(const char *name, const char **atts); void Parse_EndElement(const char *name); void Parse_Content(const char *buf, int len); void ResetTagContent(); #endif public: virtual ~FeedFile(); static FeedFile* Create(const char* szFileName); FeedItemInfos* GetFeedItemInfos() { return m_pFeedItemInfos; } void LogDebugInfo(); }; #endif nzbget-12.0+dfsg/FeedFilter.cpp000066400000000000000000000623401226450633000163670ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 915 $ * $Date: 2013-12-03 22:39:18 +0100 (Tue, 03 Dec 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #include #include "nzbget.h" #include "Log.h" #include "DownloadInfo.h" #include "Util.h" #include "FeedFilter.h" FeedFilter::Term::Term() { m_szField = NULL; m_szParam = NULL; m_bFloat = false; m_iIntParam = 0; m_fFloatParam = 0.0; m_pRegEx = NULL; m_pRefValues = NULL; } FeedFilter::Term::~Term() { free(m_szField); free(m_szParam); delete m_pRegEx; } bool FeedFilter::Term::Match(FeedItemInfo* pFeedItemInfo) { const char* szStrValue = NULL; long long iIntValue = 0; if (!GetFieldData(m_szField, pFeedItemInfo, &szStrValue, &iIntValue)) { return false; } bool bMatch = MatchValue(szStrValue, iIntValue); if (m_bPositive != bMatch) { return false; } return true; } bool FeedFilter::Term::MatchValue(const char* szStrValue, long long iIntValue) { double fFloatValue = (double)iIntValue; char szIntBuf[100]; if (m_eCommand < fcEqual && !szStrValue) { snprintf(szIntBuf, 100, "%lld", iIntValue); szIntBuf[100-1] = '\0'; szStrValue = szIntBuf; } else if (m_eCommand >= fcEqual && szStrValue) { fFloatValue = atof(szStrValue); iIntValue = (long long)fFloatValue; } switch (m_eCommand) { case fcText: return MatchText(szStrValue); case fcRegex: return MatchRegex(szStrValue); case fcEqual: return m_bFloat ? fFloatValue == m_fFloatParam : iIntValue == m_iIntParam; case fcLess: return m_bFloat ? fFloatValue < m_fFloatParam : iIntValue < m_iIntParam; case fcLessEqual: return m_bFloat ? fFloatValue <= m_fFloatParam : iIntValue <= m_iIntParam; case fcGreater: return m_bFloat ? fFloatValue > m_fFloatParam : iIntValue > m_iIntParam; case fcGreaterEqual: return m_bFloat ? fFloatValue >= m_fFloatParam : iIntValue >= m_iIntParam; default: return false; } } bool FeedFilter::Term::MatchText(const char* szStrValue) { const char* WORD_SEPARATORS = " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; // first check if we should make word-search or substring-search int iParamLen = strlen(m_szParam); bool bSubstr = iParamLen >= 2 && m_szParam[0] == '*' && m_szParam[iParamLen-1] == '*'; if (!bSubstr) { for (const char* p = m_szParam; *p; p++) { char ch = *p; if (strchr(WORD_SEPARATORS, ch) && ch != '*' && ch != '?' && ch != '#') { bSubstr = true; break; } } } bool bMatch = false; if (!bSubstr) { // Word-search // split szStrValue into tokens char* szStrValue2 = strdup(szStrValue); char* saveptr; char* szWord = strtok_r(szStrValue2, WORD_SEPARATORS, &saveptr); while (szWord) { szWord = Util::Trim(szWord); WildMask mask(m_szParam, m_pRefValues != NULL); bMatch = *szWord && mask.Match(szWord); if (bMatch) { FillWildMaskRefValues(szWord, &mask, 0); break; } szWord = strtok_r(NULL, WORD_SEPARATORS, &saveptr); } free(szStrValue2); } else { // Substring-search int iRefOffset = 1; const char* szFormat = "*%s*"; if (iParamLen >= 2 && m_szParam[0] == '*' && m_szParam[iParamLen-1] == '*') { szFormat = "%s"; iRefOffset = 0; } else if (iParamLen >= 1 && m_szParam[0] == '*') { szFormat = "%s*"; iRefOffset = 0; } else if (iParamLen >= 1 && m_szParam[iParamLen-1] == '*') { szFormat = "*%s"; } int iMaskLen = strlen(m_szParam) + 2 + 1; char* szMask = (char*)malloc(iMaskLen); snprintf(szMask, iMaskLen, szFormat, m_szParam); szMask[iMaskLen-1] = '\0'; WildMask mask(szMask, m_pRefValues != NULL); bMatch = mask.Match(szStrValue); if (bMatch) { FillWildMaskRefValues(szStrValue, &mask, iRefOffset); } free(szMask); } return bMatch; } bool FeedFilter::Term::MatchRegex(const char* szStrValue) { if (!m_pRegEx) { m_pRegEx = new RegEx(m_szParam, m_pRefValues == NULL ? 0 : 100); } bool bFound = m_pRegEx->Match(szStrValue); if (bFound) { FillRegExRefValues(szStrValue, m_pRegEx); } return bFound; } bool FeedFilter::Term::Compile(char* szToken) { debug("Token: %s", szToken); char ch = szToken[0]; m_bPositive = ch != '-'; if (ch == '-' || ch == '+') { szToken++; ch = szToken[0]; } char ch2= szToken[1]; if ((ch == '(' || ch == ')' || ch == '|') && (ch2 == ' ' || ch2 == '\0')) { switch (ch) { case '(': m_eCommand = fcOpeningBrace; return true; case ')': m_eCommand = fcClosingBrace; return true; case '|': m_eCommand = fcOrOperator; return true; } } char *szField = NULL; m_eCommand = fcText; char* szColon = NULL; if (ch != '@' && ch != '$' && ch != '<' && ch != '>' && ch != '=') { szColon = strchr(szToken, ':'); } if (szColon) { szField = szToken; szColon[0] = '\0'; szToken = szColon + 1; ch = szToken[0]; } if (ch == '\0') { return false; } ch2= szToken[1]; if (ch == '@') { m_eCommand = fcText; szToken++; } else if (ch == '$') { m_eCommand = fcRegex; szToken++; } else if (ch == '=') { m_eCommand = fcEqual; szToken++; } else if (ch == '<' && ch2 == '=') { m_eCommand = fcLessEqual; szToken += 2; } else if (ch == '>' && ch2 == '=') { m_eCommand = fcGreaterEqual; szToken += 2; } else if (ch == '<') { m_eCommand = fcLess; szToken++; } else if (ch == '>') { m_eCommand = fcGreater; szToken++; } debug("%s, Field: %s, Command: %i, Param: %s", (m_bPositive ? "Positive" : "Negative"), szField, m_eCommand, szToken); const char* szStrValue; long long iIntValue; if (!GetFieldData(szField, NULL, &szStrValue, &iIntValue)) { return false; } if (szField && !ParseParam(szField, szToken)) { return false; } m_szField = szField ? strdup(szField) : NULL; m_szParam = strdup(szToken); return true; } /* * If pFeedItemInfo is NULL, only field type info is returned */ bool FeedFilter::Term::GetFieldData(const char* szField, FeedItemInfo* pFeedItemInfo, const char** StrValue, long long* IntValue) { *StrValue = NULL; *IntValue = 0; if (!szField || !strcasecmp(szField, "title")) { *StrValue = pFeedItemInfo ? pFeedItemInfo->GetTitle() : NULL; return true; } else if (!strcasecmp(szField, "filename")) { *StrValue = pFeedItemInfo ? pFeedItemInfo->GetFilename() : NULL; return true; } else if (!strcasecmp(szField, "category")) { *StrValue = pFeedItemInfo ? pFeedItemInfo->GetCategory() : NULL; return true; } else if (!strcasecmp(szField, "link") || !strcasecmp(szField, "url")) { *StrValue = pFeedItemInfo ? pFeedItemInfo->GetUrl() : NULL; return true; } else if (!strcasecmp(szField, "size")) { *IntValue = pFeedItemInfo ? pFeedItemInfo->GetSize() : 0; return true; } else if (!strcasecmp(szField, "age")) { *IntValue = pFeedItemInfo ? time(NULL) - pFeedItemInfo->GetTime() : 0; return true; } else if (!strcasecmp(szField, "imdbid")) { *IntValue = pFeedItemInfo ? pFeedItemInfo->GetImdbId() : 0; return true; } else if (!strcasecmp(szField, "rageid")) { *IntValue = pFeedItemInfo ? pFeedItemInfo->GetRageId() : 0; return true; } else if (!strcasecmp(szField, "description")) { *StrValue = pFeedItemInfo ? pFeedItemInfo->GetDescription() : NULL; return true; } else if (!strcasecmp(szField, "season")) { *IntValue = pFeedItemInfo ? pFeedItemInfo->GetSeasonNum() : 0; return true; } else if (!strcasecmp(szField, "episode")) { *IntValue = pFeedItemInfo ? pFeedItemInfo->GetEpisodeNum() : 0; return true; } else if (!strcasecmp(szField, "priority")) { *IntValue = pFeedItemInfo ? pFeedItemInfo->GetPriority() : 0; return true; } else if (!strcasecmp(szField, "dupekey")) { *StrValue = pFeedItemInfo ? pFeedItemInfo->GetDupeKey() : NULL; return true; } else if (!strcasecmp(szField, "dupescore")) { *IntValue = pFeedItemInfo ? pFeedItemInfo->GetDupeScore() : 0; return true; } else if (!strncasecmp(szField, "attr-", 5)) { if (pFeedItemInfo) { FeedItemInfo::Attr* pAttr = pFeedItemInfo->GetAttributes()->Find(szField + 5); *StrValue = pAttr ? pAttr->GetValue() : NULL; } return true; } return false; } bool FeedFilter::Term::ParseParam(const char* szField, const char* szParam) { if (!strcasecmp(szField, "size")) { return ParseSizeParam(szParam); } else if (!strcasecmp(szField, "age")) { return ParseAgeParam(szParam); } else if (m_eCommand >= fcEqual) { return ParseNumericParam(szParam); } return true; } bool FeedFilter::Term::ParseSizeParam(const char* szParam) { double fParam = atof(szParam); const char* p; for (p = szParam; *p && ((*p >= '0' && *p <='9') || *p == '.'); p++) ; if (*p) { if (!strcasecmp(p, "K") || !strcasecmp(p, "KB")) { m_iIntParam = (long long)(fParam*1024); } else if (!strcasecmp(p, "M") || !strcasecmp(p, "MB")) { m_iIntParam = (long long)(fParam*1024*1024); } else if (!strcasecmp(p, "G") || !strcasecmp(p, "GB")) { m_iIntParam = (long long)(fParam*1024*1024*1024); } else { return false; } } else { m_iIntParam = (long long)fParam; } return true; } bool FeedFilter::Term::ParseAgeParam(const char* szParam) { double fParam = atof(szParam); const char* p; for (p = szParam; *p && ((*p >= '0' && *p <='9') || *p == '.'); p++) ; if (*p) { if (!strcasecmp(p, "m")) { // minutes m_iIntParam = (long long)(fParam*60); } else if (!strcasecmp(p, "h")) { // hours m_iIntParam = (long long)(fParam*60*60); } else if (!strcasecmp(p, "d")) { // days m_iIntParam = (long long)(fParam*60*60*24); } else { return false; } } else { // days by default m_iIntParam = (long long)(fParam*60*60*24); } return true; } bool FeedFilter::Term::ParseNumericParam(const char* szParam) { m_fFloatParam = atof(szParam); m_iIntParam = (long long)m_fFloatParam; m_bFloat = strchr(szParam, '.'); const char* p; for (p = szParam; *p && ((*p >= '0' && *p <='9') || *p == '.') ; p++) ; if (*p) { return false; } return true; } void FeedFilter::Term::FillWildMaskRefValues(const char* szStrValue, WildMask* pMask, int iRefOffset) { if (!m_pRefValues) { return; } for (int i = iRefOffset; i < pMask->GetMatchCount(); i++) { int iLen = pMask->GetMatchLen(i); char* szValue = (char*)malloc(iLen + 1); strncpy(szValue, szStrValue + pMask->GetMatchStart(i), iLen); szValue[iLen] = '\0'; m_pRefValues->push_back(szValue); } } void FeedFilter::Term::FillRegExRefValues(const char* szStrValue, RegEx* pRegEx) { if (!m_pRefValues) { return; } for (int i = 1; i < pRegEx->GetMatchCount(); i++) { int iLen = pRegEx->GetMatchLen(i); char* szValue = (char*)malloc(iLen + 1); strncpy(szValue, szStrValue + pRegEx->GetMatchStart(i), iLen); szValue[iLen] = '\0'; m_pRefValues->push_back(szValue); } } FeedFilter::Rule::Rule() { m_eCommand = frAccept; m_bIsValid = false; m_szCategory = NULL; m_iPriority = 0; m_iAddPriority = 0; m_bPause = false; m_szDupeKey = NULL; m_szAddDupeKey = NULL; m_iDupeScore = 0; m_iAddDupeScore = 0; m_eDupeMode = dmScore; m_szRageId = NULL; m_szSeries = NULL; m_bHasCategory = false; m_bHasPriority = false; m_bHasAddPriority = false; m_bHasPause = false; m_bHasDupeScore = false; m_bHasAddDupeScore = false; m_bHasDupeKey = false; m_bHasAddDupeKey = false; m_bHasDupeMode = false; m_bHasRageId = false; m_bHasSeries = false; m_bPatCategory = false; m_bPatDupeKey = false; m_bPatAddDupeKey = false; m_szPatCategory = NULL; m_szPatDupeKey = NULL; m_szPatAddDupeKey = NULL; } FeedFilter::Rule::~Rule() { free(m_szCategory); free(m_szDupeKey); free(m_szAddDupeKey); free(m_szRageId); free(m_szSeries); free(m_szPatCategory); free(m_szPatDupeKey); free(m_szPatAddDupeKey); for (TermList::iterator it = m_Terms.begin(); it != m_Terms.end(); it++) { delete *it; } for (RefValues::iterator it = m_RefValues.begin(); it != m_RefValues.end(); it++) { delete *it; } } void FeedFilter::Rule::Compile(char* szRule) { debug("Compiling rule: %s", szRule); m_bIsValid = true; char* szFilter3 = Util::Trim(szRule); char* szTerm = CompileCommand(szFilter3); if (!szTerm) { m_bIsValid = false; return; } if (m_eCommand == frComment) { return; } szTerm = Util::Trim(szTerm); for (char* p = szTerm; *p && m_bIsValid; p++) { char ch = *p; if (ch == ' ') { *p = '\0'; m_bIsValid = CompileTerm(szTerm); szTerm = p + 1; while (*szTerm == ' ') szTerm++; p = szTerm; } } m_bIsValid = m_bIsValid && CompileTerm(szTerm); if (m_bIsValid && m_bPatCategory) { m_szPatCategory = m_szCategory; m_szCategory = NULL; } if (m_bIsValid && m_bPatDupeKey) { m_szPatDupeKey = m_szDupeKey; m_szDupeKey = NULL; } if (m_bIsValid && m_bPatAddDupeKey) { m_szPatAddDupeKey = m_szAddDupeKey; m_szAddDupeKey = NULL; } } /* Checks if the rule starts with command and compiles it. * Returns a pointer to the next (first) term or NULL in a case of compilation error. */ char* FeedFilter::Rule::CompileCommand(char* szRule) { if (!strncasecmp(szRule, "A:", 2) || !strncasecmp(szRule, "Accept:", 7) || !strncasecmp(szRule, "A(", 2) || !strncasecmp(szRule, "Accept(", 7)) { m_eCommand = frAccept; szRule += szRule[1] == ':' || szRule[1] == '(' ? 2 : 7; } else if (!strncasecmp(szRule, "O(", 2) || !strncasecmp(szRule, "Options(", 8)) { m_eCommand = frOptions; szRule += szRule[1] == ':' || szRule[1] == '(' ? 2 : 8; } else if (!strncasecmp(szRule, "R:", 2) || !strncasecmp(szRule, "Reject:", 7)) { m_eCommand = frReject; szRule += szRule[1] == ':' || szRule[1] == '(' ? 2 : 7; } else if (!strncasecmp(szRule, "Q:", 2) || !strncasecmp(szRule, "Require:", 8)) { m_eCommand = frRequire; szRule += szRule[1] == ':' || szRule[1] == '(' ? 2 : 8; } else if (*szRule == '#') { m_eCommand = frComment; return szRule; } else { // not a command return szRule; } if ((m_eCommand == frAccept || m_eCommand == frOptions) && szRule[-1] == '(') { szRule = CompileOptions(szRule); } return szRule; } char* FeedFilter::Rule::CompileOptions(char* szRule) { char* p = strchr(szRule, ')'); if (!p) { // error return NULL; } // split command into tokens *p = '\0'; char* saveptr; char* szToken = strtok_r(szRule, ",", &saveptr); while (szToken) { szToken = Util::Trim(szToken); if (*szToken) { char* szOption = szToken; const char* szValue = ""; char* szColon = strchr(szToken, ':'); if (szColon) { *szColon = '\0'; szValue = Util::Trim(szColon + 1); } if (!strcasecmp(szOption, "category") || !strcasecmp(szOption, "cat") || !strcasecmp(szOption, "c")) { m_bHasCategory = true; free(m_szCategory); m_szCategory = strdup(szValue); m_bPatCategory = strstr(szValue, "${"); } else if (!strcasecmp(szOption, "pause") || !strcasecmp(szOption, "p")) { m_bHasPause = true; m_bPause = !*szValue || !strcasecmp(szValue, "yes") || !strcasecmp(szValue, "y"); if (!m_bPause && !(!strcasecmp(szValue, "no") || !strcasecmp(szValue, "n"))) { // error return NULL; } } else if (!strcasecmp(szOption, "priority") || !strcasecmp(szOption, "pr") || !strcasecmp(szOption, "r")) { if (!strchr("0123456789-+", *szValue)) { // error return NULL; } m_bHasPriority = true; m_iPriority = atoi(szValue); } else if (!strcasecmp(szOption, "priority+") || !strcasecmp(szOption, "pr+") || !strcasecmp(szOption, "r+")) { if (!strchr("0123456789-+", *szValue)) { // error return NULL; } m_bHasAddPriority = true; m_iAddPriority = atoi(szValue); } else if (!strcasecmp(szOption, "dupescore") || !strcasecmp(szOption, "ds") || !strcasecmp(szOption, "s")) { if (!strchr("0123456789-+", *szValue)) { // error return NULL; } m_bHasDupeScore = true; m_iDupeScore = atoi(szValue); } else if (!strcasecmp(szOption, "dupescore+") || !strcasecmp(szOption, "ds+") || !strcasecmp(szOption, "s+")) { if (!strchr("0123456789-+", *szValue)) { // error return NULL; } m_bHasAddDupeScore = true; m_iAddDupeScore = atoi(szValue); } else if (!strcasecmp(szOption, "dupekey") || !strcasecmp(szOption, "dk") || !strcasecmp(szOption, "k")) { m_bHasDupeKey = true; free(m_szDupeKey); m_szDupeKey = strdup(szValue); m_bPatDupeKey = strstr(szValue, "${"); } else if (!strcasecmp(szOption, "dupekey+") || !strcasecmp(szOption, "dk+") || !strcasecmp(szOption, "k+")) { m_bHasAddDupeKey = true; free(m_szAddDupeKey); m_szAddDupeKey = strdup(szValue); m_bPatAddDupeKey = strstr(szValue, "${"); } else if (!strcasecmp(szOption, "dupemode") || !strcasecmp(szOption, "dm") || !strcasecmp(szOption, "m")) { m_bHasDupeMode = true; if (!strcasecmp(szValue, "score") || !strcasecmp(szValue, "s")) { m_eDupeMode = dmScore; } else if (!strcasecmp(szValue, "all") || !strcasecmp(szValue, "a")) { m_eDupeMode = dmAll; } else if (!strcasecmp(szValue, "force") || !strcasecmp(szValue, "f")) { m_eDupeMode = dmForce; } else { // error return NULL; } } else if (!strcasecmp(szOption, "rageid")) { m_bHasRageId = true; free(m_szRageId); m_szRageId = strdup(szValue); } else if (!strcasecmp(szOption, "series")) { m_bHasSeries = true; free(m_szSeries); m_szSeries = strdup(szValue); } // for compatibility with older version we support old commands too else if (!strcasecmp(szOption, "paused") || !strcasecmp(szOption, "unpaused")) { m_bHasPause = true; m_bPause = !strcasecmp(szOption, "paused"); } else if (strchr("0123456789-+", *szOption)) { m_bHasPriority = true; m_iPriority = atoi(szOption); } else { m_bHasCategory = true; free(m_szCategory); m_szCategory = strdup(szOption); } } szToken = strtok_r(NULL, ",", &saveptr); } szRule = p + 1; if (*szRule == ':') { szRule++; } return szRule; } bool FeedFilter::Rule::CompileTerm(char* szTerm) { Term* pTerm = new Term(); pTerm->SetRefValues(m_bPatCategory || m_bPatDupeKey || m_bPatAddDupeKey ? &m_RefValues : NULL); if (pTerm->Compile(szTerm)) { m_Terms.push_back(pTerm); return true; } else { delete pTerm; return false; } } bool FeedFilter::Rule::Match(FeedItemInfo* pFeedItemInfo) { for (RefValues::iterator it = m_RefValues.begin(); it != m_RefValues.end(); it++) { delete *it; } m_RefValues.clear(); if (!MatchExpression(pFeedItemInfo)) { return false; } if (m_bPatCategory) { ExpandRefValues(pFeedItemInfo, &m_szCategory, m_szPatCategory); } if (m_bPatDupeKey) { ExpandRefValues(pFeedItemInfo, &m_szDupeKey, m_szPatDupeKey); } if (m_bPatAddDupeKey) { ExpandRefValues(pFeedItemInfo, &m_szAddDupeKey, m_szPatAddDupeKey); } return true; } bool FeedFilter::Rule::MatchExpression(FeedItemInfo* pFeedItemInfo) { char* expr = (char*)malloc(m_Terms.size() + 1); int index = 0; for (TermList::iterator it = m_Terms.begin(); it != m_Terms.end(); it++, index++) { Term* pTerm = *it; switch (pTerm->GetCommand()) { case fcOpeningBrace: expr[index] = '('; break; case fcClosingBrace: expr[index] = ')'; break; case fcOrOperator: expr[index] = '|'; break; default: expr[index] = pTerm->Match(pFeedItemInfo) ? 'T' : 'F'; break; } } expr[index] = '\0'; // reduce result tree to one element (may be longer if expression has syntax errors) for (int iOldLen = 0, iNewLen = strlen(expr); iNewLen != iOldLen; iOldLen = iNewLen, iNewLen = strlen(expr)) { // NOTE: there are no operator priorities. // the order of operators "OR" and "AND" is not defined, they can be checked in any order. // "OR" and "AND" should not be mixed in one group; instead braces should be used to define priorities. Util::ReduceStr(expr, "TT", "T"); Util::ReduceStr(expr, "TF", "F"); Util::ReduceStr(expr, "FT", "F"); Util::ReduceStr(expr, "FF", "F"); Util::ReduceStr(expr, "||", "|"); Util::ReduceStr(expr, "(|", "("); Util::ReduceStr(expr, "|)", ")"); Util::ReduceStr(expr, "T|T", "T"); Util::ReduceStr(expr, "T|F", "T"); Util::ReduceStr(expr, "F|T", "T"); Util::ReduceStr(expr, "F|F", "F"); Util::ReduceStr(expr, "(T)", "T"); Util::ReduceStr(expr, "(F)", "F"); } bool bMatch = *expr && *expr == 'T' && expr[1] == '\0'; free(expr); return bMatch; } void FeedFilter::Rule::ExpandRefValues(FeedItemInfo* pFeedItemInfo, char** pDestStr, char* pPatStr) { free(*pDestStr); *pDestStr = strdup(pPatStr); char* curvalue = *pDestStr; int iAttempts = 0; while (char* dollar = strstr(curvalue, "${")) { iAttempts++; if (iAttempts > 100) { break; // error } char* end = strchr(dollar, '}'); if (!end) { break; // error } int varlen = (int)(end - dollar - 2); char variable[101]; int maxlen = varlen < 100 ? varlen : 100; strncpy(variable, dollar + 2, maxlen); variable[maxlen] = '\0'; const char* varvalue = GetRefValue(pFeedItemInfo, variable); if (!varvalue) { break; // error } int newlen = strlen(varvalue); char* newvalue = (char*)malloc(strlen(curvalue) - varlen - 3 + newlen + 1); strncpy(newvalue, curvalue, dollar - curvalue); strncpy(newvalue + (dollar - curvalue), varvalue, newlen); strcpy(newvalue + (dollar - curvalue) + newlen, end + 1); free(curvalue); curvalue = newvalue; *pDestStr = curvalue; } } const char* FeedFilter::Rule::GetRefValue(FeedItemInfo* pFeedItemInfo, const char* szVarName) { if (!strcasecmp(szVarName, "season")) { pFeedItemInfo->GetSeasonNum(); // needed to parse title return pFeedItemInfo->GetSeason() ? pFeedItemInfo->GetSeason() : ""; } else if (!strcasecmp(szVarName, "episode")) { pFeedItemInfo->GetEpisodeNum(); // needed to parse title return pFeedItemInfo->GetEpisode() ? pFeedItemInfo->GetEpisode() : ""; } int iIndex = atoi(szVarName) - 1; if (iIndex >= 0 && iIndex < (int)m_RefValues.size()) { return m_RefValues[iIndex]; } return NULL; } FeedFilter::FeedFilter(const char* szFilter) { Compile(szFilter); } FeedFilter::~FeedFilter() { for (RuleList::iterator it = m_Rules.begin(); it != m_Rules.end(); it++) { delete *it; } } void FeedFilter::Compile(const char* szFilter) { debug("Compiling filter: %s", szFilter); char* szFilter2 = strdup(szFilter); char* szRule = szFilter2; for (char* p = szRule; *p; p++) { char ch = *p; if (ch == '%') { *p = '\0'; CompileRule(szRule); szRule = p + 1; } } CompileRule(szRule); free(szFilter2); } void FeedFilter::CompileRule(char* szRule) { Rule* pRule = new Rule(); m_Rules.push_back(pRule); pRule->Compile(szRule); } void FeedFilter::Match(FeedItemInfo* pFeedItemInfo) { int index = 0; for (RuleList::iterator it = m_Rules.begin(); it != m_Rules.end(); it++) { Rule* pRule = *it; index++; if (pRule->IsValid()) { bool bMatch = pRule->Match(pFeedItemInfo); switch (pRule->GetCommand()) { case frAccept: case frOptions: if (bMatch) { pFeedItemInfo->SetMatchStatus(FeedItemInfo::msAccepted); pFeedItemInfo->SetMatchRule(index); ApplyOptions(pRule, pFeedItemInfo); if (pRule->GetCommand() == frAccept) { return; } } break; case frReject: if (bMatch) { pFeedItemInfo->SetMatchStatus(FeedItemInfo::msRejected); pFeedItemInfo->SetMatchRule(index); return; } break; case frRequire: if (!bMatch) { pFeedItemInfo->SetMatchStatus(FeedItemInfo::msRejected); pFeedItemInfo->SetMatchRule(index); return; } break; case frComment: break; } } } pFeedItemInfo->SetMatchStatus(FeedItemInfo::msIgnored); pFeedItemInfo->SetMatchRule(0); } void FeedFilter::ApplyOptions(Rule* pRule, FeedItemInfo* pFeedItemInfo) { if (pRule->HasPause()) { pFeedItemInfo->SetPauseNzb(pRule->GetPause()); } if (pRule->HasCategory()) { pFeedItemInfo->SetAddCategory(pRule->GetCategory()); } if (pRule->HasPriority()) { pFeedItemInfo->SetPriority(pRule->GetPriority()); } if (pRule->HasAddPriority()) { pFeedItemInfo->SetPriority(pFeedItemInfo->GetPriority() + pRule->GetAddPriority()); } if (pRule->HasDupeScore()) { pFeedItemInfo->SetDupeScore(pRule->GetDupeScore()); } if (pRule->HasAddDupeScore()) { pFeedItemInfo->SetDupeScore(pFeedItemInfo->GetDupeScore() + pRule->GetAddDupeScore()); } if (pRule->HasRageId() || pRule->HasSeries()) { pFeedItemInfo->BuildDupeKey(pRule->GetRageId(), pRule->GetSeries()); } if (pRule->HasDupeKey()) { pFeedItemInfo->SetDupeKey(pRule->GetDupeKey()); } if (pRule->HasAddDupeKey()) { pFeedItemInfo->AppendDupeKey(pRule->GetAddDupeKey()); } if (pRule->HasDupeMode()) { pFeedItemInfo->SetDupeMode(pRule->GetDupeMode()); } } nzbget-12.0+dfsg/FeedFilter.h000066400000000000000000000123371226450633000160350ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 915 $ * $Date: 2013-12-03 22:39:18 +0100 (Tue, 03 Dec 2013) $ * */ #ifndef FEEDFILTER_H #define FEEDFILTER_H #include "DownloadInfo.h" #include "FeedInfo.h" #include "Util.h" class FeedFilter { private: typedef std::deque RefValues; enum ETermCommand { fcText, fcRegex, fcEqual, fcLess, fcLessEqual, fcGreater, fcGreaterEqual, fcOpeningBrace, fcClosingBrace, fcOrOperator }; class Term { private: bool m_bPositive; char* m_szField; ETermCommand m_eCommand; char* m_szParam; long long m_iIntParam; double m_fFloatParam; bool m_bFloat; RegEx* m_pRegEx; RefValues* m_pRefValues; bool GetFieldData(const char* szField, FeedItemInfo* pFeedItemInfo, const char** StrValue, long long* IntValue); bool ParseParam(const char* szField, const char* szParam); bool ParseSizeParam(const char* szParam); bool ParseAgeParam(const char* szParam); bool ParseNumericParam(const char* szParam); bool MatchValue(const char* szStrValue, long long iIntValue); bool MatchText(const char* szStrValue); bool MatchRegex(const char* szStrValue); void FillWildMaskRefValues(const char* szStrValue, WildMask* pMask, int iRefOffset); void FillRegExRefValues(const char* szStrValue, RegEx* pRegEx); public: Term(); ~Term(); void SetRefValues(RefValues* pRefValues) { m_pRefValues = pRefValues; } bool Compile(char* szToken); bool Match(FeedItemInfo* pFeedItemInfo); ETermCommand GetCommand() { return m_eCommand; } }; typedef std::deque TermList; enum ERuleCommand { frAccept, frReject, frRequire, frOptions, frComment }; class Rule { private: bool m_bIsValid; ERuleCommand m_eCommand; char* m_szCategory; int m_iPriority; int m_iAddPriority; bool m_bPause; int m_iDupeScore; int m_iAddDupeScore; char* m_szDupeKey; char* m_szAddDupeKey; EDupeMode m_eDupeMode; char* m_szSeries; char* m_szRageId; bool m_bHasCategory; bool m_bHasPriority; bool m_bHasAddPriority; bool m_bHasPause; bool m_bHasDupeScore; bool m_bHasAddDupeScore; bool m_bHasDupeKey; bool m_bHasAddDupeKey; bool m_bHasDupeMode; bool m_bPatCategory; bool m_bPatDupeKey; bool m_bPatAddDupeKey; bool m_bHasSeries; bool m_bHasRageId; char* m_szPatCategory; char* m_szPatDupeKey; char* m_szPatAddDupeKey; TermList m_Terms; RefValues m_RefValues; char* CompileCommand(char* szRule); char* CompileOptions(char* szRule); bool CompileTerm(char* szTerm); bool MatchExpression(FeedItemInfo* pFeedItemInfo); public: Rule(); ~Rule(); void Compile(char* szRule); bool IsValid() { return m_bIsValid; } ERuleCommand GetCommand() { return m_eCommand; } const char* GetCategory() { return m_szCategory; } int GetPriority() { return m_iPriority; } int GetAddPriority() { return m_iAddPriority; } bool GetPause() { return m_bPause; } const char* GetDupeKey() { return m_szDupeKey; } const char* GetAddDupeKey() { return m_szAddDupeKey; } int GetDupeScore() { return m_iDupeScore; } int GetAddDupeScore() { return m_iAddDupeScore; } EDupeMode GetDupeMode() { return m_eDupeMode; } const char* GetRageId() { return m_szRageId; } const char* GetSeries() { return m_szSeries; } bool HasCategory() { return m_bHasCategory; } bool HasPriority() { return m_bHasPriority; } bool HasAddPriority() { return m_bHasAddPriority; } bool HasPause() { return m_bHasPause; } bool HasDupeScore() { return m_bHasDupeScore; } bool HasAddDupeScore() { return m_bHasAddDupeScore; } bool HasDupeKey() { return m_bHasDupeKey; } bool HasAddDupeKey() { return m_bHasAddDupeKey; } bool HasDupeMode() { return m_bHasDupeMode; } bool HasRageId() { return m_bHasRageId; } bool HasSeries() { return m_bHasSeries; } bool Match(FeedItemInfo* pFeedItemInfo); void ExpandRefValues(FeedItemInfo* pFeedItemInfo, char** pDestStr, char* pPatStr); const char* GetRefValue(FeedItemInfo* pFeedItemInfo, const char* szVarName); }; typedef std::deque RuleList; private: RuleList m_Rules; void Compile(const char* szFilter); void CompileRule(char* szRule); void ApplyOptions(Rule* pRule, FeedItemInfo* pFeedItemInfo); public: FeedFilter(const char* szFilter); ~FeedFilter(); void Match(FeedItemInfo* pFeedItemInfo); }; #endif nzbget-12.0+dfsg/FeedInfo.cpp000066400000000000000000000221231226450633000160300ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 0 $ * $Date: 2013-11-24 20:29:52 +0100 (Sun, 24 Nov 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #include #include #include "nzbget.h" #include "FeedInfo.h" #include "Util.h" FeedInfo::FeedInfo(int iID, const char* szName, const char* szUrl, int iInterval, const char* szFilter, bool bPauseNzb, const char* szCategory, int iPriority) { m_iID = iID; m_szName = strdup(szName ? szName : ""); m_szUrl = strdup(szUrl ? szUrl : ""); m_szFilter = strdup(szFilter ? szFilter : ""); m_iFilterHash = Util::HashBJ96(szFilter, strlen(szFilter), 0); m_szCategory = strdup(szCategory ? szCategory : ""); m_iInterval = iInterval; m_bPauseNzb = bPauseNzb; m_iPriority = iPriority; m_tLastUpdate = 0; m_bPreview = false; m_eStatus = fsUndefined; m_szOutputFilename = NULL; m_bFetch = false; m_bForce = false; } FeedInfo::~FeedInfo() { free(m_szName); free(m_szUrl); free(m_szFilter); free(m_szCategory); free(m_szOutputFilename); } void FeedInfo::SetOutputFilename(const char* szOutputFilename) { free(m_szOutputFilename); m_szOutputFilename = strdup(szOutputFilename); } FeedItemInfo::Attr::Attr(const char* szName, const char* szValue) { m_szName = strdup(szName ? szName : ""); m_szValue = strdup(szValue ? szValue : ""); } FeedItemInfo::Attr::~Attr() { free(m_szName); free(m_szValue); } FeedItemInfo::Attributes::~Attributes() { for (iterator it = begin(); it != end(); it++) { delete *it; } } void FeedItemInfo::Attributes::Add(const char* szName, const char* szValue) { push_back(new Attr(szName, szValue)); } FeedItemInfo::Attr* FeedItemInfo::Attributes::Find(const char* szName) { for (iterator it = begin(); it != end(); it++) { Attr* pAttr = *it; if (!strcasecmp(pAttr->GetName(), szName)) { return pAttr; } } return NULL; } FeedItemInfo::FeedItemInfo() { m_pSharedFeedData = NULL; m_szTitle = NULL; m_szFilename = NULL; m_szUrl = NULL; m_szCategory = strdup(""); m_lSize = 0; m_tTime = 0; m_iImdbId = 0; m_iRageId = 0; m_szDescription = strdup(""); m_szSeason = NULL; m_szEpisode = NULL; m_iSeasonNum = 0; m_iEpisodeNum = 0; m_bSeasonEpisodeParsed = false; m_szAddCategory = strdup(""); m_bPauseNzb = false; m_iPriority = 0; m_eStatus = isUnknown; m_eMatchStatus = msIgnored; m_iMatchRule = 0; m_szDupeKey = NULL; m_iDupeScore = 0; m_eDupeMode = dmScore; } FeedItemInfo::~FeedItemInfo() { free(m_szTitle); free(m_szFilename); free(m_szUrl); free(m_szCategory); free(m_szDescription); free(m_szSeason); free(m_szEpisode); free(m_szAddCategory); free(m_szDupeKey); } void FeedItemInfo::SetTitle(const char* szTitle) { free(m_szTitle); m_szTitle = szTitle ? strdup(szTitle) : NULL; } void FeedItemInfo::SetFilename(const char* szFilename) { free(m_szFilename); m_szFilename = szFilename ? strdup(szFilename) : NULL; } void FeedItemInfo::SetUrl(const char* szUrl) { free(m_szUrl); m_szUrl = szUrl ? strdup(szUrl) : NULL; } void FeedItemInfo::SetCategory(const char* szCategory) { free(m_szCategory); m_szCategory = strdup(szCategory ? szCategory: ""); } void FeedItemInfo::SetDescription(const char* szDescription) { free(m_szDescription); m_szDescription = strdup(szDescription ? szDescription: ""); } void FeedItemInfo::SetSeason(const char* szSeason) { free(m_szSeason); m_szSeason = szSeason ? strdup(szSeason) : NULL; m_iSeasonNum = szSeason ? ParsePrefixedInt(szSeason) : 0; } void FeedItemInfo::SetEpisode(const char* szEpisode) { free(m_szEpisode); m_szEpisode = szEpisode ? strdup(szEpisode) : NULL; m_iEpisodeNum = szEpisode ? ParsePrefixedInt(szEpisode) : 0; } int FeedItemInfo::ParsePrefixedInt(const char *szValue) { const char* szVal = szValue; if (!strchr("0123456789", *szVal)) { szVal++; } return atoi(szVal); } void FeedItemInfo::SetAddCategory(const char* szAddCategory) { free(m_szAddCategory); m_szAddCategory = strdup(szAddCategory ? szAddCategory : ""); } void FeedItemInfo::SetDupeKey(const char* szDupeKey) { free(m_szDupeKey); m_szDupeKey = strdup(szDupeKey ? szDupeKey : ""); } void FeedItemInfo::AppendDupeKey(const char* szExtraDupeKey) { if (!m_szDupeKey || *m_szDupeKey == '\0' || !szExtraDupeKey || *szExtraDupeKey == '\0') { return; } int iLen = (m_szDupeKey ? strlen(m_szDupeKey) : 0) + 1 + strlen(szExtraDupeKey) + 1; char* szNewKey = (char*)malloc(iLen); snprintf(szNewKey, iLen, "%s-%s", m_szDupeKey, szExtraDupeKey); szNewKey[iLen - 1] = '\0'; free(m_szDupeKey); m_szDupeKey = szNewKey; } void FeedItemInfo::BuildDupeKey(const char* szRageId, const char* szSeries) { int iRageId = szRageId && *szRageId ? atoi(szRageId) : m_iRageId; free(m_szDupeKey); if (m_iImdbId != 0) { m_szDupeKey = (char*)malloc(20); snprintf(m_szDupeKey, 20, "imdb=%i", m_iImdbId); } else if (szSeries && *szSeries && GetSeasonNum() != 0 && GetEpisodeNum() != 0) { int iLen = strlen(szSeries) + 50; m_szDupeKey = (char*)malloc(iLen); snprintf(m_szDupeKey, iLen, "series=%s-%s-%s", szSeries, m_szSeason, m_szEpisode); m_szDupeKey[iLen-1] = '\0'; } else if (iRageId != 0 && GetSeasonNum() != 0 && GetEpisodeNum() != 0) { m_szDupeKey = (char*)malloc(100); snprintf(m_szDupeKey, 100, "rageid=%i-%s-%s", iRageId, m_szSeason, m_szEpisode); m_szDupeKey[100-1] = '\0'; } else { m_szDupeKey = strdup(""); } } int FeedItemInfo::GetSeasonNum() { if (!m_szSeason && !m_bSeasonEpisodeParsed) { ParseSeasonEpisode(); } return m_iSeasonNum; } int FeedItemInfo::GetEpisodeNum() { if (!m_szEpisode && !m_bSeasonEpisodeParsed) { ParseSeasonEpisode(); } return m_iEpisodeNum; } void FeedItemInfo::ParseSeasonEpisode() { m_bSeasonEpisodeParsed = true; RegEx* pRegEx = m_pSharedFeedData->GetSeasonEpisodeRegEx(); if (pRegEx->Match(m_szTitle)) { char szRegValue[100]; char szValue[100]; snprintf(szValue, 100, "S%02d", atoi(m_szTitle + pRegEx->GetMatchStart(1))); szValue[100-1] = '\0'; SetSeason(szValue); int iLen = pRegEx->GetMatchLen(2); iLen = iLen < 99 ? iLen : 99; strncpy(szRegValue, m_szTitle + pRegEx->GetMatchStart(2), pRegEx->GetMatchLen(2)); szRegValue[iLen] = '\0'; snprintf(szValue, 100, "E%s", szRegValue); szValue[100-1] = '\0'; Util::ReduceStr(szValue, "-", ""); for (char* p = szValue; *p; p++) *p = toupper(*p); // convert string to uppercase e02 -> E02 SetEpisode(szValue); } } FeedHistoryInfo::FeedHistoryInfo(const char* szUrl, FeedHistoryInfo::EStatus eStatus, time_t tLastSeen) { m_szUrl = szUrl ? strdup(szUrl) : NULL; m_eStatus = eStatus; m_tLastSeen = tLastSeen; } FeedHistoryInfo::~FeedHistoryInfo() { free(m_szUrl); } FeedHistory::~FeedHistory() { Clear(); } void FeedHistory::Clear() { for (iterator it = begin(); it != end(); it++) { delete *it; } clear(); } void FeedHistory::Add(const char* szUrl, FeedHistoryInfo::EStatus eStatus, time_t tLastSeen) { push_back(new FeedHistoryInfo(szUrl, eStatus, tLastSeen)); } void FeedHistory::Remove(const char* szUrl) { for (iterator it = begin(); it != end(); it++) { FeedHistoryInfo* pFeedHistoryInfo = *it; if (!strcmp(pFeedHistoryInfo->GetUrl(), szUrl)) { delete pFeedHistoryInfo; erase(it); break; } } } FeedHistoryInfo* FeedHistory::Find(const char* szUrl) { for (iterator it = begin(); it != end(); it++) { FeedHistoryInfo* pFeedHistoryInfo = *it; if (!strcmp(pFeedHistoryInfo->GetUrl(), szUrl)) { return pFeedHistoryInfo; } } return NULL; } FeedItemInfos::FeedItemInfos() { debug("Creating FeedItemInfos"); m_iRefCount = 0; } FeedItemInfos::~FeedItemInfos() { debug("Destroing FeedItemInfos"); for (iterator it = begin(); it != end(); it++) { delete *it; } } void FeedItemInfos::Retain() { m_iRefCount++; } void FeedItemInfos::Release() { m_iRefCount--; if (m_iRefCount <= 0) { delete this; } } void FeedItemInfos::Add(FeedItemInfo* pFeedItemInfo) { push_back(pFeedItemInfo); pFeedItemInfo->SetSharedFeedData(&m_SharedFeedData); } SharedFeedData::SharedFeedData() { m_pSeasonEpisodeRegEx = NULL; } SharedFeedData::~SharedFeedData() { delete m_pSeasonEpisodeRegEx; } RegEx* SharedFeedData::GetSeasonEpisodeRegEx() { if (!m_pSeasonEpisodeRegEx) { m_pSeasonEpisodeRegEx = new RegEx("[^[:alnum:]]s?([0-9]+)[ex]([0-9]+(-?e[0-9]+)?)[^[:alnum:]]", 10); } return m_pSeasonEpisodeRegEx; } nzbget-12.0+dfsg/FeedInfo.h000066400000000000000000000174411226450633000155040ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 0 $ * $Date: 2013-11-17 22:57:32 +0100 (Sun, 17 Nov 2013) $ * */ #ifndef FEEDINFO_H #define FEEDINFO_H #include #include #include "Util.h" #include "DownloadInfo.h" class FeedInfo { public: enum EStatus { fsUndefined, fsRunning, fsFinished, fsFailed }; private: int m_iID; char* m_szName; char* m_szUrl; int m_iInterval; char* m_szFilter; unsigned int m_iFilterHash; bool m_bPauseNzb; char* m_szCategory; int m_iPriority; time_t m_tLastUpdate; bool m_bPreview; EStatus m_eStatus; char* m_szOutputFilename; bool m_bFetch; bool m_bForce; public: FeedInfo(int iID, const char* szName, const char* szUrl, int iInterval, const char* szFilter, bool bPauseNzb, const char* szCategory, int iPriority); ~FeedInfo(); int GetID() { return m_iID; } const char* GetName() { return m_szName; } const char* GetUrl() { return m_szUrl; } int GetInterval() { return m_iInterval; } const char* GetFilter() { return m_szFilter; } unsigned int GetFilterHash() { return m_iFilterHash; } bool GetPauseNzb() { return m_bPauseNzb; } const char* GetCategory() { return m_szCategory; } int GetPriority() { return m_iPriority; } time_t GetLastUpdate() { return m_tLastUpdate; } void SetLastUpdate(time_t tLastUpdate) { m_tLastUpdate = tLastUpdate; } bool GetPreview() { return m_bPreview; } void SetPreview(bool bPreview) { m_bPreview = bPreview; } EStatus GetStatus() { return m_eStatus; } void SetStatus(EStatus Status) { m_eStatus = Status; } const char* GetOutputFilename() { return m_szOutputFilename; } void SetOutputFilename(const char* szOutputFilename); bool GetFetch() { return m_bFetch; } void SetFetch(bool bFetch) { m_bFetch = bFetch; } bool GetForce() { return m_bForce; } void SetForce(bool bForce) { m_bForce = bForce; } }; typedef std::deque Feeds; class SharedFeedData { private: RegEx* m_pSeasonEpisodeRegEx; public: SharedFeedData(); ~SharedFeedData(); RegEx* GetSeasonEpisodeRegEx(); }; class FeedItemInfo { public: enum EStatus { isUnknown, isBacklog, isFetched, isNew }; enum EMatchStatus { msIgnored, msAccepted, msRejected }; class Attr { private: char* m_szName; char* m_szValue; public: Attr(const char* szName, const char* szValue); ~Attr(); const char* GetName() { return m_szName; } const char* GetValue() { return m_szValue; } }; typedef std::deque AttributesBase; class Attributes: public AttributesBase { public: ~Attributes(); void Add(const char* szName, const char* szValue); Attr* Find(const char* szName); }; private: char* m_szTitle; char* m_szFilename; char* m_szUrl; time_t m_tTime; long long m_lSize; char* m_szCategory; int m_iImdbId; int m_iRageId; char* m_szDescription; char* m_szSeason; char* m_szEpisode; int m_iSeasonNum; int m_iEpisodeNum; bool m_bSeasonEpisodeParsed; char* m_szAddCategory; bool m_bPauseNzb; int m_iPriority; EStatus m_eStatus; EMatchStatus m_eMatchStatus; int m_iMatchRule; char* m_szDupeKey; int m_iDupeScore; EDupeMode m_eDupeMode; SharedFeedData* m_pSharedFeedData; Attributes m_Attributes; int ParsePrefixedInt(const char *szValue); void ParseSeasonEpisode(); public: FeedItemInfo(); ~FeedItemInfo(); void SetSharedFeedData(SharedFeedData* pSharedFeedData) { m_pSharedFeedData = pSharedFeedData; } const char* GetTitle() { return m_szTitle; } void SetTitle(const char* szTitle); const char* GetFilename() { return m_szFilename; } void SetFilename(const char* szFilename); const char* GetUrl() { return m_szUrl; } void SetUrl(const char* szUrl); long long GetSize() { return m_lSize; } void SetSize(long long lSize) { m_lSize = lSize; } const char* GetCategory() { return m_szCategory; } void SetCategory(const char* szCategory); int GetImdbId() { return m_iImdbId; } void SetImdbId(int iImdbId) { m_iImdbId = iImdbId; } int GetRageId() { return m_iRageId; } void SetRageId(int iRageId) { m_iRageId = iRageId; } const char* GetDescription() { return m_szDescription; } void SetDescription(const char* szDescription); const char* GetSeason() { return m_szSeason; } void SetSeason(const char* szSeason); const char* GetEpisode() { return m_szEpisode; } void SetEpisode(const char* szEpisode); int GetSeasonNum(); int GetEpisodeNum(); const char* GetAddCategory() { return m_szAddCategory; } void SetAddCategory(const char* szAddCategory); bool GetPauseNzb() { return m_bPauseNzb; } void SetPauseNzb(bool bPauseNzb) { m_bPauseNzb = bPauseNzb; } int GetPriority() { return m_iPriority; } void SetPriority(int iPriority) { m_iPriority = iPriority; } time_t GetTime() { return m_tTime; } void SetTime(time_t tTime) { m_tTime = tTime; } EStatus GetStatus() { return m_eStatus; } void SetStatus(EStatus eStatus) { m_eStatus = eStatus; } EMatchStatus GetMatchStatus() { return m_eMatchStatus; } void SetMatchStatus(EMatchStatus eMatchStatus) { m_eMatchStatus = eMatchStatus; } int GetMatchRule() { return m_iMatchRule; } void SetMatchRule(int iMatchRule) { m_iMatchRule = iMatchRule; } const char* GetDupeKey() { return m_szDupeKey; } void SetDupeKey(const char* szDupeKey); void AppendDupeKey(const char* szExtraDupeKey); void BuildDupeKey(const char* szRageId, const char* szSeries); int GetDupeScore() { return m_iDupeScore; } void SetDupeScore(int iDupeScore) { m_iDupeScore = iDupeScore; } EDupeMode GetDupeMode() { return m_eDupeMode; } void SetDupeMode(EDupeMode eDupeMode) { m_eDupeMode = eDupeMode; } Attributes* GetAttributes() { return &m_Attributes; } }; typedef std::deque FeedItemInfosBase; class FeedItemInfos : public FeedItemInfosBase { private: int m_iRefCount; SharedFeedData m_SharedFeedData; public: FeedItemInfos(); ~FeedItemInfos(); void Retain(); void Release(); void Add(FeedItemInfo* pFeedItemInfo); }; class FeedHistoryInfo { public: enum EStatus { hsUnknown, hsBacklog, hsFetched }; private: char* m_szUrl; EStatus m_eStatus; time_t m_tLastSeen; public: FeedHistoryInfo(const char* szUrl, EStatus eStatus, time_t tLastSeen); ~FeedHistoryInfo(); const char* GetUrl() { return m_szUrl; } EStatus GetStatus() { return m_eStatus; } void SetStatus(EStatus Status) { m_eStatus = Status; } time_t GetLastSeen() { return m_tLastSeen; } void SetLastSeen(time_t tLastSeen) { m_tLastSeen = tLastSeen; } }; typedef std::deque FeedHistoryBase; class FeedHistory : public FeedHistoryBase { public: ~FeedHistory(); void Clear(); void Add(const char* szUrl, FeedHistoryInfo::EStatus eStatus, time_t tLastSeen); void Remove(const char* szUrl); FeedHistoryInfo* Find(const char* szUrl); }; #endif nzbget-12.0+dfsg/Frontend.cpp000066400000000000000000000237051226450633000161370ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 705 $ * $Date: 2013-06-05 23:09:28 +0200 (Wed, 05 Jun 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #ifndef WIN32 #include #include #include #endif #include "nzbget.h" #include "Options.h" #include "Frontend.h" #include "Log.h" #include "Connection.h" #include "MessageBase.h" #include "QueueCoordinator.h" #include "RemoteClient.h" #include "Util.h" extern QueueCoordinator* g_pQueueCoordinator; extern Options* g_pOptions; Frontend::Frontend() { debug("Creating Frontend"); m_iNeededLogFirstID = 0; m_iNeededLogEntries = 0; m_bSummary = false; m_bFileList = false; m_iCurrentDownloadSpeed = 0; m_lRemainingSize = 0; m_bPauseDownload = false; m_bPauseDownload2 = false; m_iDownloadLimit = 0; m_iThreadCount = 0; m_iPostJobCount = 0; m_iUpTimeSec = 0; m_iDnTimeSec = 0; m_iAllBytes = 0; m_bStandBy = 0; m_iUpdateInterval = g_pOptions->GetUpdateInterval(); } bool Frontend::PrepareData() { if (IsRemoteMode()) { if (IsStopped()) { return false; } if (!RequestMessages() || ((m_bSummary || m_bFileList) && !RequestFileList())) { printf("\nUnable to send request to nzbget-server at %s (port %i) \n", g_pOptions->GetControlIP(), g_pOptions->GetControlPort()); Stop(); return false; } } else { if (m_bSummary) { m_iCurrentDownloadSpeed = g_pQueueCoordinator->CalcCurrentDownloadSpeed(); m_lRemainingSize = g_pQueueCoordinator->CalcRemainingSize(); m_bPauseDownload = g_pOptions->GetPauseDownload(); m_bPauseDownload2 = g_pOptions->GetPauseDownload2(); m_iDownloadLimit = g_pOptions->GetDownloadRate(); m_iThreadCount = Thread::GetThreadCount(); PostQueue* pPostQueue = g_pQueueCoordinator->LockQueue()->GetPostQueue(); m_iPostJobCount = pPostQueue->size(); g_pQueueCoordinator->UnlockQueue(); g_pQueueCoordinator->CalcStat(&m_iUpTimeSec, &m_iDnTimeSec, &m_iAllBytes, &m_bStandBy); } } return true; } void Frontend::FreeData() { if (IsRemoteMode()) { for (Log::Messages::iterator it = m_RemoteMessages.begin(); it != m_RemoteMessages.end(); it++) { delete *it; } m_RemoteMessages.clear(); for (FileQueue::iterator it = m_RemoteQueue.GetFileQueue()->begin(); it != m_RemoteQueue.GetFileQueue()->end(); it++) { delete *it; } m_RemoteQueue.GetFileQueue()->clear(); } } Log::Messages * Frontend::LockMessages() { if (IsRemoteMode()) { return &m_RemoteMessages; } else { return g_pLog->LockMessages(); } } void Frontend::UnlockMessages() { if (!IsRemoteMode()) { g_pLog->UnlockMessages(); } } DownloadQueue* Frontend::LockQueue() { if (IsRemoteMode()) { return &m_RemoteQueue; } else { return g_pQueueCoordinator->LockQueue(); } } void Frontend::UnlockQueue() { if (!IsRemoteMode()) { g_pQueueCoordinator->UnlockQueue(); } } bool Frontend::IsRemoteMode() { return g_pOptions->GetRemoteClientMode(); } void Frontend::ServerPauseUnpause(bool bPause, bool bSecondRegister) { if (IsRemoteMode()) { RequestPauseUnpause(bPause, bSecondRegister); } else { g_pOptions->SetResumeTime(0); if (bSecondRegister) { g_pOptions->SetPauseDownload2(bPause); } else { g_pOptions->SetPauseDownload(bPause); } } } void Frontend::ServerSetDownloadRate(int iRate) { if (IsRemoteMode()) { RequestSetDownloadRate(iRate); } else { g_pOptions->SetDownloadRate(iRate); } } void Frontend::ServerDumpDebug() { if (IsRemoteMode()) { RequestDumpDebug(); } else { g_pQueueCoordinator->LogDebugInfo(); } } bool Frontend::ServerEditQueue(QueueEditor::EEditAction eAction, int iOffset, int iID) { if (IsRemoteMode()) { return RequestEditQueue((eRemoteEditAction)eAction, iOffset, iID); } else { return g_pQueueCoordinator->GetQueueEditor()->EditEntry(iID, true, eAction, iOffset, NULL); } return false; } void Frontend::InitMessageBase(SNZBRequestBase* pMessageBase, int iRequest, int iSize) { pMessageBase->m_iSignature = htonl(NZBMESSAGE_SIGNATURE); pMessageBase->m_iType = htonl(iRequest); pMessageBase->m_iStructSize = htonl(iSize); strncpy(pMessageBase->m_szUsername, g_pOptions->GetControlUsername(), NZBREQUESTPASSWORDSIZE - 1); pMessageBase->m_szUsername[NZBREQUESTPASSWORDSIZE - 1] = '\0'; strncpy(pMessageBase->m_szPassword, g_pOptions->GetControlPassword(), NZBREQUESTPASSWORDSIZE); pMessageBase->m_szPassword[NZBREQUESTPASSWORDSIZE - 1] = '\0'; } bool Frontend::RequestMessages() { Connection connection(g_pOptions->GetControlIP(), g_pOptions->GetControlPort(), false); bool OK = connection.Connect(); if (!OK) { return false; } SNZBLogRequest LogRequest; InitMessageBase(&LogRequest.m_MessageBase, eRemoteRequestLog, sizeof(LogRequest)); LogRequest.m_iLines = htonl(m_iNeededLogEntries); if (m_iNeededLogEntries == 0) { LogRequest.m_iIDFrom = htonl(m_iNeededLogFirstID > 0 ? m_iNeededLogFirstID : 1); } else { LogRequest.m_iIDFrom = 0; } if (!connection.Send((char*)(&LogRequest), sizeof(LogRequest))) { return false; } // Now listen for the returned log SNZBLogResponse LogResponse; bool bRead = connection.Recv((char*) &LogResponse, sizeof(LogResponse)); if (!bRead || (int)ntohl(LogResponse.m_MessageBase.m_iSignature) != (int)NZBMESSAGE_SIGNATURE || ntohl(LogResponse.m_MessageBase.m_iStructSize) != sizeof(LogResponse)) { return false; } char* pBuf = NULL; if (ntohl(LogResponse.m_iTrailingDataLength) > 0) { pBuf = (char*)malloc(ntohl(LogResponse.m_iTrailingDataLength)); if (!connection.Recv(pBuf, ntohl(LogResponse.m_iTrailingDataLength))) { free(pBuf); return false; } } connection.Disconnect(); if (ntohl(LogResponse.m_iTrailingDataLength) > 0) { char* pBufPtr = (char*)pBuf; for (unsigned int i = 0; i < ntohl(LogResponse.m_iNrTrailingEntries); i++) { SNZBLogResponseEntry* pLogAnswer = (SNZBLogResponseEntry*) pBufPtr; char* szText = pBufPtr + sizeof(SNZBLogResponseEntry); Message* pMessage = new Message(ntohl(pLogAnswer->m_iID), (Message::EKind)ntohl(pLogAnswer->m_iKind), ntohl(pLogAnswer->m_tTime), szText); m_RemoteMessages.push_back(pMessage); pBufPtr += sizeof(SNZBLogResponseEntry) + ntohl(pLogAnswer->m_iTextLen); } free(pBuf); } return true; } bool Frontend::RequestFileList() { Connection connection(g_pOptions->GetControlIP(), g_pOptions->GetControlPort(), false); bool OK = connection.Connect(); if (!OK) { return false; } SNZBListRequest ListRequest; InitMessageBase(&ListRequest.m_MessageBase, eRemoteRequestList, sizeof(ListRequest)); ListRequest.m_bFileList = htonl(m_bFileList); ListRequest.m_bServerState = htonl(m_bSummary); if (!connection.Send((char*)(&ListRequest), sizeof(ListRequest))) { return false; } // Now listen for the returned list SNZBListResponse ListResponse; bool bRead = connection.Recv((char*) &ListResponse, sizeof(ListResponse)); if (!bRead || (int)ntohl(ListResponse.m_MessageBase.m_iSignature) != (int)NZBMESSAGE_SIGNATURE || ntohl(ListResponse.m_MessageBase.m_iStructSize) != sizeof(ListResponse)) { return false; } char* pBuf = NULL; if (ntohl(ListResponse.m_iTrailingDataLength) > 0) { pBuf = (char*)malloc(ntohl(ListResponse.m_iTrailingDataLength)); if (!connection.Recv(pBuf, ntohl(ListResponse.m_iTrailingDataLength))) { free(pBuf); return false; } } connection.Disconnect(); if (m_bSummary) { m_bPauseDownload = ntohl(ListResponse.m_bDownloadPaused); m_bPauseDownload2 = ntohl(ListResponse.m_bDownload2Paused); m_lRemainingSize = Util::JoinInt64(ntohl(ListResponse.m_iRemainingSizeHi), ntohl(ListResponse.m_iRemainingSizeLo)); m_iCurrentDownloadSpeed = ntohl(ListResponse.m_iDownloadRate); m_iDownloadLimit = ntohl(ListResponse.m_iDownloadLimit); m_iThreadCount = ntohl(ListResponse.m_iThreadCount); m_iPostJobCount = ntohl(ListResponse.m_iPostJobCount); m_iUpTimeSec = ntohl(ListResponse.m_iUpTimeSec); m_iDnTimeSec = ntohl(ListResponse.m_iDownloadTimeSec); m_bStandBy = ntohl(ListResponse.m_bDownloadStandBy); m_iAllBytes = Util::JoinInt64(ntohl(ListResponse.m_iDownloadedBytesHi), ntohl(ListResponse.m_iDownloadedBytesLo)); } if (m_bFileList && ntohl(ListResponse.m_iTrailingDataLength) > 0) { RemoteClient client; client.SetVerbose(false); client.BuildFileList(&ListResponse, pBuf, &m_RemoteQueue); } if (pBuf) { free(pBuf); } return true; } bool Frontend::RequestPauseUnpause(bool bPause, bool bSecondRegister) { RemoteClient client; client.SetVerbose(false); return client.RequestServerPauseUnpause(bPause, bSecondRegister ? eRemotePauseUnpauseActionDownload2 : eRemotePauseUnpauseActionDownload); } bool Frontend::RequestSetDownloadRate(int iRate) { RemoteClient client; client.SetVerbose(false); return client.RequestServerSetDownloadRate(iRate); } bool Frontend::RequestDumpDebug() { RemoteClient client; client.SetVerbose(false); return client.RequestServerDumpDebug(); } bool Frontend::RequestEditQueue(eRemoteEditAction iAction, int iOffset, int iID) { RemoteClient client; client.SetVerbose(false); return client.RequestServerEditQueue(iAction, iOffset, NULL, &iID, 1, NULL, eRemoteMatchModeID, false); } nzbget-12.0+dfsg/Frontend.h000066400000000000000000000047301226450633000156010ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2010 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 544 $ * $Date: 2013-01-18 22:36:17 +0100 (Fri, 18 Jan 2013) $ * */ #ifndef FRONTEND_H #define FRONTEND_H #include "Thread.h" #include "Log.h" #include "DownloadInfo.h" #include "MessageBase.h" #include "QueueEditor.h" class Frontend : public Thread { private: Log::Messages m_RemoteMessages; DownloadQueue m_RemoteQueue; bool RequestMessages(); bool RequestFileList(); protected: bool m_bSummary; bool m_bFileList; unsigned int m_iNeededLogEntries; unsigned int m_iNeededLogFirstID; int m_iUpdateInterval; // summary int m_iCurrentDownloadSpeed; long long m_lRemainingSize; bool m_bPauseDownload; bool m_bPauseDownload2; int m_iDownloadLimit; int m_iThreadCount; int m_iPostJobCount; int m_iUpTimeSec; int m_iDnTimeSec; long long m_iAllBytes; bool m_bStandBy; bool PrepareData(); void FreeData(); Log::Messages* LockMessages(); void UnlockMessages(); DownloadQueue* LockQueue(); void UnlockQueue(); bool IsRemoteMode(); void InitMessageBase(SNZBRequestBase* pMessageBase, int iRequest, int iSize); void ServerPauseUnpause(bool bPause, bool bSecondRegister); bool RequestPauseUnpause(bool bPause, bool bSecondRegister); void ServerSetDownloadRate(int iRate); bool RequestSetDownloadRate(int iRate); void ServerDumpDebug(); bool RequestDumpDebug(); bool ServerEditQueue(QueueEditor::EEditAction eAction, int iOffset, int iEntry); bool RequestEditQueue(eRemoteEditAction iAction, int iOffset, int iID); public: Frontend(); }; #endif nzbget-12.0+dfsg/INSTALL000066400000000000000000000154721226450633000147070ustar00rootroot00000000000000Basic Installation ================== These are generic installation instructions. The `configure' shell script attempts to guess correct values for various system-dependent variables used during compilation. It uses those values to create a `Makefile' in each directory of the package. It may also create one or more `.h' files containing system-dependent definitions. Finally, it creates a shell script `config.status' that you can run in the future to recreate the current configuration, a file `config.cache' that saves the results of its tests to speed up reconfiguring, and a file `config.log' containing compiler output (useful mainly for debugging `configure'). If you need to do unusual things to compile the package, please try to figure out how `configure' could check whether to do them, and mail diffs or instructions to the address given in the `README' so they can be considered for the next release. If at some point `config.cache' contains results you don't want to keep, you may remove or edit it. The file `configure.in' is used to create `configure' by a program called `autoconf'. You only need `configure.in' if you want to change it or regenerate `configure' using a newer version of `autoconf'. The simplest way to compile this package is: 1. `cd' to the directory containing the package's source code and type `./configure' to configure the package for your system. If you're using `csh' on an old version of System V, you might need to type `sh ./configure' instead to prevent `csh' from trying to execute `configure' itself. Running `configure' takes a while. While running, it prints some messages telling which features it is checking for. 2. Type `make' to compile the package. 3. Type `make install' to install the programs and any data files and documentation. 4. You can remove the program binaries and object files from the source code directory by typing `make clean'. Compilers and Options ===================== Some systems require unusual options for compilation or linking that the `configure' script does not know about. You can give `configure' initial values for variables by setting them in the environment. Using a Bourne-compatible shell, you can do that on the command line like this: CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure Or on systems that have the `env' program, you can do it like this: env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure Compiling For Multiple Architectures ==================================== You can compile the package for more than one kind of computer at the same time, by placing the object files for each architecture in their own directory. To do this, you must use a version of `make' that supports the `VPATH' variable, such as GNU `make'. `cd' to the directory where you want the object files and executables to go and run the `configure' script. `configure' automatically checks for the source code in the directory that `configure' is in and in `..'. If you have to use a `make' that does not supports the `VPATH' variable, you have to compile the package for one architecture at a time in the source code directory. After you have installed the package for one architecture, use `make distclean' before reconfiguring for another architecture. Installation Names ================== By default, `make install' will install the package's files in `/usr/local/bin', `/usr/local/man', etc. You can specify an installation prefix other than `/usr/local' by giving `configure' the option `--prefix=PATH'. You can specify separate installation prefixes for architecture-specific files and architecture-independent files. If you give `configure' the option `--exec-prefix=PATH', the package will use PATH as the prefix for installing programs and libraries. Documentation and other data files will still use the regular prefix. If the package supports it, you can cause programs to be installed with an extra prefix or suffix on their names by giving `configure' the option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. Optional Features ================= Some packages pay attention to `--enable-FEATURE' options to `configure', where FEATURE indicates an optional part of the package. They may also pay attention to `--with-PACKAGE' options, where PACKAGE is something like `gnu-as' or `x' (for the X Window System). The `README' should mention any `--enable-' and `--with-' options that the package recognizes. For packages that use the X Window System, `configure' can usually find the X include and library files automatically, but if it doesn't, you can use the `configure' options `--x-includes=DIR' and `--x-libraries=DIR' to specify their locations. Specifying the System Type ========================== There may be some features `configure' can not figure out automatically, but needs to determine by the type of host the package will run on. Usually `configure' can figure that out, but if it prints a message saying it can not guess the host type, give it the `--host=TYPE' option. TYPE can either be a short name for the system type, such as `sun4', or a canonical name with three fields: CPU-COMPANY-SYSTEM See the file `config.sub' for the possible values of each field. If `config.sub' isn't included in this package, then this package doesn't need to know the host type. If you are building compiler tools for cross-compiling, you can also use the `--target=TYPE' option to select the type of system they will produce code for and the `--build=TYPE' option to select the type of system on which you are compiling the package. Sharing Defaults ================ If you want to set default values for `configure' scripts to share, you can create a site shell script called `config.site' that gives default values for variables like `CC', `cache_file', and `prefix'. `configure' looks for `PREFIX/share/config.site' if it exists, then `PREFIX/etc/config.site' if it exists. Or, you can set the `CONFIG_SITE' environment variable to the location of the site script. A warning: not all `configure' scripts look for a site script. Operation Controls ================== `configure' recognizes the following options to control how it operates. `--cache-file=FILE' Use and save the results of the tests in FILE instead of `./config.cache'. Set FILE to `/dev/null' to disable caching, for debugging `configure'. `--help' Print a summary of the options to `configure', and exit. `--quiet' `--silent' `-q' Do not print messages saying which checks are being made. `--srcdir=DIR' Look for the package's source code in directory DIR. Usually `configure' can determine that directory automatically. `--version' Print the version of Autoconf used to generate the `configure' script, and exit. `configure' also accepts some other, not widely useful, options. nzbget-12.0+dfsg/Log.cpp000066400000000000000000000214231226450633000150740ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 914 $ * $Date: 2013-11-28 22:03:01 +0100 (Thu, 28 Nov 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #else #include #endif #include #include #include #include #include #include "nzbget.h" #include "Options.h" #include "Log.h" #include "Util.h" extern Options* g_pOptions; Log::Log() { m_Messages.clear(); m_iIDGen = 0; m_szLogFilename = NULL; #ifdef DEBUG m_bExtraDebug = Util::FileExists("extradebug"); #endif } Log::~Log() { Clear(); free(m_szLogFilename); } void Log::Filelog(const char* msg, ...) { if (m_szLogFilename) { char tmp2[1024]; va_list ap; va_start(ap, msg); vsnprintf(tmp2, 1024, msg, ap); tmp2[1024-1] = '\0'; va_end(ap); time_t rawtime; time(&rawtime); rawtime += g_pOptions->GetTimeCorrection(); char szTime[50]; #ifdef HAVE_CTIME_R_3 ctime_r(&rawtime, szTime, 50); #else ctime_r(&rawtime, szTime); #endif szTime[50-1] = '\0'; szTime[strlen(szTime) - 1] = '\0'; // trim LF FILE* file = fopen(m_szLogFilename, "ab+"); if (file) { #ifdef WIN32 unsigned long iThreadId = GetCurrentThreadId(); #else unsigned long iThreadId = (unsigned long)pthread_self(); #endif #ifdef DEBUG fprintf(file, "%s\t%lu\t%s%s", szTime, iThreadId, tmp2, LINE_ENDING); #else fprintf(file, "%s\t%s%s", szTime, tmp2, LINE_ENDING); #endif fclose(file); } else { perror(m_szLogFilename); } } } #ifdef DEBUG #undef debug #ifdef HAVE_VARIADIC_MACROS void debug(const char* szFilename, const char* szFuncname, int iLineNr, const char* msg, ...) #else void debug(const char* msg, ...) #endif { char tmp1[1024]; va_list ap; va_start(ap, msg); vsnprintf(tmp1, 1024, msg, ap); tmp1[1024-1] = '\0'; va_end(ap); char tmp2[1024]; #ifdef HAVE_VARIADIC_MACROS if (szFuncname) { snprintf(tmp2, 1024, "%s (%s:%i:%s)", tmp1, Util::BaseFileName(szFilename), iLineNr, szFuncname); } else { snprintf(tmp2, 1024, "%s (%s:%i)", tmp1, Util::BaseFileName(szFilename), iLineNr); } #else snprintf(tmp2, 1024, "%s", tmp1); #endif tmp2[1024-1] = '\0'; g_pLog->m_mutexLog.Lock(); if (!g_pOptions && g_pLog->m_bExtraDebug) { printf("%s\n", tmp2); } Options::EMessageTarget eMessageTarget = g_pOptions ? g_pOptions->GetDebugTarget() : Options::mtScreen; if (eMessageTarget == Options::mtLog || eMessageTarget == Options::mtBoth) { g_pLog->Filelog("DEBUG\t%s", tmp2); } if (eMessageTarget == Options::mtScreen || eMessageTarget == Options::mtBoth) { g_pLog->AppendMessage(Message::mkDebug, tmp2); } g_pLog->m_mutexLog.Unlock(); } #endif void error(const char* msg, ...) { char tmp2[1024]; va_list ap; va_start(ap, msg); vsnprintf(tmp2, 1024, msg, ap); tmp2[1024-1] = '\0'; va_end(ap); g_pLog->m_mutexLog.Lock(); Options::EMessageTarget eMessageTarget = g_pOptions ? g_pOptions->GetErrorTarget() : Options::mtBoth; if (eMessageTarget == Options::mtLog || eMessageTarget == Options::mtBoth) { g_pLog->Filelog("ERROR\t%s", tmp2); } if (eMessageTarget == Options::mtScreen || eMessageTarget == Options::mtBoth) { g_pLog->AppendMessage(Message::mkError, tmp2); } g_pLog->m_mutexLog.Unlock(); } void warn(const char* msg, ...) { char tmp2[1024]; va_list ap; va_start(ap, msg); vsnprintf(tmp2, 1024, msg, ap); tmp2[1024-1] = '\0'; va_end(ap); g_pLog->m_mutexLog.Lock(); Options::EMessageTarget eMessageTarget = g_pOptions ? g_pOptions->GetWarningTarget() : Options::mtScreen; if (eMessageTarget == Options::mtLog || eMessageTarget == Options::mtBoth) { g_pLog->Filelog("WARNING\t%s", tmp2); } if (eMessageTarget == Options::mtScreen || eMessageTarget == Options::mtBoth) { g_pLog->AppendMessage(Message::mkWarning, tmp2); } g_pLog->m_mutexLog.Unlock(); } void info(const char* msg, ...) { char tmp2[1024]; va_list ap; va_start(ap, msg); vsnprintf(tmp2, 1024, msg, ap); tmp2[1024-1] = '\0'; va_end(ap); g_pLog->m_mutexLog.Lock(); Options::EMessageTarget eMessageTarget = g_pOptions ? g_pOptions->GetInfoTarget() : Options::mtScreen; if (eMessageTarget == Options::mtLog || eMessageTarget == Options::mtBoth) { g_pLog->Filelog("INFO\t%s", tmp2); } if (eMessageTarget == Options::mtScreen || eMessageTarget == Options::mtBoth) { g_pLog->AppendMessage(Message::mkInfo, tmp2); } g_pLog->m_mutexLog.Unlock(); } void detail(const char* msg, ...) { char tmp2[1024]; va_list ap; va_start(ap, msg); vsnprintf(tmp2, 1024, msg, ap); tmp2[1024-1] = '\0'; va_end(ap); g_pLog->m_mutexLog.Lock(); Options::EMessageTarget eMessageTarget = g_pOptions ? g_pOptions->GetDetailTarget() : Options::mtScreen; if (eMessageTarget == Options::mtLog || eMessageTarget == Options::mtBoth) { g_pLog->Filelog("DETAIL\t%s", tmp2); } if (eMessageTarget == Options::mtScreen || eMessageTarget == Options::mtBoth) { g_pLog->AppendMessage(Message::mkDetail, tmp2); } g_pLog->m_mutexLog.Unlock(); } void abort(const char* msg, ...) { char tmp2[1024]; va_list ap; va_start(ap, msg); vsnprintf(tmp2, 1024, msg, ap); tmp2[1024-1] = '\0'; va_end(ap); g_pLog->m_mutexLog.Lock(); printf("\n%s", tmp2); g_pLog->Filelog(tmp2); g_pLog->m_mutexLog.Unlock(); exit(-1); } //************************************************************ // Message Message::Message(unsigned int iID, EKind eKind, time_t tTime, const char* szText) { m_iID = iID; m_eKind = eKind; m_tTime = tTime; if (szText) { m_szText = strdup(szText); } else { m_szText = NULL; } } Message::~ Message() { free(m_szText); } void Log::Clear() { m_mutexLog.Lock(); for (Messages::iterator it = m_Messages.begin(); it != m_Messages.end(); it++) { delete *it; } m_Messages.clear(); m_mutexLog.Unlock(); } void Log::AppendMessage(Message::EKind eKind, const char * szText) { Message* pMessage = new Message(++m_iIDGen, eKind, time(NULL), szText); m_Messages.push_back(pMessage); if (g_pOptions) { while (m_Messages.size() > (unsigned int)g_pOptions->GetLogBufferSize()) { Message* pMessage = m_Messages.front(); delete pMessage; m_Messages.pop_front(); } } } Log::Messages* Log::LockMessages() { m_mutexLog.Lock(); return &m_Messages; } void Log::UnlockMessages() { m_mutexLog.Unlock(); } void Log::ResetLog() { remove(g_pOptions->GetLogFile()); } /* * During intializing stage (when options were not read yet) all messages * are saved in screen log, even if they shouldn't (according to options). * Method "InitOptions()" check all messages added to screen log during * intializing stage and does three things: * 1) save the messages to log-file (if they should according to options); * 2) delete messages from screen log (if they should not be saved in screen log). * 3) renumerate IDs */ void Log::InitOptions() { const char* szMessageType[] = { "INFO", "WARNING", "ERROR", "DEBUG", "DETAIL"}; if (g_pOptions->GetCreateLog() && g_pOptions->GetLogFile()) { m_szLogFilename = strdup(g_pOptions->GetLogFile()); #ifdef WIN32 WebUtil::Utf8ToAnsi(m_szLogFilename, strlen(m_szLogFilename) + 1); #endif } m_iIDGen = 0; for (unsigned int i = 0; i < m_Messages.size(); ) { Message* pMessage = m_Messages.at(i); Options::EMessageTarget eTarget = Options::mtNone; switch (pMessage->GetKind()) { case Message::mkDebug: eTarget = g_pOptions->GetDebugTarget(); break; case Message::mkDetail: eTarget = g_pOptions->GetDetailTarget(); break; case Message::mkInfo: eTarget = g_pOptions->GetInfoTarget(); break; case Message::mkWarning: eTarget = g_pOptions->GetWarningTarget(); break; case Message::mkError: eTarget = g_pOptions->GetErrorTarget(); break; } if (eTarget == Options::mtLog || eTarget == Options::mtBoth) { Filelog("%s\t%s", szMessageType[pMessage->GetKind()], pMessage->GetText()); } if (eTarget == Options::mtLog || eTarget == Options::mtNone) { delete pMessage; m_Messages.erase(m_Messages.begin() + i); } else { pMessage->m_iID = ++m_iIDGen; i++; } } } nzbget-12.0+dfsg/Log.h000066400000000000000000000057601226450633000145470ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2009 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 512 $ * $Date: 2012-11-20 21:42:00 +0100 (Tue, 20 Nov 2012) $ * */ #ifndef LOG_H #define LOG_H #include #include #include "Thread.h" void error(const char* msg, ...); void warn(const char* msg, ...); void info(const char* msg, ...); void detail(const char* msg, ...); void abort(const char* msg, ...); #ifdef DEBUG #ifdef HAVE_VARIADIC_MACROS void debug(const char* szFilename, const char* szFuncname, int iLineNr, const char* msg, ...); #else void debug(const char* msg, ...); #endif #endif class Message { public: enum EKind { mkInfo, mkWarning, mkError, mkDebug, mkDetail }; private: unsigned int m_iID; EKind m_eKind; time_t m_tTime; char* m_szText; friend class Log; public: Message(unsigned int iID, EKind eKind, time_t tTime, const char* szText); ~Message(); unsigned int GetID() { return m_iID; } EKind GetKind() { return m_eKind; } time_t GetTime() { return m_tTime; } const char* GetText() { return m_szText; } }; class Log { public: typedef std::deque Messages; private: Mutex m_mutexLog; Messages m_Messages; char* m_szLogFilename; unsigned int m_iIDGen; #ifdef DEBUG bool m_bExtraDebug; #endif void Filelog(const char* msg, ...); void AppendMessage(Message::EKind eKind, const char* szText); friend void error(const char* msg, ...); friend void warn(const char* msg, ...); friend void info(const char* msg, ...); friend void abort(const char* msg, ...); friend void detail(const char* msg, ...); #ifdef DEBUG #ifdef HAVE_VARIADIC_MACROS friend void debug(const char* szFilename, const char* szFuncname, int iLineNr, const char* msg, ...); #else friend void debug(const char* msg, ...); #endif #endif public: Log(); ~Log(); Messages* LockMessages(); void UnlockMessages(); void Clear(); void ResetLog(); void InitOptions(); }; #ifdef DEBUG #ifdef HAVE_VARIADIC_MACROS #define debug(...) debug(__FILE__, FUNCTION_MACRO_NAME, __LINE__, __VA_ARGS__) #endif #else #define debug(...) do { } while(0) #endif extern Log* g_pLog; #endif nzbget-12.0+dfsg/LoggableFrontend.cpp000066400000000000000000000055261226450633000175750ustar00rootroot00000000000000/* * This file if part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 397 $ * $Date: 2011-05-24 14:52:41 +0200 (Tue, 24 May 2011) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #ifndef WIN32 #include #endif #include "nzbget.h" #include "LoggableFrontend.h" #include "Log.h" LoggableFrontend::LoggableFrontend() { debug("Creating LoggableFrontend"); m_iNeededLogEntries = 0; m_bSummary = false; m_bFileList = false; } void LoggableFrontend::Run() { debug("Entering LoggableFrontend-loop"); while (!IsStopped()) { Update(); usleep(m_iUpdateInterval * 1000); } // Printing the last messages Update(); BeforeExit(); debug("Exiting LoggableFrontend-loop"); } void LoggableFrontend::Update() { if (!PrepareData()) { FreeData(); return; } BeforePrint(); Log::Messages* pMessages = LockMessages(); if (!pMessages->empty()) { Message* pFirstMessage = pMessages->front(); int iStart = m_iNeededLogFirstID - pFirstMessage->GetID() + 1; if (iStart < 0) { PrintSkip(); iStart = 0; } for (unsigned int i = (unsigned int)iStart; i < pMessages->size(); i++) { PrintMessage((*pMessages)[i]); m_iNeededLogFirstID = (*pMessages)[i]->GetID(); } } UnlockMessages(); PrintStatus(); FreeData(); fflush(stdout); } 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: printf("[DEBUG] %s\n", msg); break; case Message::mkError: printf("[ERROR] %s\n", msg); break; case Message::mkWarning: printf("[WARNING] %s\n", msg); break; case Message::mkInfo: printf("[INFO] %s\n", msg); break; case Message::mkDetail: printf("[DETAIL] %s\n", msg); break; } #ifdef WIN32 free(msg); #endif } void LoggableFrontend::PrintSkip() { printf(".....\n"); } nzbget-12.0+dfsg/LoggableFrontend.h000066400000000000000000000025771226450633000172450ustar00rootroot00000000000000/* * This file if part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 397 $ * $Date: 2011-05-24 14:52:41 +0200 (Tue, 24 May 2011) $ * */ #ifndef LOGGABLEFRONTEND_H #define LOGGABLEFRONTEND_H #include "Frontend.h" #include "Log.h" class LoggableFrontend : public Frontend { protected: virtual void Run(); virtual void Update(); virtual void BeforePrint() {}; virtual void BeforeExit() {}; virtual void PrintMessage(Message* pMessage); virtual void PrintStatus() {}; virtual void PrintSkip(); public: LoggableFrontend(); }; #endif nzbget-12.0+dfsg/Maintenance.cpp000066400000000000000000000165651226450633000166100ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 911 $ * $Date: 2013-11-24 20:29:52 +0100 (Sun, 24 Nov 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #include #ifndef WIN32 #include #endif #include #include "nzbget.h" #include "Log.h" #include "Util.h" #include "Maintenance.h" extern Options* g_pOptions; extern Maintenance* g_pMaintenance; Maintenance::Maintenance() { m_iIDMessageGen = 0; m_UpdateScriptController = NULL; m_szUpdateScript = NULL; } Maintenance::~Maintenance() { m_mutexController.Lock(); if (m_UpdateScriptController) { m_UpdateScriptController->Detach(); m_mutexController.Unlock(); while (m_UpdateScriptController) { usleep(20*1000); } } ClearMessages(); free(m_szUpdateScript); } void Maintenance::ResetUpdateController() { m_mutexController.Lock(); m_UpdateScriptController = NULL; m_mutexController.Unlock(); } void Maintenance::ClearMessages() { for (Log::Messages::iterator it = m_Messages.begin(); it != m_Messages.end(); it++) { delete *it; } m_Messages.clear(); } Log::Messages* Maintenance::LockMessages() { m_mutexLog.Lock(); return &m_Messages; } void Maintenance::UnlockMessages() { m_mutexLog.Unlock(); } void Maintenance::AppendMessage(Message::EKind eKind, time_t tTime, const char * szText) { if (tTime == 0) { tTime = time(NULL); } m_mutexLog.Lock(); Message* pMessage = new Message(++m_iIDMessageGen, eKind, tTime, szText); m_Messages.push_back(pMessage); m_mutexLog.Unlock(); } bool Maintenance::StartUpdate(EBranch eBranch) { m_mutexController.Lock(); bool bAlreadyUpdating = m_UpdateScriptController != NULL; m_mutexController.Unlock(); if (bAlreadyUpdating) { error("Could not start update-script: update-script is already running"); return false; } if (m_szUpdateScript) { free(m_szUpdateScript); m_szUpdateScript = NULL; } if (!ReadPackageInfoStr("install-script", &m_szUpdateScript)) { return false; } ClearMessages(); m_UpdateScriptController = new UpdateScriptController(); m_UpdateScriptController->SetScript(m_szUpdateScript); m_UpdateScriptController->SetBranch(eBranch); m_UpdateScriptController->SetAutoDestroy(true); m_UpdateScriptController->Start(); return true; } bool Maintenance::CheckUpdates(char** pUpdateInfo) { char* szUpdateInfoScript; if (!ReadPackageInfoStr("update-info-script", &szUpdateInfoScript)) { return false; } *pUpdateInfo = NULL; UpdateInfoScriptController::ExecuteScript(szUpdateInfoScript, pUpdateInfo); free(szUpdateInfoScript); return *pUpdateInfo; } bool Maintenance::ReadPackageInfoStr(const char* szKey, char** pValue) { char szFileName[1024]; snprintf(szFileName, 1024, "%s%cpackage-info.json", g_pOptions->GetWebDir(), PATH_SEPARATOR); szFileName[1024-1] = '\0'; char* szPackageInfo; int iPackageInfoLen; if (!Util::LoadFileIntoBuffer(szFileName, &szPackageInfo, &iPackageInfoLen)) { error("Could not load file %s", szFileName); return false; } char szKeyStr[100]; snprintf(szKeyStr, 100, "\"%s\"", szKey); szKeyStr[100-1] = '\0'; char* p = strstr(szPackageInfo, szKeyStr); if (!p) { error("Could not parse file %s", szFileName); free(szPackageInfo); return false; } p = strchr(p + strlen(szKeyStr), '"'); if (!p) { error("Could not parse file %s", szFileName); free(szPackageInfo); return false; } p++; char* pend = strchr(p, '"'); if (!pend) { error("Could not parse file %s", szFileName); free(szPackageInfo); return false; } int iLen = pend - p; if (iLen >= sizeof(szFileName)) { error("Could not parse file %s", szFileName); free(szPackageInfo); return false; } *pValue = (char*)malloc(iLen+1); strncpy(*pValue, p, iLen); (*pValue)[iLen] = '\0'; WebUtil::JsonDecode(*pValue); free(szPackageInfo); return true; } void UpdateScriptController::Run() { m_iPrefixLen = 0; PrintMessage(Message::mkInfo, "Executing update-script %s", GetScript()); char szInfoName[1024]; snprintf(szInfoName, 1024, "update-script %s", Util::BaseFileName(GetScript())); szInfoName[1024-1] = '\0'; SetInfoName(szInfoName); const char* szBranchName[] = { "STABLE", "TESTING", "DEVEL" }; SetEnvVar("NZBUP_BRANCH", szBranchName[m_eBranch]); char szProcessID[20]; #ifdef WIN32 int pid = (int)GetCurrentProcessId(); #else int pid = (int)getppid(); #endif snprintf(szProcessID, 20, "%i", pid); szProcessID[20-1] = '\0'; SetEnvVar("NZBUP_PROCESSID", szProcessID); char szLogPrefix[100]; strncpy(szLogPrefix, Util::BaseFileName(GetScript()), 100); szLogPrefix[100-1] = '\0'; if (char* ext = strrchr(szLogPrefix, '.')) *ext = '\0'; // strip file extension SetLogPrefix(szLogPrefix); m_iPrefixLen = strlen(szLogPrefix) + 2; // 2 = strlen(": "); Execute(); g_pMaintenance->ResetUpdateController(); } void UpdateScriptController::AddMessage(Message::EKind eKind, const char* szText) { szText = szText + m_iPrefixLen; g_pMaintenance->AppendMessage(eKind, time(NULL), szText); ScriptController::AddMessage(eKind, szText); } void UpdateInfoScriptController::ExecuteScript(const char* szScript, char** pUpdateInfo) { detail("Executing update-info-script %s", Util::BaseFileName(szScript)); UpdateInfoScriptController* pScriptController = new UpdateInfoScriptController(); pScriptController->SetScript(szScript); char szInfoName[1024]; snprintf(szInfoName, 1024, "update-info-script %s", Util::BaseFileName(szScript)); szInfoName[1024-1] = '\0'; pScriptController->SetInfoName(szInfoName); char szLogPrefix[1024]; strncpy(szLogPrefix, Util::BaseFileName(szScript), 1024); szLogPrefix[1024-1] = '\0'; if (char* ext = strrchr(szLogPrefix, '.')) *ext = '\0'; // strip file extension pScriptController->SetLogPrefix(szLogPrefix); pScriptController->m_iPrefixLen = strlen(szLogPrefix) + 2; // 2 = strlen(": "); pScriptController->Execute(); if (pScriptController->m_UpdateInfo.GetBuffer()) { int iLen = strlen(pScriptController->m_UpdateInfo.GetBuffer()); *pUpdateInfo = (char*)malloc(iLen + 1); strncpy(*pUpdateInfo, pScriptController->m_UpdateInfo.GetBuffer(), iLen); (*pUpdateInfo)[iLen] = '\0'; } delete pScriptController; } void UpdateInfoScriptController::AddMessage(Message::EKind eKind, const char* szText) { szText = szText + m_iPrefixLen; if (!strncmp(szText, "[NZB] ", 6)) { debug("Command %s detected", szText + 6); if (!strncmp(szText + 6, "[UPDATEINFO]", 12)) { m_UpdateInfo.Append(szText + 6 + 12); } else { error("Invalid command \"%s\" received from %s", szText, GetInfoName()); } } else { ScriptController::AddMessage(eKind, szText); } } nzbget-12.0+dfsg/Maintenance.h000066400000000000000000000045251226450633000162460ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 881 $ * $Date: 2013-10-17 21:35:43 +0200 (Thu, 17 Oct 2013) $ * */ #ifndef MAINTENANCE_H #define MAINTENANCE_H #include "Thread.h" #include "ScriptController.h" #include "Log.h" #include "Util.h" class UpdateScriptController; class Maintenance { private: Log::Messages m_Messages; Mutex m_mutexLog; Mutex m_mutexController; int m_iIDMessageGen; UpdateScriptController* m_UpdateScriptController; char* m_szUpdateScript; bool ReadPackageInfoStr(const char* szKey, char** pValue); public: enum EBranch { brStable, brTesting, brDevel }; Maintenance(); ~Maintenance(); void ClearMessages(); void AppendMessage(Message::EKind eKind, time_t tTime, const char* szText); Log::Messages* LockMessages(); void UnlockMessages(); bool StartUpdate(EBranch eBranch); void ResetUpdateController(); bool CheckUpdates(char** pUpdateInfo); }; class UpdateScriptController : public Thread, public ScriptController { private: Maintenance::EBranch m_eBranch; int m_iPrefixLen; protected: virtual void AddMessage(Message::EKind eKind, const char* szText); public: virtual void Run(); void SetBranch(Maintenance::EBranch eBranch) { m_eBranch = eBranch; } }; class UpdateInfoScriptController : public ScriptController { private: int m_iPrefixLen; StringBuilder m_UpdateInfo; protected: virtual void AddMessage(Message::EKind eKind, const char* szText); public: static void ExecuteScript(const char* szScript, char** pUpdateInfo); }; #endif nzbget-12.0+dfsg/Makefile.am000066400000000000000000000202111226450633000156750ustar00rootroot00000000000000# # This file if part of nzbget # # Copyright (C) 2008-2013 Andrey Prygunkov # # 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # bin_PROGRAMS = nzbget nzbget_SOURCES = \ ArticleDownloader.cpp ArticleDownloader.h BinRpc.cpp BinRpc.h \ ColoredFrontend.cpp ColoredFrontend.h Connection.cpp Connection.h Decoder.cpp Decoder.h \ DiskState.cpp DiskState.h DownloadInfo.cpp DownloadInfo.h DupeCoordinator.cpp DupeCoordinator.h \ Frontend.cpp Frontend.h FeedCoordinator.cpp FeedCoordinator.h FeedFile.cpp FeedFile.h \ FeedFilter.cpp FeedFilter.h FeedInfo.cpp FeedInfo.h Log.cpp Log.h LoggableFrontend.cpp \ LoggableFrontend.h Maintenance.cpp Maintenance.h MessageBase.h NCursesFrontend.cpp \ NCursesFrontend.h NNTPConnection.cpp \ NNTPConnection.h NZBFile.cpp NZBFile.h NewsServer.cpp NewsServer.h Observer.cpp \ Observer.h Options.cpp Options.h ParChecker.cpp ParChecker.h ParRenamer.cpp ParRenamer.h \ ParCoordinator.cpp ParCoordinator.h PrePostProcessor.cpp PrePostProcessor.h QueueCoordinator.cpp \ QueueCoordinator.h QueueEditor.cpp QueueEditor.h RemoteClient.cpp RemoteClient.h \ RemoteServer.cpp RemoteServer.h Scanner.cpp Scanner.h Scheduler.cpp Scheduler.h ScriptController.cpp \ ScriptController.h ServerPool.cpp ServerPool.h svn_version.cpp TLS.cpp TLS.h Thread.cpp Thread.h \ Util.cpp Util.h XmlRpc.cpp XmlRpc.h WebDownloader.cpp WebDownloader.h WebServer.cpp WebServer.h \ UrlCoordinator.cpp UrlCoordinator.h Unpack.cpp Unpack.h nzbget.cpp nzbget.h EXTRA_DIST = \ Makefile.cvs nzbgetd \ $(patches_FILES) $(windows_FILES) $(osx_FILES) patches_FILES = \ libpar2-0.2-bugfixes.patch libpar2-0.2-cancel.patch \ libpar2-0.2-MSVC8.patch libsigc++-2.0.18-MSVC8.patch windows_FILES = \ win32.h NTService.cpp NTService.h nzbget.sln nzbget.vcproj nzbget-shell.bat osx_FILES = \ osx/App_Prefix.pch osx/NZBGet-Info.plist \ osx/DaemonController.h osx/DaemonController.m \ osx/MainApp.h osx/MainApp.m osx/MainApp.xib \ osx/PFMoveApplication.h osx/PFMoveApplication.m \ osx/PreferencesDialog.h osx/PreferencesDialog.m osx/PreferencesDialog.xib \ osx/RPC.h osx/RPC.m osx/WebClient.h osx/WebClient.m \ osx/WelcomeDialog.h osx/WelcomeDialog.m osx/WelcomeDialog.xib \ osx/NZBGet.xcodeproj/project.pbxproj \ osx/Resources/Images/mainicon.icns osx/Resources/Images/statusicon.png \ osx/Resources/Images/statusicon@2x.png osx/Resources/Images/statusicon-inv.png \ osx/Resources/Images/statusicon-inv@2x.png osx/Resources/licenses/license-bootstrap.txt \ osx/Resources/licenses/license-jquery-GPL.txt osx/Resources/licenses/license-jquery-MIT.txt \ osx/Resources/Credits.rtf osx/Resources/Localizable.strings osx/Resources/Welcome.rtf doc_FILES = \ README ChangeLog COPYING exampleconf_FILES = \ nzbget.conf webui_FILES = \ webui/index.html webui/index.js webui/downloads.js webui/edit.js webui/fasttable.js \ webui/history.js webui/messages.js webui/status.js webui/style.css webui/upload.js \ webui/util.js webui/config.js webui/feed.js \ webui/lib/bootstrap.js webui/lib/bootstrap.min.js webui/lib/bootstrap.css \ webui/lib/jquery.js webui/lib/jquery.min.js \ webui/img/icons.png webui/img/icons-2x.png \ webui/img/transmit.gif webui/img/transmit-file.gif webui/img/favicon.ico \ webui/img/download-anim-green-2x.png webui/img/download-anim-orange-2x.png \ webui/img/transmit-reload-2x.gif ppscripts_FILES = \ ppscripts/EMail.py ppscripts/Logger.py # Install sbin_SCRIPTS = nzbgetd dist_doc_DATA = $(doc_FILES) exampleconfdir = $(datadir)/nzbget dist_exampleconf_DATA = $(exampleconf_FILES) webuidir = $(datadir)/nzbget nobase_dist_webui_DATA = $(webui_FILES) ppscriptsdir = $(datadir)/nzbget nobase_dist_ppscripts_SCRIPTS = $(ppscripts_FILES) # Note about "sed": # We need to make some changes in installed files. # On Linux "sed" has option "-i" for in-place-edit. Unfortunateley the BSD version of "sed" # has incompatible syntax. To solve the problem we perform in-place-edit in three steps: # 1) copy the original file to original.temp (delete existing original.temp, if any); # 2) sed < original.temp > original # 3) delete original.temp # These steps ensure that the output file has the same permissions as the original file. # Configure installed script install-exec-hook: rm -f "$(DESTDIR)$(sbindir)/nzbgetd.temp" cp "$(DESTDIR)$(sbindir)/nzbgetd" "$(DESTDIR)$(sbindir)/nzbgetd.temp" sed 's?/usr/local/bin?$(bindir)?' < "$(DESTDIR)$(sbindir)/nzbgetd.temp" > "$(DESTDIR)$(sbindir)/nzbgetd" rm "$(DESTDIR)$(sbindir)/nzbgetd.temp" # Prepare example configuration file install-data-hook: rm -f "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" cp "$(DESTDIR)$(exampleconfdir)/nzbget.conf" "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" sed 's:^ConfigTemplate=:ConfigTemplate=$(exampleconfdir)/nzbget.conf:' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf" sed 's:configuration file (typically installed:configuration file (installed:' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" sed 's:/usr/local/share/nzbget/nzbget.conf):$(exampleconfdir)/nzbget.conf):' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf" sed 's:^WebDir=:WebDir=$(webuidir)/webui:' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" sed 's:typically installed to /usr/local/share/nzbget/ppscripts:installed to $(ppscriptsdir)/ppscripts:' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf" rm "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" # Install configuration files into /etc # (only if they do not exist there to prevent override by update) install-conf: if test ! -f "$(DESTDIR)$(sysconfdir)/nzbget.conf" ; then \ $(mkinstalldirs) "$(DESTDIR)$(sysconfdir)" ; \ cp "$(DESTDIR)$(exampleconfdir)/nzbget.conf" "$(DESTDIR)$(sysconfdir)/nzbget.conf" ; \ fi uninstall-conf: rm -f "$(DESTDIR)$(sysconfdir)/nzbget.conf" # Determining subversion revision: # 1) If directory ".svn" exists we take revision from it using program svnversion (part of subversion package) # File is recreated only if revision number was changed. # 2) If directory ".svn" doesn't exists we keep and reuse file "svn_version.cpp", # which was possibly created early. # 3) If neither directory ".svn" nor file "svn_version.cpp" are available # we create new file "svn_version.c" with empty revision number. svn_version.cpp: FORCE @ if test -d ./.svn ; then \ V="$(shell svnversion -n .)"; \ H="$(shell test -f ./svn_version.cpp && head -n 1 svn_version.cpp)"; \ if test "/* $$V */" != "$$H" ; then \ ( \ echo "/* $$V */" ;\ echo "/* This file is automatically regenerated on each build. Do not edit it. */" ;\ echo "const char* svn_version(void)" ;\ echo "{" ;\ echo " const char* SVN_Version = \"$$V\";" ;\ echo " return SVN_Version;" ;\ echo "}" ;\ ) > svn_version.cpp ; \ fi \ elif test -f ./svn_version.cpp ; then \ test "ok, reuse existing file"; \ else \ ( \ echo "/* */" ;\ echo "/* This file is automatically regenerated on each build. Do not edit it. */" ;\ echo "const char* svn_version(void)" ;\ echo "{" ;\ echo " const char* SVN_Version = \"\";" ;\ echo " return SVN_Version;" ;\ echo "}" ;\ ) > svn_version.cpp ; \ fi FORCE: # Ignore "svn_version.cpp" in distcleancheck distcleancheck_listfiles = \ find . -type f -exec sh -c 'test -f $(srcdir)/$$1 || echo $$1' \ sh '{}' ';' clean-bak: rm *~ # Fix premissions dist-hook: chmod -x $(distdir)/*.cpp $(distdir)/*.h find $(distdir)/webui -type f -print -exec chmod -x {} \; nzbget-12.0+dfsg/Makefile.cvs000066400000000000000000000000751226450633000161010ustar00rootroot00000000000000default: all all: aclocal autoheader automake autoconf nzbget-12.0+dfsg/Makefile.in000066400000000000000000001071741226450633000157240ustar00rootroot00000000000000# Makefile.in generated by automake 1.9.6 from Makefile.am. # @configure_input@ # Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, # 2003, 2004, 2005 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. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ # # This file if part of nzbget # # Copyright (C) 2008-2013 Andrey Prygunkov # # 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # 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 INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ target_triplet = @target@ bin_PROGRAMS = nzbget$(EXEEXT) DIST_COMMON = README $(am__configure_deps) $(dist_doc_DATA) \ $(dist_exampleconf_DATA) $(nobase_dist_ppscripts_SCRIPTS) \ $(nobase_dist_webui_DATA) $(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 mkinstalldirs = $(install_sh) -d CONFIG_HEADER = config.h CONFIG_CLEAN_FILES = am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(ppscriptsdir)" \ "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(docdir)" \ "$(DESTDIR)$(exampleconfdir)" "$(DESTDIR)$(webuidir)" binPROGRAMS_INSTALL = $(INSTALL_PROGRAM) PROGRAMS = $(bin_PROGRAMS) am_nzbget_OBJECTS = ArticleDownloader.$(OBJEXT) BinRpc.$(OBJEXT) \ ColoredFrontend.$(OBJEXT) Connection.$(OBJEXT) \ Decoder.$(OBJEXT) DiskState.$(OBJEXT) DownloadInfo.$(OBJEXT) \ DupeCoordinator.$(OBJEXT) Frontend.$(OBJEXT) \ FeedCoordinator.$(OBJEXT) FeedFile.$(OBJEXT) \ FeedFilter.$(OBJEXT) FeedInfo.$(OBJEXT) Log.$(OBJEXT) \ LoggableFrontend.$(OBJEXT) Maintenance.$(OBJEXT) \ NCursesFrontend.$(OBJEXT) NNTPConnection.$(OBJEXT) \ NZBFile.$(OBJEXT) NewsServer.$(OBJEXT) Observer.$(OBJEXT) \ Options.$(OBJEXT) ParChecker.$(OBJEXT) ParRenamer.$(OBJEXT) \ ParCoordinator.$(OBJEXT) PrePostProcessor.$(OBJEXT) \ QueueCoordinator.$(OBJEXT) QueueEditor.$(OBJEXT) \ RemoteClient.$(OBJEXT) RemoteServer.$(OBJEXT) \ Scanner.$(OBJEXT) Scheduler.$(OBJEXT) \ ScriptController.$(OBJEXT) ServerPool.$(OBJEXT) \ svn_version.$(OBJEXT) TLS.$(OBJEXT) Thread.$(OBJEXT) \ Util.$(OBJEXT) XmlRpc.$(OBJEXT) WebDownloader.$(OBJEXT) \ WebServer.$(OBJEXT) UrlCoordinator.$(OBJEXT) Unpack.$(OBJEXT) \ nzbget.$(OBJEXT) nzbget_OBJECTS = $(am_nzbget_OBJECTS) nzbget_LDADD = $(LDADD) am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; am__vpath_adj = case $$p in \ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ *) f=$$p;; \ esac; am__strip_dir = `echo $$p | sed -e 's|^.*/||'`; nobase_dist_ppscriptsSCRIPT_INSTALL = $(install_sh_SCRIPT) sbinSCRIPT_INSTALL = $(INSTALL_SCRIPT) SCRIPTS = $(nobase_dist_ppscripts_SCRIPTS) $(sbin_SCRIPTS) DEFAULT_INCLUDES = -I. -I$(srcdir) -I. depcomp = $(SHELL) $(top_srcdir)/depcomp am__depfiles_maybe = depfiles CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) CXXLD = $(CXX) CXXLINK = $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) \ -o $@ COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) CCLD = $(CC) LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ SOURCES = $(nzbget_SOURCES) DIST_SOURCES = $(nzbget_SOURCES) dist_docDATA_INSTALL = $(INSTALL_DATA) dist_exampleconfDATA_INSTALL = $(INSTALL_DATA) nobase_dist_webuiDATA_INSTALL = $(install_sh_DATA) DATA = $(dist_doc_DATA) $(dist_exampleconf_DATA) \ $(nobase_dist_webui_DATA) ETAGS = etags CTAGS = ctags DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) distdir = $(PACKAGE)-$(VERSION) top_distdir = $(distdir) am__remove_distdir = \ { test ! -d $(distdir) \ || { find $(distdir) -type d ! -perm -200 -exec chmod u+w {} ';' \ && rm -fr $(distdir); }; } DIST_ARCHIVES = $(distdir).tar.gz GZIP_ENV = --best distuninstallcheck_listfiles = find . -type f -print ACLOCAL = @ACLOCAL@ AMDEP_FALSE = @AMDEP_FALSE@ AMDEP_TRUE = @AMDEP_TRUE@ AMTAR = @AMTAR@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ CPPFLAGS = @CPPFLAGS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ GREP = @GREP@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ LDFLAGS = @LDFLAGS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LTLIBOBJS = @LTLIBOBJS@ MAKE = @MAKE@ MAKEINFO = @MAKEINFO@ OBJEXT = @OBJEXT@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_VERSION = @PACKAGE_VERSION@ PATH_SEPARATOR = @PATH_SEPARATOR@ PKG_CONFIG = @PKG_CONFIG@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ STRIP = @STRIP@ TAR = @TAR@ VERSION = @VERSION@ ac_ct_CXX = @ac_ct_CXX@ 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@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ 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@ libsigc_CFLAGS = @libsigc_CFLAGS@ libsigc_LIBS = @libsigc_LIBS@ libxml2_CFLAGS = @libxml2_CFLAGS@ libxml2_LIBS = @libxml2_LIBS@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ oldincludedir = @oldincludedir@ openssl_CFLAGS = @openssl_CFLAGS@ openssl_LIBS = @openssl_LIBS@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sysconfdir = @sysconfdir@ target = @target@ target_alias = @target_alias@ target_cpu = @target_cpu@ target_os = @target_os@ target_vendor = @target_vendor@ nzbget_SOURCES = \ ArticleDownloader.cpp ArticleDownloader.h BinRpc.cpp BinRpc.h \ ColoredFrontend.cpp ColoredFrontend.h Connection.cpp Connection.h Decoder.cpp Decoder.h \ DiskState.cpp DiskState.h DownloadInfo.cpp DownloadInfo.h DupeCoordinator.cpp DupeCoordinator.h \ Frontend.cpp Frontend.h FeedCoordinator.cpp FeedCoordinator.h FeedFile.cpp FeedFile.h \ FeedFilter.cpp FeedFilter.h FeedInfo.cpp FeedInfo.h Log.cpp Log.h LoggableFrontend.cpp \ LoggableFrontend.h Maintenance.cpp Maintenance.h MessageBase.h NCursesFrontend.cpp \ NCursesFrontend.h NNTPConnection.cpp \ NNTPConnection.h NZBFile.cpp NZBFile.h NewsServer.cpp NewsServer.h Observer.cpp \ Observer.h Options.cpp Options.h ParChecker.cpp ParChecker.h ParRenamer.cpp ParRenamer.h \ ParCoordinator.cpp ParCoordinator.h PrePostProcessor.cpp PrePostProcessor.h QueueCoordinator.cpp \ QueueCoordinator.h QueueEditor.cpp QueueEditor.h RemoteClient.cpp RemoteClient.h \ RemoteServer.cpp RemoteServer.h Scanner.cpp Scanner.h Scheduler.cpp Scheduler.h ScriptController.cpp \ ScriptController.h ServerPool.cpp ServerPool.h svn_version.cpp TLS.cpp TLS.h Thread.cpp Thread.h \ Util.cpp Util.h XmlRpc.cpp XmlRpc.h WebDownloader.cpp WebDownloader.h WebServer.cpp WebServer.h \ UrlCoordinator.cpp UrlCoordinator.h Unpack.cpp Unpack.h nzbget.cpp nzbget.h EXTRA_DIST = \ Makefile.cvs nzbgetd \ $(patches_FILES) $(windows_FILES) $(osx_FILES) patches_FILES = \ libpar2-0.2-bugfixes.patch libpar2-0.2-cancel.patch \ libpar2-0.2-MSVC8.patch libsigc++-2.0.18-MSVC8.patch windows_FILES = \ win32.h NTService.cpp NTService.h nzbget.sln nzbget.vcproj nzbget-shell.bat osx_FILES = \ osx/App_Prefix.pch osx/NZBGet-Info.plist \ osx/DaemonController.h osx/DaemonController.m \ osx/MainApp.h osx/MainApp.m osx/MainApp.xib \ osx/PFMoveApplication.h osx/PFMoveApplication.m \ osx/PreferencesDialog.h osx/PreferencesDialog.m osx/PreferencesDialog.xib \ osx/RPC.h osx/RPC.m osx/WebClient.h osx/WebClient.m \ osx/WelcomeDialog.h osx/WelcomeDialog.m osx/WelcomeDialog.xib \ osx/NZBGet.xcodeproj/project.pbxproj \ osx/Resources/Images/mainicon.icns osx/Resources/Images/statusicon.png \ osx/Resources/Images/statusicon@2x.png osx/Resources/Images/statusicon-inv.png \ osx/Resources/Images/statusicon-inv@2x.png osx/Resources/licenses/license-bootstrap.txt \ osx/Resources/licenses/license-jquery-GPL.txt osx/Resources/licenses/license-jquery-MIT.txt \ osx/Resources/Credits.rtf osx/Resources/Localizable.strings osx/Resources/Welcome.rtf doc_FILES = \ README ChangeLog COPYING exampleconf_FILES = \ nzbget.conf webui_FILES = \ webui/index.html webui/index.js webui/downloads.js webui/edit.js webui/fasttable.js \ webui/history.js webui/messages.js webui/status.js webui/style.css webui/upload.js \ webui/util.js webui/config.js webui/feed.js \ webui/lib/bootstrap.js webui/lib/bootstrap.min.js webui/lib/bootstrap.css \ webui/lib/jquery.js webui/lib/jquery.min.js \ webui/img/icons.png webui/img/icons-2x.png \ webui/img/transmit.gif webui/img/transmit-file.gif webui/img/favicon.ico \ webui/img/download-anim-green-2x.png webui/img/download-anim-orange-2x.png \ webui/img/transmit-reload-2x.gif ppscripts_FILES = \ ppscripts/EMail.py ppscripts/Logger.py # Install sbin_SCRIPTS = nzbgetd dist_doc_DATA = $(doc_FILES) exampleconfdir = $(datadir)/nzbget dist_exampleconf_DATA = $(exampleconf_FILES) webuidir = $(datadir)/nzbget nobase_dist_webui_DATA = $(webui_FILES) ppscriptsdir = $(datadir)/nzbget nobase_dist_ppscripts_SCRIPTS = $(ppscripts_FILES) # Ignore "svn_version.cpp" in distcleancheck distcleancheck_listfiles = \ find . -type f -exec sh -c 'test -f $(srcdir)/$$1 || echo $$1' \ sh '{}' ';' all: config.h $(MAKE) $(AM_MAKEFLAGS) all-am .SUFFIXES: .SUFFIXES: .cpp .o .obj am--refresh: @: $(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ echo ' cd $(srcdir) && $(AUTOMAKE) --gnu '; \ cd $(srcdir) && $(AUTOMAKE) --gnu \ && exit 0; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu Makefile'; \ cd $(top_srcdir) && \ $(AUTOMAKE) --gnu Makefile .PRECIOUS: Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ echo ' $(SHELL) ./config.status'; \ $(SHELL) ./config.status;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe)'; \ cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) $(SHELL) ./config.status --recheck $(top_srcdir)/configure: $(am__configure_deps) cd $(srcdir) && $(AUTOCONF) $(ACLOCAL_M4): $(am__aclocal_m4_deps) cd $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS) config.h: stamp-h1 @if test ! -f $@; then \ rm -f stamp-h1; \ $(MAKE) stamp-h1; \ else :; fi stamp-h1: $(srcdir)/config.h.in $(top_builddir)/config.status @rm -f stamp-h1 cd $(top_builddir) && $(SHELL) ./config.status config.h $(srcdir)/config.h.in: $(am__configure_deps) cd $(top_srcdir) && $(AUTOHEADER) rm -f stamp-h1 touch $@ distclean-hdr: -rm -f config.h stamp-h1 install-binPROGRAMS: $(bin_PROGRAMS) @$(NORMAL_INSTALL) 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 \ ; then \ f=`echo "$$p1" | sed 's,^.*/,,;$(transform);s/$$/$(EXEEXT)/'`; \ echo " $(INSTALL_PROGRAM_ENV) $(binPROGRAMS_INSTALL) '$$p' '$(DESTDIR)$(bindir)/$$f'"; \ $(INSTALL_PROGRAM_ENV) $(binPROGRAMS_INSTALL) "$$p" "$(DESTDIR)$(bindir)/$$f" || exit 1; \ else :; fi; \ done uninstall-binPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(bin_PROGRAMS)'; for p in $$list; do \ f=`echo "$$p" | sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \ echo " rm -f '$(DESTDIR)$(bindir)/$$f'"; \ rm -f "$(DESTDIR)$(bindir)/$$f"; \ done 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) install-nobase_dist_ppscriptsSCRIPTS: $(nobase_dist_ppscripts_SCRIPTS) @$(NORMAL_INSTALL) test -z "$(ppscriptsdir)" || $(mkdir_p) "$(DESTDIR)$(ppscriptsdir)" @$(am__vpath_adj_setup) \ list='$(nobase_dist_ppscripts_SCRIPTS)'; for p in $$list; do \ $(am__vpath_adj) p=$$f; \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ if test -f $$d$$p; then \ f=`echo "$$p" | sed 's|^.*/||;$(transform)'`; \ f=`echo "$$p" | sed 's|[^/]*$$||'`"$$f"; \ echo " $(nobase_dist_ppscriptsSCRIPT_INSTALL) '$$d$$p' '$(DESTDIR)$(ppscriptsdir)/$$f'"; \ $(nobase_dist_ppscriptsSCRIPT_INSTALL) "$$d$$p" "$(DESTDIR)$(ppscriptsdir)/$$f"; \ else :; fi; \ done uninstall-nobase_dist_ppscriptsSCRIPTS: @$(NORMAL_UNINSTALL) @$(am__vpath_adj_setup) \ list='$(nobase_dist_ppscripts_SCRIPTS)'; for p in $$list; do \ $(am__vpath_adj) p=$$f; \ f=`echo "$$p" | sed 's|^.*/||;$(transform)'`; \ f=`echo "$$p" | sed 's|[^/]*$$||'`"$$f"; \ echo " rm -f '$(DESTDIR)$(ppscriptsdir)/$$f'"; \ rm -f "$(DESTDIR)$(ppscriptsdir)/$$f"; \ done install-sbinSCRIPTS: $(sbin_SCRIPTS) @$(NORMAL_INSTALL) test -z "$(sbindir)" || $(mkdir_p) "$(DESTDIR)$(sbindir)" @list='$(sbin_SCRIPTS)'; for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ if test -f $$d$$p; then \ f=`echo "$$p" | sed 's|^.*/||;$(transform)'`; \ echo " $(sbinSCRIPT_INSTALL) '$$d$$p' '$(DESTDIR)$(sbindir)/$$f'"; \ $(sbinSCRIPT_INSTALL) "$$d$$p" "$(DESTDIR)$(sbindir)/$$f"; \ else :; fi; \ done uninstall-sbinSCRIPTS: @$(NORMAL_UNINSTALL) @list='$(sbin_SCRIPTS)'; for p in $$list; do \ f=`echo "$$p" | sed 's|^.*/||;$(transform)'`; \ echo " rm -f '$(DESTDIR)$(sbindir)/$$f'"; \ rm -f "$(DESTDIR)$(sbindir)/$$f"; \ done mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ArticleDownloader.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/BinRpc.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ColoredFrontend.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Connection.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Decoder.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DiskState.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DownloadInfo.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DupeCoordinator.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FeedCoordinator.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FeedFile.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FeedFilter.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FeedInfo.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Frontend.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Log.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/LoggableFrontend.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Maintenance.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NCursesFrontend.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NNTPConnection.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NZBFile.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NewsServer.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Observer.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Options.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ParChecker.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ParCoordinator.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ParRenamer.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)/Scanner.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Scheduler.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ScriptController.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ServerPool.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TLS.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Thread.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Unpack.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/UrlCoordinator.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Util.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/WebDownloader.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/WebServer.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/XmlRpc.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nzbget.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/svn_version.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 @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 @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: install-dist_docDATA: $(dist_doc_DATA) @$(NORMAL_INSTALL) test -z "$(docdir)" || $(mkdir_p) "$(DESTDIR)$(docdir)" @list='$(dist_doc_DATA)'; for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ f=$(am__strip_dir) \ echo " $(dist_docDATA_INSTALL) '$$d$$p' '$(DESTDIR)$(docdir)/$$f'"; \ $(dist_docDATA_INSTALL) "$$d$$p" "$(DESTDIR)$(docdir)/$$f"; \ done uninstall-dist_docDATA: @$(NORMAL_UNINSTALL) @list='$(dist_doc_DATA)'; for p in $$list; do \ f=$(am__strip_dir) \ echo " rm -f '$(DESTDIR)$(docdir)/$$f'"; \ rm -f "$(DESTDIR)$(docdir)/$$f"; \ done install-dist_exampleconfDATA: $(dist_exampleconf_DATA) @$(NORMAL_INSTALL) test -z "$(exampleconfdir)" || $(mkdir_p) "$(DESTDIR)$(exampleconfdir)" @list='$(dist_exampleconf_DATA)'; for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ f=$(am__strip_dir) \ echo " $(dist_exampleconfDATA_INSTALL) '$$d$$p' '$(DESTDIR)$(exampleconfdir)/$$f'"; \ $(dist_exampleconfDATA_INSTALL) "$$d$$p" "$(DESTDIR)$(exampleconfdir)/$$f"; \ done uninstall-dist_exampleconfDATA: @$(NORMAL_UNINSTALL) @list='$(dist_exampleconf_DATA)'; for p in $$list; do \ f=$(am__strip_dir) \ echo " rm -f '$(DESTDIR)$(exampleconfdir)/$$f'"; \ rm -f "$(DESTDIR)$(exampleconfdir)/$$f"; \ done install-nobase_dist_webuiDATA: $(nobase_dist_webui_DATA) @$(NORMAL_INSTALL) test -z "$(webuidir)" || $(mkdir_p) "$(DESTDIR)$(webuidir)" @$(am__vpath_adj_setup) \ list='$(nobase_dist_webui_DATA)'; for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ $(am__vpath_adj) \ echo " $(nobase_dist_webuiDATA_INSTALL) '$$d$$p' '$(DESTDIR)$(webuidir)/$$f'"; \ $(nobase_dist_webuiDATA_INSTALL) "$$d$$p" "$(DESTDIR)$(webuidir)/$$f"; \ done uninstall-nobase_dist_webuiDATA: @$(NORMAL_UNINSTALL) @$(am__vpath_adj_setup) \ list='$(nobase_dist_webui_DATA)'; for p in $$list; do \ $(am__vpath_adj) \ echo " rm -f '$(DESTDIR)$(webuidir)/$$f'"; \ rm -f "$(DESTDIR)$(webuidir)/$$f"; \ done ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | \ $(AWK) ' { files[$$0] = 1; } \ END { for (i in files) print i; }'`; \ mkid -fID $$unique tags: TAGS TAGS: $(HEADERS) $(SOURCES) config.h.in $(TAGS_DEPENDENCIES) \ $(TAGS_FILES) $(LISP) tags=; \ here=`pwd`; \ list='$(SOURCES) $(HEADERS) config.h.in $(LISP) $(TAGS_FILES)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | \ $(AWK) ' { files[$$0] = 1; } \ END { for (i in files) print i; }'`; \ if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$tags $$unique; \ fi ctags: CTAGS CTAGS: $(HEADERS) $(SOURCES) config.h.in $(TAGS_DEPENDENCIES) \ $(TAGS_FILES) $(LISP) tags=; \ here=`pwd`; \ list='$(SOURCES) $(HEADERS) config.h.in $(LISP) $(TAGS_FILES)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | \ $(AWK) ' { files[$$0] = 1; } \ END { for (i in files) print i; }'`; \ test -z "$(CTAGS_ARGS)$$tags$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$tags $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && cd $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) $$here distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(DISTFILES) $(am__remove_distdir) mkdir $(distdir) $(mkdir_p) $(distdir)/osx $(distdir)/osx/NZBGet.xcodeproj $(distdir)/osx/Resources $(distdir)/osx/Resources/Images $(distdir)/osx/Resources/licenses $(distdir)/ppscripts $(distdir)/webui $(distdir)/webui/img $(distdir)/webui/lib @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; \ 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 \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \ fi; \ cp -pR $$d/$$file $(distdir)$$dir || exit 1; \ else \ test -f $(distdir)/$$file \ || cp -p $$d/$$file $(distdir)/$$file \ || exit 1; \ fi; \ done $(MAKE) $(AM_MAKEFLAGS) \ top_distdir="$(top_distdir)" distdir="$(distdir)" \ dist-hook -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 {} {} \; \ || chmod -R a+r $(distdir) dist-gzip: distdir tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz $(am__remove_distdir) dist-bzip2: distdir tardir=$(distdir) && $(am__tar) | bzip2 -9 -c >$(distdir).tar.bz2 $(am__remove_distdir) dist-tarZ: distdir tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z $(am__remove_distdir) dist-shar: distdir shar $(distdir) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).shar.gz $(am__remove_distdir) dist-zip: distdir -rm -f $(distdir).zip zip -rq $(distdir).zip $(distdir) $(am__remove_distdir) dist dist-all: distdir tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz $(am__remove_distdir) # This target untars the dist file and tries a VPATH configuration. Then # it guarantees that the distribution is self-contained by making another # tarfile. distcheck: dist case '$(DIST_ARCHIVES)' in \ *.tar.gz*) \ GZIP=$(GZIP_ENV) gunzip -c $(distdir).tar.gz | $(am__untar) ;;\ *.tar.bz2*) \ bunzip2 -c $(distdir).tar.bz2 | $(am__untar) ;;\ *.tar.Z*) \ uncompress -c $(distdir).tar.Z | $(am__untar) ;;\ *.shar.gz*) \ GZIP=$(GZIP_ENV) gunzip -c $(distdir).shar.gz | unshar ;;\ *.zip*) \ unzip $(distdir).zip ;;\ esac chmod -R a-w $(distdir); chmod a+w $(distdir) mkdir $(distdir)/_build mkdir $(distdir)/_inst chmod a-w $(distdir) dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \ && dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \ && cd $(distdir)/_build \ && ../configure --srcdir=.. --prefix="$$dc_install_base" \ $(DISTCHECK_CONFIGURE_FLAGS) \ && $(MAKE) $(AM_MAKEFLAGS) \ && $(MAKE) $(AM_MAKEFLAGS) dvi \ && $(MAKE) $(AM_MAKEFLAGS) check \ && $(MAKE) $(AM_MAKEFLAGS) install \ && $(MAKE) $(AM_MAKEFLAGS) installcheck \ && $(MAKE) $(AM_MAKEFLAGS) uninstall \ && $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \ distuninstallcheck \ && chmod -R a-w "$$dc_install_base" \ && ({ \ (cd ../.. && umask 077 && mkdir "$$dc_destdir") \ && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \ && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \ && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \ distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \ } || { rm -rf "$$dc_destdir"; exit 1; }) \ && rm -rf "$$dc_destdir" \ && $(MAKE) $(AM_MAKEFLAGS) dist \ && rm -rf $(DIST_ARCHIVES) \ && $(MAKE) $(AM_MAKEFLAGS) distcleancheck $(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;}' distuninstallcheck: @cd $(distuninstallcheck_dir) \ && test `$(distuninstallcheck_listfiles) | wc -l` -le 1 \ || { echo "ERROR: files left after uninstall:" ; \ if test -n "$(DESTDIR)"; then \ echo " (check DESTDIR support)"; \ fi ; \ $(distuninstallcheck_listfiles) ; \ exit 1; } >&2 distcleancheck: distclean @if test '$(srcdir)' = . ; then \ echo "ERROR: distcleancheck can only run from a VPATH build" ; \ exit 1 ; \ fi @test `$(distcleancheck_listfiles) | wc -l` -eq 0 \ || { echo "ERROR: files left in build directory after distclean:" ; \ $(distcleancheck_listfiles) ; \ exit 1; } >&2 check-am: all-am check: check-am all-am: Makefile $(PROGRAMS) $(SCRIPTS) $(DATA) config.h installdirs: for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(ppscriptsdir)" "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(docdir)" "$(DESTDIR)$(exampleconfdir)" "$(DESTDIR)$(webuidir)"; do \ test -z "$$dir" || $(mkdir_p) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ `test -z '$(STRIP)' || \ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-binPROGRAMS clean-generic mostlyclean-am distclean: distclean-am -rm -f $(am__CONFIG_DISTCLEAN_FILES) -rm -rf ./$(DEPDIR) -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-hdr distclean-tags dvi: dvi-am dvi-am: html: html-am info: info-am info-am: install-data-am: install-dist_docDATA install-dist_exampleconfDATA \ install-nobase_dist_ppscriptsSCRIPTS \ install-nobase_dist_webuiDATA @$(NORMAL_INSTALL) $(MAKE) $(AM_MAKEFLAGS) install-data-hook install-exec-am: install-binPROGRAMS install-sbinSCRIPTS @$(NORMAL_INSTALL) $(MAKE) $(AM_MAKEFLAGS) install-exec-hook install-info: install-info-am install-man: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f $(am__CONFIG_DISTCLEAN_FILES) -rm -rf $(top_srcdir)/autom4te.cache -rm -rf ./$(DEPDIR) -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-binPROGRAMS uninstall-dist_docDATA \ uninstall-dist_exampleconfDATA uninstall-info-am \ uninstall-nobase_dist_ppscriptsSCRIPTS \ uninstall-nobase_dist_webuiDATA uninstall-sbinSCRIPTS .PHONY: CTAGS GTAGS all all-am am--refresh check check-am clean \ clean-binPROGRAMS clean-generic ctags dist dist-all dist-bzip2 \ dist-gzip dist-hook dist-shar dist-tarZ dist-zip distcheck \ distclean 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-data-hook install-dist_docDATA \ install-dist_exampleconfDATA install-exec install-exec-am \ install-exec-hook install-info install-info-am install-man \ install-nobase_dist_ppscriptsSCRIPTS \ install-nobase_dist_webuiDATA install-sbinSCRIPTS \ 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-dist_docDATA uninstall-dist_exampleconfDATA \ uninstall-info-am uninstall-nobase_dist_ppscriptsSCRIPTS \ uninstall-nobase_dist_webuiDATA uninstall-sbinSCRIPTS # Note about "sed": # We need to make some changes in installed files. # On Linux "sed" has option "-i" for in-place-edit. Unfortunateley the BSD version of "sed" # has incompatible syntax. To solve the problem we perform in-place-edit in three steps: # 1) copy the original file to original.temp (delete existing original.temp, if any); # 2) sed < original.temp > original # 3) delete original.temp # These steps ensure that the output file has the same permissions as the original file. # Configure installed script install-exec-hook: rm -f "$(DESTDIR)$(sbindir)/nzbgetd.temp" cp "$(DESTDIR)$(sbindir)/nzbgetd" "$(DESTDIR)$(sbindir)/nzbgetd.temp" sed 's?/usr/local/bin?$(bindir)?' < "$(DESTDIR)$(sbindir)/nzbgetd.temp" > "$(DESTDIR)$(sbindir)/nzbgetd" rm "$(DESTDIR)$(sbindir)/nzbgetd.temp" # Prepare example configuration file install-data-hook: rm -f "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" cp "$(DESTDIR)$(exampleconfdir)/nzbget.conf" "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" sed 's:^ConfigTemplate=:ConfigTemplate=$(exampleconfdir)/nzbget.conf:' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf" sed 's:configuration file (typically installed:configuration file (installed:' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" sed 's:/usr/local/share/nzbget/nzbget.conf):$(exampleconfdir)/nzbget.conf):' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf" sed 's:^WebDir=:WebDir=$(webuidir)/webui:' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" sed 's:typically installed to /usr/local/share/nzbget/ppscripts:installed to $(ppscriptsdir)/ppscripts:' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf" rm "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" # Install configuration files into /etc # (only if they do not exist there to prevent override by update) install-conf: if test ! -f "$(DESTDIR)$(sysconfdir)/nzbget.conf" ; then \ $(mkinstalldirs) "$(DESTDIR)$(sysconfdir)" ; \ cp "$(DESTDIR)$(exampleconfdir)/nzbget.conf" "$(DESTDIR)$(sysconfdir)/nzbget.conf" ; \ fi uninstall-conf: rm -f "$(DESTDIR)$(sysconfdir)/nzbget.conf" # Determining subversion revision: # 1) If directory ".svn" exists we take revision from it using program svnversion (part of subversion package) # File is recreated only if revision number was changed. # 2) If directory ".svn" doesn't exists we keep and reuse file "svn_version.cpp", # which was possibly created early. # 3) If neither directory ".svn" nor file "svn_version.cpp" are available # we create new file "svn_version.c" with empty revision number. svn_version.cpp: FORCE @ if test -d ./.svn ; then \ V="$(shell svnversion -n .)"; \ H="$(shell test -f ./svn_version.cpp && head -n 1 svn_version.cpp)"; \ if test "/* $$V */" != "$$H" ; then \ ( \ echo "/* $$V */" ;\ echo "/* This file is automatically regenerated on each build. Do not edit it. */" ;\ echo "const char* svn_version(void)" ;\ echo "{" ;\ echo " const char* SVN_Version = \"$$V\";" ;\ echo " return SVN_Version;" ;\ echo "}" ;\ ) > svn_version.cpp ; \ fi \ elif test -f ./svn_version.cpp ; then \ test "ok, reuse existing file"; \ else \ ( \ echo "/* */" ;\ echo "/* This file is automatically regenerated on each build. Do not edit it. */" ;\ echo "const char* svn_version(void)" ;\ echo "{" ;\ echo " const char* SVN_Version = \"\";" ;\ echo " return SVN_Version;" ;\ echo "}" ;\ ) > svn_version.cpp ; \ fi FORCE: clean-bak: rm *~ # Fix premissions dist-hook: chmod -x $(distdir)/*.cpp $(distdir)/*.h find $(distdir)/webui -type f -print -exec chmod -x {} \; # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: nzbget-12.0+dfsg/MessageBase.h000066400000000000000000000652011226450633000162010ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2005 Bo Cordes Petersen * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 924 $ * $Date: 2013-12-21 22:39:49 +0100 (Sat, 21 Dec 2013) $ * */ #ifndef MESSAGEBASE_H #define MESSAGEBASE_H static const int32_t NZBMESSAGE_SIGNATURE = 0x6E7A621B; // = "nzb-XX" (protocol version) 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 ends with NULL-char. * * NOTE: * NZBGet communication protocol is intended for usage only by NZBGet itself. * The communication works only if server and client has the same version. * The compatibility with previous program versions is not provided. * Third-party programs should use JSON-RPC or XML-RPC to communicate with NZBGet. */ // Possible values for field "m_iType" of struct "SNZBRequestBase": enum eRemoteRequest { eRemoteRequestDownload = 1, eRemoteRequestPauseUnpause, eRemoteRequestList, eRemoteRequestSetDownloadRate, eRemoteRequestDumpDebug, eRemoteRequestEditQueue, eRemoteRequestLog, eRemoteRequestShutdown, eRemoteRequestReload, eRemoteRequestVersion, eRemoteRequestPostQueue, eRemoteRequestWriteLog, eRemoteRequestScan, eRemoteRequestHistory, eRemoteRequestDownloadUrl, eRemoteRequestUrlQueue }; // Possible values for field "m_iAction" of struct "SNZBEditQueueRequest": // File-Actions affect one file, Group-Actions affect all files in group. // Group is a list of files, added to queue from one NZB-File. enum eRemoteEditAction { eRemoteEditActionFileMoveOffset = 1, // move files to m_iOffset relative to the current position in download-queue eRemoteEditActionFileMoveTop, // move files to the top of download-queue eRemoteEditActionFileMoveBottom, // move files to the bottom of download-queue eRemoteEditActionFilePause, // pause files eRemoteEditActionFileResume, // resume (unpause) files eRemoteEditActionFileDelete, // delete files 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) eRemoteEditActionFileSetPriority, // set priority for files eRemoteEditActionFileReorder, // (not supported) eRemoteEditActionFileSplit, // split - create new group from selected files eRemoteEditActionGroupMoveOffset, // move group to m_iOffset relative to the current position in download-queue eRemoteEditActionGroupMoveTop, // move group to the top of download-queue eRemoteEditActionGroupMoveBottom, // move group to the bottom of download-queue eRemoteEditActionGroupPause, // pause group eRemoteEditActionGroupResume, // resume (unpause) group eRemoteEditActionGroupDelete, // delete group eRemoteEditActionGroupDupeDelete, // delete group eRemoteEditActionGroupFinalDelete, // delete group eRemoteEditActionGroupPauseAllPars, // pause only (all) pars (does not affect other files) in group eRemoteEditActionGroupPauseExtraPars, // pause only (almost all) pars in group, except main par-file (does not affect other files) eRemoteEditActionGroupSetPriority, // set priority for groups eRemoteEditActionGroupSetCategory, // set or change category for a group eRemoteEditActionGroupMerge, // merge group eRemoteEditActionGroupSetParameter, // set post-process parameter for group eRemoteEditActionGroupSetName, // set group name (rename group) eRemoteEditActionGroupSetDupeKey, // (reserved) eRemoteEditActionGroupSetDupeScore, // (reserved) eRemoteEditActionGroupSetDupeMode, // (reserved) eRemoteEditActionPostMoveOffset = 51, // move post-job to m_iOffset relative to the current position in post-queue eRemoteEditActionPostMoveTop, // move post-job to the top of post-queue eRemoteEditActionPostMoveBottom, // move post-job to the bottom of post-queue eRemoteEditActionPostDelete, // delete post-job eRemoteEditActionHistoryDelete, // hide history-item eRemoteEditActionHistoryFinalDelete, // delete history-item eRemoteEditActionHistoryReturn, // move history-item back to download queue eRemoteEditActionHistoryProcess, // move history-item back to download queue and start postprocessing eRemoteEditActionHistoryRedownload, // move history-item back to download queue for redownload eRemoteEditActionHistorySetParameter, // set post-process parameter for history-item eRemoteEditActionHistorySetDupeKey, // (reserved) eRemoteEditActionHistorySetDupeScore, // (reserved) eRemoteEditActionHistorySetDupeMode, // (reserved) eRemoteEditActionHistorySetDupeBackup, // (reserved) eRemoteEditActionHistoryMarkBad, // mark history-item as bad (and download other duplicate) eRemoteEditActionHistoryMarkGood // mark history-item as good (and push it into dup-history) }; // Possible values for field "m_iAction" of struct "SNZBPauseUnpauseRequest": enum eRemotePauseUnpauseAction { eRemotePauseUnpauseActionDownload = 1, // pause/unpause download queue eRemotePauseUnpauseActionDownload2, // pause/unpause download queue (second pause-register) eRemotePauseUnpauseActionPostProcess, // pause/unpause post-processor queue eRemotePauseUnpauseActionScan // pause/unpause scan of incoming nzb-directory }; // Possible values for field "m_iMatchMode" of struct "SNZBEditQueueRequest": enum eRemoteMatchMode { eRemoteMatchModeID = 1, // ID eRemoteMatchModeName, // Name eRemoteMatchModeRegEx, // RegEx }; // The basic SNZBRequestBase struct, used in all requests struct SNZBRequestBase { int32_t m_iSignature; // Signature must be NZBMESSAGE_SIGNATURE in integer-value int32_t m_iStructSize; // Size of the entire struct int32_t m_iType; // Message type, see enum in NZBMessageRequest-namespace char m_szUsername[NZBREQUESTPASSWORDSIZE]; // User name char m_szPassword[NZBREQUESTPASSWORDSIZE]; // Password }; // The basic SNZBResposneBase struct, used in all responses struct SNZBResponseBase { int32_t m_iSignature; // Signature must be NZBMESSAGE_SIGNATURE in integer-value int32_t m_iStructSize; // Size of the entire struct }; // A download request struct SNZBDownloadRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct char m_szFilename[NZBREQUESTFILENAMESIZE]; // Name of nzb-file, may contain full path (local path on client) or only filename char m_szCategory[NZBREQUESTFILENAMESIZE]; // Category, can be empty int32_t m_bAddFirst; // 1 - add file to the top of download queue int32_t m_bAddPaused; // 1 - pause added files int32_t m_iPriority; // Priority for files (0 - default) int32_t m_iTrailingDataLength; // Length of nzb-file in bytes //char m_szContent[m_iTrailingDataLength]; // variable sized }; // A download response struct SNZBDownloadResponse { SNZBResponseBase m_MessageBase; // Must be the first in the struct int32_t m_bSuccess; // 0 - command failed, 1 - command executed successfully int32_t m_iTrailingDataLength; // Length of Text-string (m_szText), following to this record //char m_szText[m_iTrailingDataLength]; // variable sized }; // A list and status request struct SNZBListRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct int32_t m_bFileList; // 1 - return file list int32_t m_bServerState; // 1 - return server state int32_t m_iMatchMode; // File/Group match mode, see enum eRemoteMatchMode (only values eRemoteMatchModeID (no filter) and eRemoteMatchModeRegEx are allowed) int32_t m_bMatchGroup; // 0 - match files; 1 - match nzbs (when m_iMatchMode == eRemoteMatchModeRegEx) char m_szPattern[NZBREQUESTFILENAMESIZE]; // RegEx Pattern (when m_iMatchMode == eRemoteMatchModeRegEx) }; // A list response struct SNZBListResponse { SNZBResponseBase m_MessageBase; // Must be the first in the struct int32_t m_iEntrySize; // Size of the SNZBListResponseEntry-struct int32_t m_iRemainingSizeLo; // Remaining size in bytes, Low 32-bits of 64-bit value int32_t m_iRemainingSizeHi; // Remaining size in bytes, High 32-bits of 64-bit value int32_t m_iDownloadRate; // Current download speed, in Bytes pro Second int32_t m_iDownloadLimit; // Current download limit, in Bytes pro Second int32_t m_bDownloadPaused; // 1 - download queue is currently in paused-state int32_t m_bDownload2Paused; // 1 - download queue is currently in paused-state (second pause-register) int32_t m_bDownloadStandBy; // 0 - there are currently downloads running, 1 - no downloads in progress (download queue paused or all download jobs completed) int32_t m_bPostPaused; // 1 - post-processor queue is currently in paused-state int32_t m_bScanPaused; // 1 - scaning of incoming directory is currently in paused-state int32_t m_iThreadCount; // Number of threads running int32_t m_iPostJobCount; // Number of jobs in post-processor queue (including current job) 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_bRegExValid; // 0 - error in RegEx-pattern, 1 - RegEx-pattern is valid (only when Request has eRemoteMatchModeRegEx) int32_t m_iNrTrailingNZBEntries; // Number of List-NZB-entries, following to this structure int32_t m_iNrTrailingPPPEntries; // Number of List-PPP-entries, following to this structure int32_t m_iNrTrailingFileEntries; // Number of List-File-entries, following to this structure int32_t m_iTrailingDataLength; // Length of all List-entries, following to this structure // SNZBListResponseEntry m_NZBEntries[m_iNrTrailingNZBEntries] // variable sized // SNZBListResponseEntry m_PPPEntries[m_iNrTrailingPPPEntries] // variable sized // SNZBListResponseEntry m_FileEntries[m_iNrTrailingFileEntries] // variable sized }; // A list response nzb entry struct SNZBListResponseNZBEntry { int32_t m_iSizeLo; // Size of all files in bytes, Low 32-bits of 64-bit value int32_t m_iSizeHi; // Size of all files in bytes, High 32-bits of 64-bit value int32_t m_bMatch; // 1 - group matches the pattern (only when Request has eRemoteMatchModeRegEx) int32_t m_iFilenameLen; // Length of Filename-string (m_szFilename), following to this record int32_t m_iNameLen; // Length of Name-string (m_szName), following to this record int32_t m_iDestDirLen; // Length of DestDir-string (m_szDestDir), following to this record int32_t m_iCategoryLen; // Length of Category-string (m_szCategory), following to this record int32_t m_iQueuedFilenameLen; // Length of queued file name (m_szQueuedFilename), following to this record //char m_szFilename[m_iFilenameLen]; // variable sized //char m_szName[m_iNameLen]; // variable sized //char m_szDestDir[m_iDestDirLen]; // variable sized //char m_szCategory[m_iCategoryLen]; // variable sized //char m_szQueuedFilename[m_iQueuedFilenameLen]; // variable sized }; // A list response pp-parameter entry struct SNZBListResponsePPPEntry { int32_t m_iNZBIndex; // Index of NZB-Entry in m_NZBEntries-list int32_t m_iNameLen; // Length of Name-string (m_szName), following to this record int32_t m_iValueLen; // Length of Value-string (m_szValue), following to this record //char m_szName[m_iNameLen]; // variable sized //char m_szValue[m_iValueLen]; // variable sized }; // A list response file entry struct SNZBListResponseFileEntry { int32_t m_iID; // Entry-ID int32_t m_iNZBIndex; // Index of NZB-Entry in m_NZBEntries-list 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_iPriority; // Download priority int32_t m_iActiveDownloads; // Number of active downloads for this file int32_t m_bMatch; // 1 - file matches the pattern (only when Request has eRemoteMatchModeRegEx) 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 //char m_szSubject[m_iSubjectLen]; // variable sized //char m_szFilename[m_iFilenameLen]; // variable sized }; // A log request struct SNZBLogRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct int32_t m_iIDFrom; // Only one of these two parameters int32_t m_iLines; // can be set. The another one must be set to "0". }; // A log response struct SNZBLogResponse { SNZBResponseBase m_MessageBase; // Must be the first in the struct int32_t m_iEntrySize; // Size of the SNZBLogResponseEntry-struct int32_t m_iNrTrailingEntries; // Number of Log-entries, following to this structure int32_t m_iTrailingDataLength; // Length of all Log-entries, following to this structure // SNZBLogResponseEntry m_Entries[m_iNrTrailingEntries] // variable sized }; // A log response entry struct SNZBLogResponseEntry { int32_t m_iID; // ID of Log-entry int32_t m_iKind; // see Message::Kind in "Log.h" int32_t m_tTime; // time since the Epoch (00:00:00 UTC, January 1, 1970), measured in seconds. int32_t m_iTextLen; // Length of Text-string (m_szText), following to this record //char m_szText[m_iTextLen]; // variable sized }; // A Pause/Unpause request struct SNZBPauseUnpauseRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct int32_t m_bPause; // 1 - server must be paused, 0 - server must be unpaused int32_t m_iAction; // Action to be executed, see enum eRemotePauseUnpauseAction }; // A Pause/Unpause response struct SNZBPauseUnpauseResponse { SNZBResponseBase m_MessageBase; // Must be the first in the struct int32_t m_bSuccess; // 0 - command failed, 1 - command executed successfully int32_t m_iTrailingDataLength; // Length of Text-string (m_szText), following to this record //char m_szText[m_iTrailingDataLength]; // variable sized }; // Request setting the download rate struct SNZBSetDownloadRateRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct int32_t m_iDownloadRate; // Speed limit, in Bytes pro Second }; // A setting download rate response struct SNZBSetDownloadRateResponse { SNZBResponseBase m_MessageBase; // Must be the first in the struct int32_t m_bSuccess; // 0 - command failed, 1 - command executed successfully int32_t m_iTrailingDataLength; // Length of Text-string (m_szText), following to this record //char m_szText[m_iTrailingDataLength]; // variable sized }; // edit queue request struct SNZBEditQueueRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct int32_t m_iAction; // Action to be executed, see enum eRemoteEditAction 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_iMatchMode; // File/Group match mode, see enum eRemoteMatchMode int32_t m_iNrTrailingIDEntries; // Number of ID-entries, following to this structure int32_t m_iNrTrailingNameEntries; // Number of Name-entries, following to this structure int32_t m_iTrailingNameEntriesLen; // Length of all Name-entries, following to this structure int32_t m_iTextLen; // Length of Text-string (m_szText), following to this record int32_t m_iTrailingDataLength; // Length of Text-string and all ID-entries, following to this structure //char m_szText[m_iTextLen]; // variable sized //int32_t m_iIDs[m_iNrTrailingIDEntries]; // variable sized array of IDs. For File-Actions - ID of file, for Group-Actions - ID of any file belonging to group //char* m_szNames[m_iNrTrailingNameEntries]; // variable sized array of strings. For File-Actions - name of file incl. nzb-name as path, for Group-Actions - name of group }; // An edit queue response struct SNZBEditQueueResponse { SNZBResponseBase m_MessageBase; // Must be the first in the struct int32_t m_bSuccess; // 0 - command failed, 1 - command executed successfully int32_t m_iTrailingDataLength; // Length of Text-string (m_szText), following to this record //char m_szText[m_iTrailingDataLength]; // variable sized }; // Request dumping of debug info struct SNZBDumpDebugRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct }; // Dumping of debug response struct SNZBDumpDebugResponse { SNZBResponseBase m_MessageBase; // Must be the first in the struct int32_t m_bSuccess; // 0 - command failed, 1 - command executed successfully int32_t m_iTrailingDataLength; // Length of Text-string (m_szText), following to this record //char m_szText[m_iTrailingDataLength]; // variable sized }; // Shutdown server request struct SNZBShutdownRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct }; // Shutdown server response struct SNZBShutdownResponse { SNZBResponseBase m_MessageBase; // Must be the first in the struct int32_t m_bSuccess; // 0 - command failed, 1 - command executed successfully int32_t m_iTrailingDataLength; // Length of Text-string (m_szText), following to this record //char m_szText[m_iTrailingDataLength]; // variable sized }; // Reload server request struct SNZBReloadRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct }; // Reload server response struct SNZBReloadResponse { 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 }; // PostQueue request struct SNZBPostQueueRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct }; // A PostQueue response struct SNZBPostQueueResponse { SNZBResponseBase m_MessageBase; // Must be the first in the struct int32_t m_iEntrySize; // Size of the SNZBPostQueueResponseEntry-struct int32_t m_iNrTrailingEntries; // Number of PostQueue-entries, following to this structure int32_t m_iTrailingDataLength; // Length of all PostQueue-entries, following to this structure // SNZBPostQueueResponseEntry m_Entries[m_iNrTrailingEntries] // variable sized }; // A PostQueue response entry struct SNZBPostQueueResponseEntry { int32_t m_iID; // ID of Post-entry int32_t m_iStage; // See PrePostProcessor::EPostJobStage int32_t m_iStageProgress; // Progress of current stage, value in range 0..1000 int32_t m_iFileProgress; // Progress of current file, value in range 0..1000 int32_t m_iTotalTimeSec; // Number of seconds this post-job is beeing processed (after it first changed the state from QUEUED). int32_t m_iStageTimeSec; // Number of seconds the current stage is beeing processed. int32_t m_iNZBFilenameLen; // Length of NZBFileName-string (m_szNZBFilename), following to this record int32_t m_iInfoNameLen; // Length of Filename-string (m_szFilename), following to this record int32_t m_iDestDirLen; // Length of DestDir-string (m_szDestDir), following to this record int32_t m_iProgressLabelLen; // Length of ProgressLabel-string (m_szProgressLabel), following to this record //char m_szNZBFilename[m_iNZBFilenameLen]; // variable sized, may contain full path (local path on client) or only filename //char m_szInfoName[m_iInfoNameLen]; // variable sized //char m_szDestDir[m_iDestDirLen]; // variable sized //char m_szProgressLabel[m_iProgressLabelLen]; // variable sized }; // Write log request struct SNZBWriteLogRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct int32_t m_iKind; // see Message::Kind in "Log.h" int32_t m_iTrailingDataLength; // Length of nzb-file in bytes //char m_szText[m_iTrailingDataLength]; // variable sized }; // Write log response struct SNZBWriteLogResponse { 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 }; // Scan nzb directory request struct SNZBScanRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct int32_t m_bSyncMode; // 0 - asynchronous Scan (the command returns immediately), 1 - synchronous Scan (the command returns when the scan is completed) }; // Scan nzb directory response struct SNZBScanResponse { 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 history request struct SNZBHistoryRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct }; // history response struct SNZBHistoryResponse { SNZBResponseBase m_MessageBase; // Must be the first in the struct int32_t m_iEntrySize; // Size of the SNZBHistoryResponseEntry-struct int32_t m_iNrTrailingEntries; // Number of History-entries, following to this structure int32_t m_iTrailingDataLength; // Length of all History-entries, following to this structure // SNZBHistoryResponseEntry m_Entries[m_iNrTrailingEntries] // variable sized }; // history entry struct SNZBHistoryResponseEntry { int32_t m_iID; // History-ID int32_t m_iKind; // Kind of Item: 1 - Collection (NZB), 2 - URL int32_t m_tTime; // When the item was added to history. time since the Epoch (00:00:00 UTC, January 1, 1970), measured in seconds. int32_t m_iNicenameLen; // Length of Nicename-string (m_szNicename), following to this record // for Collection items (m_iKind = 1) int32_t m_iSizeLo; // Size of all files in bytes, Low 32-bits of 64-bit value int32_t m_iSizeHi; // Size of all files in bytes, High 32-bits of 64-bit value int32_t m_iFileCount; // Initial number of files included in NZB-file int32_t m_iParStatus; // See NZBInfo::EParStatus int32_t m_iScriptStatus; // See NZBInfo::EScriptStatus // for URL items (m_iKind = 2) int32_t m_iUrlStatus; // See UrlInfo::EStatus // trailing data //char m_szNicename[m_iNicenameLen]; // variable sized }; // download url request struct SNZBDownloadUrlRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct char m_szURL[NZBREQUESTFILENAMESIZE]; // url to nzb-file char m_szNZBFilename[NZBREQUESTFILENAMESIZE];// Name of nzb-file. Can be empty, then the filename is read from URL download response char m_szCategory[NZBREQUESTFILENAMESIZE]; // Category, can be empty int32_t m_bAddFirst; // 1 - add url to the top of download queue int32_t m_bAddPaused; // 1 - pause added files int32_t m_iPriority; // Priority for files (0 - default) }; // download url response struct SNZBDownloadUrlResponse { 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 }; // UrlQueue request struct SNZBUrlQueueRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct }; // UrlQueue response struct SNZBUrlQueueResponse { SNZBResponseBase m_MessageBase; // Must be the first in the struct int32_t m_iEntrySize; // Size of the SNZBUrlQueueResponseEntry-struct int32_t m_iNrTrailingEntries; // Number of UrlQueue-entries, following to this structure int32_t m_iTrailingDataLength; // Length of all UrlQueue-entries, following to this structure // SNZBUrlQueueResponseEntry m_Entries[m_iNrTrailingEntries] // variable sized }; // UrlQueue response entry struct SNZBUrlQueueResponseEntry { int32_t m_iID; // ID of Url-entry int32_t m_iURLLen; // Length of URL-string (m_szURL), following to this record int32_t m_iNZBFilenameLen; // Length of NZBFilename-string (m_szNZBFilename), following to this record //char m_szURL[m_iURLLen]; // variable sized //char m_szNZBFilename[m_iNZBFilenameLen]; // variable sized }; #endif nzbget-12.0+dfsg/NCursesFrontend.cpp000066400000000000000000001140461226450633000174410ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2011 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 914 $ * $Date: 2013-11-28 22:03:01 +0100 (Thu, 28 Nov 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #ifndef DISABLE_CURSES #ifdef HAVE_NCURSES_H #include #endif #ifdef HAVE_NCURSES_NCURSES_H #include #endif #include #include #ifndef WIN32 #include #endif #include "nzbget.h" #include "NCursesFrontend.h" #include "Options.h" #include "Util.h" #ifdef HAVE_CURSES_H // curses.h header must be included last to avoid problems on Solaris // (and possibly other systems, that uses curses.h (not ncurses.h) #include // "#undef erase" is neccessary on Solaris #undef erase #endif #ifndef WIN32 // curses.h on Solaris declares "clear()" via DEFINE. That causes problems, because // it also affects calls to deque's method "clear()", producing compiler errors. // We use function "curses_clear()" to call macro "clear" of curses, then // undefine macro "clear". void curses_clear() { clear(); } #undef clear #endif extern Options* g_pOptions; extern void ExitProc(); static const int NCURSES_COLORPAIR_TEXT = 1; static const int NCURSES_COLORPAIR_INFO = 2; static const int NCURSES_COLORPAIR_WARNING = 3; static const int NCURSES_COLORPAIR_ERROR = 4; static const int NCURSES_COLORPAIR_DEBUG = 5; static const int NCURSES_COLORPAIR_DETAIL = 6; static const int NCURSES_COLORPAIR_STATUS = 7; static const int NCURSES_COLORPAIR_KEYBAR = 8; static const int NCURSES_COLORPAIR_INFOLINE = 9; static const int NCURSES_COLORPAIR_TEXTHIGHL = 10; static const int NCURSES_COLORPAIR_CURSOR = 11; static const int NCURSES_COLORPAIR_HINT = 12; static const int MAX_SCREEN_WIDTH = 512; #ifdef WIN32 static const int COLOR_BLACK = 0; static const int COLOR_BLUE = FOREGROUND_BLUE; static const int COLOR_RED = FOREGROUND_RED; static const int COLOR_GREEN = FOREGROUND_GREEN; static const int COLOR_WHITE = FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_GREEN; static const int COLOR_MAGENTA = FOREGROUND_RED | FOREGROUND_BLUE; static const int COLOR_CYAN = FOREGROUND_BLUE | FOREGROUND_GREEN; static const int COLOR_YELLOW = FOREGROUND_RED | FOREGROUND_GREEN; static const int READKEY_EMPTY = 0; #define KEY_DOWN VK_DOWN #define KEY_UP VK_UP #define KEY_PPAGE VK_PRIOR #define KEY_NPAGE VK_NEXT #define KEY_END VK_END #define KEY_HOME VK_HOME #define KEY_BACKSPACE VK_BACK #else static const int READKEY_EMPTY = ERR; #endif NCursesFrontend::NCursesFrontend() { m_iScreenHeight = 0; m_iScreenWidth = 0; m_iInputNumberIndex = 0; m_eInputMode = eNormal; m_bSummary = true; m_bFileList = true; m_iNeededLogEntries = 0; m_iQueueWinTop = 0; m_iQueueWinHeight = 0; m_iQueueWinClientHeight = 0; m_iMessagesWinTop = 0; m_iMessagesWinHeight = 0; m_iMessagesWinClientHeight = 0; m_iSelectedQueueEntry = 0; m_iQueueScrollOffset = 0; m_bShowNZBname = g_pOptions->GetCursesNZBName(); m_bShowTimestamp = g_pOptions->GetCursesTime(); m_bGroupFiles = g_pOptions->GetCursesGroup(); m_QueueWindowPercentage = 0.5f; m_iDataUpdatePos = 0; m_bUpdateNextTime = false; m_iLastEditEntry = -1; m_bLastPausePars = false; m_szHint = NULL; // Setup curses #ifdef WIN32 HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); m_pScreenBuffer = NULL; m_pOldScreenBuffer = NULL; m_ColorAttr.clear(); CONSOLE_CURSOR_INFO ConsoleCursorInfo; GetConsoleCursorInfo(hConsole, &ConsoleCursorInfo); ConsoleCursorInfo.bVisible = false; SetConsoleCursorInfo(hConsole, &ConsoleCursorInfo); if (IsRemoteMode()) { SetConsoleTitle("NZBGet - remote mode"); } else { SetConsoleTitle("NZBGet"); } m_bUseColor = true; #else m_pWindow = initscr(); if (m_pWindow == NULL) { printf("ERROR: m_pWindow == NULL\n"); exit(-1); } keypad(stdscr, true); nodelay((WINDOW*)m_pWindow, true); noecho(); curs_set(0); m_bUseColor = has_colors(); #endif if (m_bUseColor) { #ifndef WIN32 start_color(); #endif init_pair(0, COLOR_WHITE, COLOR_BLUE); init_pair(NCURSES_COLORPAIR_TEXT, COLOR_WHITE, COLOR_BLACK); init_pair(NCURSES_COLORPAIR_INFO, COLOR_GREEN, COLOR_BLACK); init_pair(NCURSES_COLORPAIR_WARNING, COLOR_MAGENTA, COLOR_BLACK); init_pair(NCURSES_COLORPAIR_ERROR, COLOR_RED, COLOR_BLACK); init_pair(NCURSES_COLORPAIR_DEBUG, COLOR_WHITE, COLOR_BLACK); init_pair(NCURSES_COLORPAIR_DETAIL, COLOR_GREEN, COLOR_BLACK); init_pair(NCURSES_COLORPAIR_STATUS, COLOR_BLUE, COLOR_WHITE); init_pair(NCURSES_COLORPAIR_KEYBAR, COLOR_WHITE, COLOR_BLUE); init_pair(NCURSES_COLORPAIR_INFOLINE, COLOR_WHITE, COLOR_BLUE); init_pair(NCURSES_COLORPAIR_TEXTHIGHL, COLOR_BLACK, COLOR_CYAN); init_pair(NCURSES_COLORPAIR_CURSOR, COLOR_BLACK, COLOR_YELLOW); init_pair(NCURSES_COLORPAIR_HINT, COLOR_WHITE, COLOR_RED); } } NCursesFrontend::~NCursesFrontend() { #ifdef WIN32 free(m_pScreenBuffer); free(m_pOldScreenBuffer); m_ColorAttr.clear(); HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_CURSOR_INFO ConsoleCursorInfo; GetConsoleCursorInfo(hConsole, &ConsoleCursorInfo); ConsoleCursorInfo.bVisible = true; SetConsoleCursorInfo(hConsole, &ConsoleCursorInfo); #else keypad(stdscr, false); echo(); curs_set(1); endwin(); #endif printf("\n"); SetHint(NULL); } void NCursesFrontend::Run() { debug("Entering NCursesFrontend-loop"); m_iDataUpdatePos = 0; while (!IsStopped()) { // 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 bool updateNow = false; int iKey = ReadConsoleKey(); if (iKey != READKEY_EMPTY) { // Update now and next if a key is pressed. updateNow = true; m_bUpdateNextTime = true; } else if (m_bUpdateNextTime) { // Update due to key being pressed during previous call. updateNow = true; m_bUpdateNextTime = false; } else if (m_iDataUpdatePos <= 0) { updateNow = true; m_bUpdateNextTime = false; } if (updateNow) { Update(iKey); } if (m_iDataUpdatePos <= 0) { m_iDataUpdatePos = m_iUpdateInterval; } usleep(10 * 1000); m_iDataUpdatePos -= 10; } FreeData(); ClearGroupQueue(); debug("Exiting NCursesFrontend-loop"); } void NCursesFrontend::NeedUpdateData() { m_iDataUpdatePos = 10; m_bUpdateNextTime = true; } void NCursesFrontend::Update(int iKey) { // Figure out how big the screen is CalcWindowSizes(); if (m_iDataUpdatePos <= 0) { FreeData(); ClearGroupQueue(); m_iNeededLogEntries = m_iMessagesWinClientHeight; if (!PrepareData()) { return; } PrepareGroupQueue(); // recalculate frame sizes CalcWindowSizes(); } if (m_eInputMode == eEditQueue) { int iQueueSize = CalcQueueSize(); if (iQueueSize == 0) { m_iSelectedQueueEntry = 0; m_eInputMode = eNormal; } } //------------------------------------------ // Print Current NZBInfoList //------------------------------------------ if (m_iQueueWinHeight > 0) { PrintQueue(); } //------------------------------------------ // Print Messages //------------------------------------------ if (m_iMessagesWinHeight > 0) { PrintMessages(); } PrintStatus(); PrintKeyInputBar(); UpdateInput(iKey); RefreshScreen(); } void NCursesFrontend::CalcWindowSizes() { int iNrRows, iNrColumns; #ifdef WIN32 HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFO BufInfo; GetConsoleScreenBufferInfo(hConsole, &BufInfo); iNrRows = BufInfo.srWindow.Bottom - BufInfo.srWindow.Top + 1; iNrColumns = BufInfo.srWindow.Right - BufInfo.srWindow.Left + 1; #else getmaxyx(stdscr, iNrRows, iNrColumns); #endif if (iNrRows != m_iScreenHeight || iNrColumns != m_iScreenWidth) { #ifdef WIN32 m_iScreenBufferSize = iNrRows * iNrColumns * sizeof(CHAR_INFO); m_pScreenBuffer = (CHAR_INFO*)realloc(m_pScreenBuffer, m_iScreenBufferSize); memset(m_pScreenBuffer, 0, m_iScreenBufferSize); m_pOldScreenBuffer = (CHAR_INFO*)realloc(m_pOldScreenBuffer, m_iScreenBufferSize); memset(m_pOldScreenBuffer, 0, m_iScreenBufferSize); #else curses_clear(); #endif m_iScreenHeight = iNrRows; m_iScreenWidth = iNrColumns; } int iQueueSize = CalcQueueSize(); m_iQueueWinTop = 0; m_iQueueWinHeight = (int)((float) (m_iScreenHeight - 2) * m_QueueWindowPercentage); if (m_iQueueWinHeight - 1 > iQueueSize) { m_iQueueWinHeight = iQueueSize > 0 ? iQueueSize + 1 : 1 + 1; } m_iQueueWinClientHeight = m_iQueueWinHeight - 1; if (m_iQueueWinClientHeight < 0) { m_iQueueWinClientHeight = 0; } m_iMessagesWinTop = m_iQueueWinTop + m_iQueueWinHeight; m_iMessagesWinHeight = m_iScreenHeight - m_iQueueWinHeight - 2; m_iMessagesWinClientHeight = m_iMessagesWinHeight - 1; if (m_iMessagesWinClientHeight < 0) { m_iMessagesWinClientHeight = 0; } } int NCursesFrontend::CalcQueueSize() { if (m_bGroupFiles) { return m_groupQueue.size(); } else { DownloadQueue* pDownloadQueue = LockQueue(); int iQueueSize = pDownloadQueue->GetFileQueue()->size(); UnlockQueue(); return iQueueSize; } } void NCursesFrontend::PlotLine(const char * szString, int iRow, int iPos, int iColorPair) { char szBuffer[MAX_SCREEN_WIDTH]; snprintf(szBuffer, sizeof(szBuffer), "%-*s", m_iScreenWidth, szString); szBuffer[MAX_SCREEN_WIDTH - 1] = '\0'; int iLen = strlen(szBuffer); if (iLen > m_iScreenWidth - iPos && m_iScreenWidth - iPos < MAX_SCREEN_WIDTH) { szBuffer[m_iScreenWidth - iPos] = '\0'; } PlotText(szBuffer, iRow, iPos, iColorPair, false); } void NCursesFrontend::PlotText(const char * szString, int iRow, int iPos, int iColorPair, bool bBlink) { #ifdef WIN32 int iBufPos = iRow * m_iScreenWidth + iPos; int len = strlen(szString); for (int i = 0; i < len; 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 if( m_bUseColor ) { attron(COLOR_PAIR(iColorPair)); if (bBlink) { attron(A_BLINK); } } mvaddstr(iRow, iPos, (char*)szString); if( m_bUseColor ) { attroff(COLOR_PAIR(iColorPair)); if (bBlink) { attroff(A_BLINK); } } #endif } void NCursesFrontend::RefreshScreen() { #ifdef WIN32 bool bBufChanged = memcmp(m_pScreenBuffer, m_pOldScreenBuffer, m_iScreenBufferSize); if (bBufChanged) { COORD BufSize; BufSize.X = m_iScreenWidth; BufSize.Y = m_iScreenHeight; COORD BufCoord; BufCoord.X = 0; BufCoord.Y = 0; HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFO BufInfo; GetConsoleScreenBufferInfo(hConsole, &BufInfo); WriteConsoleOutput(hConsole, m_pScreenBuffer, BufSize, BufCoord, &BufInfo.srWindow); BufInfo.dwCursorPosition.X = BufInfo.srWindow.Right; BufInfo.dwCursorPosition.Y = BufInfo.srWindow.Bottom; SetConsoleCursorPosition(hConsole, BufInfo.dwCursorPosition); memcpy(m_pOldScreenBuffer, m_pScreenBuffer, m_iScreenBufferSize); } #else // Cursor placement wmove((WINDOW*)m_pWindow, m_iScreenHeight, m_iScreenWidth); // NCurses refresh refresh(); #endif } #ifdef WIN32 void NCursesFrontend::init_pair(int iColorNumber, WORD wForeColor, WORD wBackColor) { m_ColorAttr.resize(iColorNumber + 1); m_ColorAttr[iColorNumber] = wForeColor | (wBackColor << 4); } #endif void NCursesFrontend::PrintMessages() { int iLineNr = m_iMessagesWinTop; char szBuffer[MAX_SCREEN_WIDTH]; snprintf(szBuffer, sizeof(szBuffer), "%s Messages", m_bUseColor ? "" : "*** "); szBuffer[MAX_SCREEN_WIDTH - 1] = '\0'; PlotLine(szBuffer, iLineNr++, 0, NCURSES_COLORPAIR_INFOLINE); int iLine = iLineNr + m_iMessagesWinClientHeight - 1; int iLinesToPrint = m_iMessagesWinClientHeight; Log::Messages* pMessages = LockMessages(); // print messages from bottom for (int i = (int)pMessages->size() - 1; i >= 0 && iLinesToPrint > 0; i--) { int iPrintedLines = PrintMessage((*pMessages)[i], iLine, iLinesToPrint); iLine -= iPrintedLines; iLinesToPrint -= iPrintedLines; } if (iLinesToPrint > 0) { // too few messages, print them again from top iLine = iLineNr + m_iMessagesWinClientHeight - 1; while (iLinesToPrint-- > 0) { PlotLine("", iLine--, 0, NCURSES_COLORPAIR_TEXT); } int iLinesToPrint2 = m_iMessagesWinClientHeight; for (int i = (int)pMessages->size() - 1; i >= 0 && iLinesToPrint2 > 0; i--) { int iPrintedLines = PrintMessage((*pMessages)[i], iLine, iLinesToPrint2); iLine -= iPrintedLines; iLinesToPrint2 -= iPrintedLines; } } UnlockMessages(); } int NCursesFrontend::PrintMessage(Message* Msg, int iRow, int iMaxLines) { const char* szMessageType[] = { "INFO ", "WARNING ", "ERROR ", "DEBUG ", "DETAIL "}; const int iMessageTypeColor[] = { NCURSES_COLORPAIR_INFO, NCURSES_COLORPAIR_WARNING, NCURSES_COLORPAIR_ERROR, NCURSES_COLORPAIR_DEBUG, NCURSES_COLORPAIR_DETAIL }; char* szText = (char*)Msg->GetText(); if (m_bShowTimestamp) { int iLen = strlen(szText) + 50; szText = (char*)malloc(iLen); time_t rawtime = Msg->GetTime(); rawtime += g_pOptions->GetTimeCorrection(); 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 some special characters with spaces for (char* p = szText; *p; p++) { if (*p == '\n' || *p == '\r' || *p == '\b') { *p = ' '; } } int iLen = strlen(szText); int iWinWidth = m_iScreenWidth - 8; int iMsgLines = iLen / iWinWidth; if (iLen % iWinWidth > 0) { iMsgLines++; } int iLines = 0; for (int i = iMsgLines - 1; i >= 0 && iLines < iMaxLines; i--) { int iR = iRow - iMsgLines + i + 1; PlotLine(szText + iWinWidth * i, iR, 8, NCURSES_COLORPAIR_TEXT); if (i == 0) { PlotText(szMessageType[Msg->GetKind()], iR, 0, iMessageTypeColor[Msg->GetKind()], false); } else { PlotText(" ", iR, 0, iMessageTypeColor[Msg->GetKind()], false); } iLines++; } free(szText); return iLines; } void NCursesFrontend::PrintStatus() { char tmp[MAX_SCREEN_WIDTH]; int iStatusRow = m_iScreenHeight - 2; char timeString[100]; timeString[0] = '\0'; int iCurrentDownloadSpeed = m_bStandBy ? 0 : m_iCurrentDownloadSpeed; if (iCurrentDownloadSpeed > 0 && !(m_bPauseDownload || m_bPauseDownload2)) { long long remain_sec = (long long)(m_lRemainingSize / iCurrentDownloadSpeed); int h = (int)(remain_sec / 3600); int m = (int)((remain_sec % 3600) / 60); int s = (int)(remain_sec % 60); sprintf(timeString, " (~ %.2d:%.2d:%.2d)", h, m, s); } char szDownloadLimit[128]; if (m_iDownloadLimit > 0) { sprintf(szDownloadLimit, ", Limit %.0f KB/s", (float)m_iDownloadLimit / 1024.0); } else { szDownloadLimit[0] = 0; } char szPostStatus[128]; if (m_iPostJobCount > 0) { sprintf(szPostStatus, ", %i post-job%s", m_iPostJobCount, m_iPostJobCount > 1 ? "s" : ""); } else { szPostStatus[0] = 0; } float fAverageSpeed = (float)(Util::Int64ToFloat(m_iDnTimeSec > 0 ? m_iAllBytes / m_iDnTimeSec : 0) / 1024.0); snprintf(tmp, MAX_SCREEN_WIDTH, " %d threads, %.*f KB/s, %.2f MB remaining%s%s%s%s%s, Avg. %.*f KB/s", m_iThreadCount, (iCurrentDownloadSpeed >= 10*1024 ? 0 : 1), (float)iCurrentDownloadSpeed / 1024.0, (float)(Util::Int64ToFloat(m_lRemainingSize) / 1024.0 / 1024.0), timeString, szPostStatus, m_bPauseDownload || m_bPauseDownload2 ? (m_bStandBy ? ", Paused" : ", Pausing") : "", m_bPauseDownload || m_bPauseDownload2 ? (m_bPauseDownload && m_bPauseDownload2 ? " (+2)" : m_bPauseDownload2 ? " (2)" : "") : "", szDownloadLimit, (fAverageSpeed >= 10 ? 0 : 1), fAverageSpeed); tmp[MAX_SCREEN_WIDTH - 1] = '\0'; PlotLine(tmp, iStatusRow, 0, NCURSES_COLORPAIR_STATUS); } void NCursesFrontend::PrintKeyInputBar() { int iQueueSize = CalcQueueSize(); int iInputBarRow = m_iScreenHeight - 1; if (m_szHint) { time_t tTime = time(NULL); if (tTime - m_tStartHint < 5) { PlotLine(m_szHint, iInputBarRow, 0, NCURSES_COLORPAIR_HINT); return; } else { SetHint(NULL); } } switch (m_eInputMode) { case eNormal: 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: { const 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); break; } case eDownloadRate: char szString[128]; snprintf(szString, 128, "Download rate: %i", m_iInputValue); szString[128-1] = '\0'; PlotLine(szString, iInputBarRow, 0, NCURSES_COLORPAIR_KEYBAR); // Print the cursor PlotText(" ", iInputBarRow, 15 + m_iInputNumberIndex, NCURSES_COLORPAIR_CURSOR, true); break; } } void NCursesFrontend::SetHint(const char* szHint) { free(m_szHint); m_szHint = NULL; if (szHint) { m_szHint = strdup(szHint); m_tStartHint = time(NULL); } } void NCursesFrontend::PrintQueue() { if (m_bGroupFiles) { PrintGroupQueue(); } else { PrintFileQueue(); } } void NCursesFrontend::PrintFileQueue() { int iLineNr = m_iQueueWinTop; DownloadQueue* pDownloadQueue = LockQueue(); if (pDownloadQueue->GetFileQueue()->empty()) { char szBuffer[MAX_SCREEN_WIDTH]; snprintf(szBuffer, sizeof(szBuffer), "%s Files for downloading", m_bUseColor ? "" : "*** "); szBuffer[MAX_SCREEN_WIDTH - 1] = '\0'; PrintTopHeader(szBuffer, iLineNr++, true); PlotLine("Ready to receive nzb-job", iLineNr++, 0, NCURSES_COLORPAIR_TEXT); } else { iLineNr++; long long lRemaining = 0; long long lPaused = 0; int iPausedFiles = 0; int i = 0; for (FileQueue::iterator it = pDownloadQueue->GetFileQueue()->begin(); it != pDownloadQueue->GetFileQueue()->end(); it++, i++) { FileInfo* pFileInfo = *it; if (i >= m_iQueueScrollOffset && i < m_iQueueScrollOffset + m_iQueueWinHeight -1) { PrintFilename(pFileInfo, iLineNr++, i == m_iSelectedQueueEntry); } if (pFileInfo->GetPaused()) { iPausedFiles++; lPaused += pFileInfo->GetRemainingSize(); } lRemaining += pFileInfo->GetRemainingSize(); } char szRemaining[20]; Util::FormatFileSize(szRemaining, sizeof(szRemaining), lRemaining); char szUnpaused[20]; Util::FormatFileSize(szUnpaused, sizeof(szUnpaused), lRemaining - lPaused); char szBuffer[MAX_SCREEN_WIDTH]; snprintf(szBuffer, sizeof(szBuffer), " %sFiles for downloading - %i / %i files in queue - %s / %s", m_bUseColor ? "" : "*** ", (int)pDownloadQueue->GetFileQueue()->size(), (int)pDownloadQueue->GetFileQueue()->size() - iPausedFiles, szRemaining, szUnpaused); szBuffer[MAX_SCREEN_WIDTH - 1] = '\0'; PrintTopHeader(szBuffer, m_iQueueWinTop, true); } UnlockQueue(); } void NCursesFrontend::PrintFilename(FileInfo * pFileInfo, 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; } const char* szDownloading = ""; if (pFileInfo->GetActiveDownloads() > 0) { szDownloading = " *"; } char szPriority[100]; szPriority[0] = '\0'; if (pFileInfo->GetPriority() != 0) { sprintf(szPriority, " [%+i]", pFileInfo->GetPriority()); } char szCompleted[20]; szCompleted[0] = '\0'; if (pFileInfo->GetRemainingSize() < pFileInfo->GetSize()) { sprintf(szCompleted, ", %i%%", (int)(100 - Util::Int64ToFloat(pFileInfo->GetRemainingSize()) * 100.0 / Util::Int64ToFloat(pFileInfo->GetSize()))); } char szNZBNiceName[1024]; if (m_bShowNZBname) { strncpy(szNZBNiceName, pFileInfo->GetNZBInfo()->GetName(), 1023); int len = strlen(szNZBNiceName); szNZBNiceName[len] = PATH_SEPARATOR; szNZBNiceName[len + 1] = '\0'; } else { szNZBNiceName[0] = '\0'; } char szBuffer[MAX_SCREEN_WIDTH]; snprintf(szBuffer, MAX_SCREEN_WIDTH, "%s%i%s%s%s %s%s (%.2f MB%s)%s", Brace1, pFileInfo->GetID(), Brace2, szPriority, szDownloading, szNZBNiceName, pFileInfo->GetFilename(), (float)(Util::Int64ToFloat(pFileInfo->GetSize()) / 1024.0 / 1024.0), szCompleted, pFileInfo->GetPaused() ? " (paused)" : ""); szBuffer[MAX_SCREEN_WIDTH - 1] = '\0'; PlotLine(szBuffer, iRow, 0, color); } 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; LockQueue(); 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++; ResetColWidths(); int iCalcLineNr = iLineNr; 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, iCalcLineNr++, false, true); } } long long lRemaining = 0; long long lPaused = 0; 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, false); } lRemaining += pGroupInfo->GetRemainingSize(); lPaused += pGroupInfo->GetPausedSize(); } char szRemaining[20]; Util::FormatFileSize(szRemaining, sizeof(szRemaining), lRemaining); char szUnpaused[20]; Util::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 ? "" : "*** ", (int)pGroupQueue->size(), szRemaining, szUnpaused); szBuffer[MAX_SCREEN_WIDTH - 1] = '\0'; PrintTopHeader(szBuffer, m_iQueueWinTop, false); } UnlockQueue(); } void NCursesFrontend::ResetColWidths() { m_iColWidthFiles = 0; m_iColWidthTotal = 0; m_iColWidthLeft = 0; } void NCursesFrontend::PrintGroupname(GroupInfo * pGroupInfo, int iRow, bool bSelected, bool bCalcColWidth) { int color = NCURSES_COLORPAIR_TEXT; char chBrace1 = '['; char chBrace2 = ']'; if (m_eInputMode == eEditQueue && bSelected) { color = NCURSES_COLORPAIR_TEXTHIGHL; if (!m_bUseColor) { chBrace1 = '<'; chBrace2 = '>'; } } const char* szDownloading = ""; if (pGroupInfo->GetActiveDownloads() > 0) { szDownloading = " *"; } long long lUnpausedRemainingSize = pGroupInfo->GetRemainingSize() - pGroupInfo->GetPausedSize(); char szRemaining[20]; Util::FormatFileSize(szRemaining, sizeof(szRemaining), lUnpausedRemainingSize); char szPriority[100]; szPriority[0] = '\0'; if (pGroupInfo->GetMinPriority() != 0 || pGroupInfo->GetMaxPriority() != 0) { if (pGroupInfo->GetMinPriority() == pGroupInfo->GetMaxPriority()) { sprintf(szPriority, " [%+i]", pGroupInfo->GetMinPriority()); } else { sprintf(szPriority, " [%+i..%+i]", pGroupInfo->GetMinPriority(), pGroupInfo->GetMaxPriority()); } } char szBuffer[MAX_SCREEN_WIDTH]; // Format: // [id - id] Name Left-Files/Paused Total Left Time // [1-2] Nzb-name 999/999 999.99 MB 999.99 MB 00:00:00 int iNameLen = 0; if (bCalcColWidth) { iNameLen = m_iScreenWidth - 1 - 9 - 11 - 11 - 9; } else { iNameLen = m_iScreenWidth - 1 - m_iColWidthFiles - 2 - m_iColWidthTotal - 2 - m_iColWidthLeft - 2 - 9; } bool bPrintFormatted = iNameLen > 20; if (bPrintFormatted) { char szFiles[20]; snprintf(szFiles, 20, "%i/%i", pGroupInfo->GetRemainingFileCount(), pGroupInfo->GetPausedFileCount()); szFiles[20-1] = '\0'; char szTotal[20]; Util::FormatFileSize(szTotal, sizeof(szTotal), pGroupInfo->GetNZBInfo()->GetSize()); char szNameWithIds[1024]; snprintf(szNameWithIds, 1024, "%c%i-%i%c%s%s %s", chBrace1, pGroupInfo->GetFirstID(), pGroupInfo->GetLastID(), chBrace2, szPriority, szDownloading, pGroupInfo->GetNZBInfo()->GetName()); szNameWithIds[iNameLen] = '\0'; char szTime[100]; szTime[0] = '\0'; int iCurrentDownloadSpeed = m_bStandBy ? 0 : m_iCurrentDownloadSpeed; if (pGroupInfo->GetPausedSize() > 0 && lUnpausedRemainingSize == 0) { snprintf(szTime, 100, "[paused]"); Util::FormatFileSize(szRemaining, sizeof(szRemaining), pGroupInfo->GetRemainingSize()); } else if (iCurrentDownloadSpeed > 0 && !(m_bPauseDownload || m_bPauseDownload2)) { long long remain_sec = (long long)(lUnpausedRemainingSize / iCurrentDownloadSpeed); int h = (int)(remain_sec / 3600); int m = (int)((remain_sec % 3600) / 60); int s = (int)(remain_sec % 60); if (h < 100) { snprintf(szTime, 100, "%.2d:%.2d:%.2d", h, m, s); } else { snprintf(szTime, 100, "99:99:99"); } } if (bCalcColWidth) { int iColWidthFiles = strlen(szFiles); m_iColWidthFiles = iColWidthFiles > m_iColWidthFiles ? iColWidthFiles : m_iColWidthFiles; int iColWidthTotal = strlen(szTotal); m_iColWidthTotal = iColWidthTotal > m_iColWidthTotal ? iColWidthTotal : m_iColWidthTotal; int iColWidthLeft = strlen(szRemaining); m_iColWidthLeft = iColWidthLeft > m_iColWidthLeft ? iColWidthLeft : m_iColWidthLeft; } else { snprintf(szBuffer, MAX_SCREEN_WIDTH, "%-*s %*s %*s %*s %8s", iNameLen, szNameWithIds, m_iColWidthFiles, szFiles, m_iColWidthTotal, szTotal, m_iColWidthLeft, szRemaining, szTime); } } else { snprintf(szBuffer, MAX_SCREEN_WIDTH, "%c%i-%i%c%s %s", chBrace1, pGroupInfo->GetFirstID(), pGroupInfo->GetLastID(), chBrace2, szDownloading, pGroupInfo->GetNZBInfo()->GetName()); } szBuffer[MAX_SCREEN_WIDTH - 1] = '\0'; if (!bCalcColWidth) { PlotLine(szBuffer, iRow, 0, color); } } void NCursesFrontend::PrepareGroupQueue() { m_groupQueue.clear(); DownloadQueue* pDownloadQueue = LockQueue(); pDownloadQueue->BuildGroups(&m_groupQueue); UnlockQueue(); } void NCursesFrontend::ClearGroupQueue() { 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->GetLastID(); if (eAction == QueueEditor::eaFilePause) { if (pGroupInfo->GetRemainingSize() == pGroupInfo->GetPausedSize()) { eAction = QueueEditor::eaFileResume; } else if (pGroupInfo->GetPausedSize() == 0 && (pGroupInfo->GetRemainingParCount() > 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->GetFileQueue()->size()) { FileInfo* pFileInfo = pDownloadQueue->GetFileQueue()->at(m_iSelectedQueueEntry); ID = pFileInfo->GetID(); if (eAction == QueueEditor::eaFilePause) { eAction = !pFileInfo->GetPaused() ? QueueEditor::eaFilePause : QueueEditor::eaFileResume; } } UnlockQueue(); } m_iLastEditEntry = m_iSelectedQueueEntry; NeedUpdateData(); if (ID != 0) { return ServerEditQueue(eAction, iOffset, ID); } else { return false; } } void NCursesFrontend::SetCurrentQueueEntry(int iEntry) { int iQueueSize = CalcQueueSize(); if (iEntry < 0) { iEntry = 0; } else if (iEntry > iQueueSize - 1) { iEntry = iQueueSize - 1; } if (iEntry > m_iQueueScrollOffset + m_iQueueWinClientHeight || iEntry < m_iQueueScrollOffset - m_iQueueWinClientHeight) { m_iQueueScrollOffset = iEntry - m_iQueueWinClientHeight / 2; } else if (iEntry < m_iQueueScrollOffset) { m_iQueueScrollOffset -= m_iQueueWinClientHeight; } else if (iEntry >= m_iQueueScrollOffset + m_iQueueWinClientHeight) { m_iQueueScrollOffset += m_iQueueWinClientHeight; } if (m_iQueueScrollOffset > iQueueSize - m_iQueueWinClientHeight) { m_iQueueScrollOffset = iQueueSize - m_iQueueWinClientHeight; } if (m_iQueueScrollOffset < 0) { m_iQueueScrollOffset = 0; } m_iSelectedQueueEntry = iEntry; } /* * Process keystrokes starting with the initialKey, which must not be * READKEY_EMPTY but has alread been set via ReadConsoleKey. */ void NCursesFrontend::UpdateInput(int initialKey) { int iKey = initialKey; while (iKey != READKEY_EMPTY) { int iQueueSize = CalcQueueSize(); // Normal or edit queue mode if (m_eInputMode == eNormal || m_eInputMode == eEditQueue) { switch (iKey) { case 'q': // Key 'q' for quit ExitProc(); break; case 'z': // show/hide NZBFilename m_bShowNZBname = !m_bShowNZBname; break; case 'w': // swicth window sizes if (m_QueueWindowPercentage == 0.5) { m_QueueWindowPercentage = 1; } else if (m_QueueWindowPercentage == 1 && m_eInputMode != eEditQueue) { m_QueueWindowPercentage = 0; } else { m_QueueWindowPercentage = 0.5; } CalcWindowSizes(); SetCurrentQueueEntry(m_iSelectedQueueEntry); break; case 'g': // group/ungroup files m_bGroupFiles = !m_bGroupFiles; SetCurrentQueueEntry(m_iSelectedQueueEntry); NeedUpdateData(); break; } } // Normal mode if (m_eInputMode == eNormal) { switch (iKey) { case 'p': // Key 'p' for pause if (!IsRemoteMode()) { info(m_bPauseDownload || m_bPauseDownload2 ? "Unpausing download" : "Pausing download"); } ServerPauseUnpause(!(m_bPauseDownload || m_bPauseDownload2), m_bPauseDownload2 && !m_bPauseDownload); break; case '\'': ServerDumpDebug(); break; case 'e': case 10: // return case 13: // enter if (iQueueSize > 0) { m_eInputMode = eEditQueue; if (m_QueueWindowPercentage == 0) { m_QueueWindowPercentage = 0.5; } return; } break; 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 if (m_eInputMode == eEditQueue) { switch (iKey) { case 'e': case 10: // return case 13: // enter m_eInputMode = eNormal; return; case KEY_DOWN: if (m_iSelectedQueueEntry < iQueueSize - 1) { SetCurrentQueueEntry(m_iSelectedQueueEntry + 1); } break; case KEY_UP: if (m_iSelectedQueueEntry > 0) { SetCurrentQueueEntry(m_iSelectedQueueEntry - 1); } break; case KEY_PPAGE: if (m_iSelectedQueueEntry > 0) { if (m_iSelectedQueueEntry == m_iQueueScrollOffset) { m_iQueueScrollOffset -= m_iQueueWinClientHeight; SetCurrentQueueEntry(m_iSelectedQueueEntry - m_iQueueWinClientHeight); } else { SetCurrentQueueEntry(m_iQueueScrollOffset); } } break; case KEY_NPAGE: if (m_iSelectedQueueEntry < iQueueSize - 1) { if (m_iSelectedQueueEntry == m_iQueueScrollOffset + m_iQueueWinClientHeight - 1) { m_iQueueScrollOffset += m_iQueueWinClientHeight; SetCurrentQueueEntry(m_iSelectedQueueEntry + m_iQueueWinClientHeight); } else { SetCurrentQueueEntry(m_iQueueScrollOffset + m_iQueueWinClientHeight - 1); } } break; case KEY_HOME: SetCurrentQueueEntry(0); break; case KEY_END: SetCurrentQueueEntry(iQueueSize > 0 ? iQueueSize - 1 : 0); break; case 'p': // Key 'p' for pause EditQueue(QueueEditor::eaFilePause, 0); break; case 'd': SetHint(" Use Uppercase \"D\" for delete"); 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 if (m_eInputMode == eDownloadRate) { // Numbers if (m_iInputNumberIndex < 5 && iKey >= '0' && iKey <= '9') { m_iInputValue = (m_iInputValue * 10) + (iKey - '0'); m_iInputNumberIndex++; } // Enter else if (iKey == 10 || iKey == 13) { ServerSetDownloadRate(m_iInputValue * 1024); m_eInputMode = eNormal; return; } // Escape else if (iKey == 27) { m_eInputMode = eNormal; return; } // Backspace else if (m_iInputNumberIndex > 0 && iKey == KEY_BACKSPACE) { int iRemain = m_iInputValue % 10; m_iInputValue = (m_iInputValue - iRemain) / 10; m_iInputNumberIndex--; } } iKey = ReadConsoleKey(); } } int NCursesFrontend::ReadConsoleKey() { #ifdef WIN32 HANDLE hConsole = GetStdHandle(STD_INPUT_HANDLE); DWORD NumberOfEvents; BOOL bOK = GetNumberOfConsoleInputEvents(hConsole, &NumberOfEvents); if (bOK && NumberOfEvents > 0) { while (NumberOfEvents--) { INPUT_RECORD InputRecord; DWORD NumberOfEventsRead; if (ReadConsoleInput(hConsole, &InputRecord, 1, &NumberOfEventsRead) && NumberOfEventsRead > 0 && InputRecord.EventType == KEY_EVENT && InputRecord.Event.KeyEvent.bKeyDown) { char c = tolower(InputRecord.Event.KeyEvent.wVirtualKeyCode); if (bool(InputRecord.Event.KeyEvent.dwControlKeyState & CAPSLOCK_ON) ^ bool(InputRecord.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED)) { c = toupper(c); } return c; } } } return READKEY_EMPTY; #else return getch(); #endif } #endif nzbget-12.0+dfsg/NCursesFrontend.h000066400000000000000000000067221226450633000171070ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2009 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 397 $ * $Date: 2011-05-24 14:52:41 +0200 (Tue, 24 May 2011) $ * */ #ifndef NCURSESFRONTEND_H #define NCURSESFRONTEND_H #ifndef DISABLE_CURSES #include #include #include "Frontend.h" #include "Log.h" #include "DownloadInfo.h" class NCursesFrontend : public Frontend { private: enum EInputMode { eNormal, eEditQueue, eDownloadRate }; bool m_bUseColor; int m_iDataUpdatePos; bool m_bUpdateNextTime; 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; char* m_szHint; time_t m_tStartHint; int m_iColWidthFiles; int m_iColWidthTotal; int m_iColWidthLeft; // Inputting numbers int m_iInputNumberIndex; int m_iInputValue; #ifdef WIN32 CHAR_INFO* m_pScreenBuffer; CHAR_INFO* m_pOldScreenBuffer; int m_iScreenBufferSize; std::vector m_ColorAttr; #else void* m_pWindow; // WINDOW* #endif EInputMode m_eInputMode; bool m_bShowNZBname; bool m_bShowTimestamp; bool m_bGroupFiles; float m_QueueWindowPercentage; #ifdef WIN32 void init_pair(int iColorNumber, WORD wForeColor, WORD wBackColor); #endif void PlotLine(const char * szString, int iRow, int iPos, int iColorPair); void PlotText(const char * szString, int iRow, int iPos, int iColorPair, bool bBlink); void PrintMessages(); void PrintQueue(); void PrintFileQueue(); void PrintFilename(FileInfo* pFileInfo, int iRow, bool bSelected); void PrintGroupQueue(); void ResetColWidths(); void PrintGroupname(GroupInfo * pGroupInfo, int iRow, bool bSelected, bool bCalcColWidth); void PrepareGroupQueue(); void PrintTopHeader(char* szHeader, int iLineNr, bool bUpTime); void ClearGroupQueue(); int PrintMessage(Message* Msg, int iRow, int iMaxLines); void PrintKeyInputBar(); void PrintStatus(); void UpdateInput(int initialKey); void Update(int iKey); void SetCurrentQueueEntry(int iEntry); void CalcWindowSizes(); void RefreshScreen(); int ReadConsoleKey(); int CalcQueueSize(); void NeedUpdateData(); bool EditQueue(QueueEditor::EEditAction eAction, int iOffset); void SetHint(const char* szHint); protected: virtual void Run(); public: NCursesFrontend(); virtual ~NCursesFrontend(); }; #endif #endif nzbget-12.0+dfsg/NEWS000066400000000000000000000000001226450633000143320ustar00rootroot00000000000000nzbget-12.0+dfsg/NNTPConnection.cpp000066400000000000000000000141471226450633000171570ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 911 $ * $Date: 2013-11-24 20:29:52 +0100 (Sun, 24 Nov 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #include #include "nzbget.h" #include "Log.h" #include "NNTPConnection.h" #include "Connection.h" #include "NewsServer.h" static const int CONNECTION_LINEBUFFER_SIZE = 1024*10; NNTPConnection::NNTPConnection(NewsServer* pNewsServer) : Connection(pNewsServer->GetHost(), pNewsServer->GetPort(), pNewsServer->GetTLS()) { m_pNewsServer = pNewsServer; m_szActiveGroup = NULL; m_szLineBuf = (char*)malloc(CONNECTION_LINEBUFFER_SIZE); m_bAuthError = false; SetCipher(pNewsServer->GetCipher()); } NNTPConnection::~NNTPConnection() { free(m_szActiveGroup); free(m_szLineBuf); } const char* NNTPConnection::Request(const char* req) { if (!req) { return NULL; } m_bAuthError = false; WriteLine(req); char* answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL); if (!answer) { return NULL; } if (!strncmp(answer, "480", 3)) { debug("%s requested authorization", GetHost()); if (!Authenticate()) { return NULL; } //try again WriteLine(req); answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL); } return answer; } bool NNTPConnection::Authenticate() { if (strlen(m_pNewsServer->GetUser()) == 0 || strlen(m_pNewsServer->GetPassword()) == 0) { error("%c%s (%s) requested authorization but username/password are not set in settings", toupper(m_pNewsServer->GetName()[0]), m_pNewsServer->GetName() + 1, m_pNewsServer->GetHost()); m_bAuthError = true; return false; } m_bAuthError = !AuthInfoUser(0); return !m_bAuthError; } bool NNTPConnection::AuthInfoUser(int iRecur) { if (iRecur > 10) { return false; } char tmp[1024]; snprintf(tmp, 1024, "AUTHINFO USER %s\r\n", m_pNewsServer->GetUser()); tmp[1024-1] = '\0'; WriteLine(tmp); char* answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL); if (!answer) { ReportErrorAnswer("Authorization for server%i (%s) failed: Connection closed by remote host", NULL); return false; } if (!strncmp(answer, "281", 3)) { debug("Authorization for %s successful", GetHost()); return true; } else if (!strncmp(answer, "381", 3)) { return AuthInfoPass(++iRecur); } else if (!strncmp(answer, "480", 3)) { return AuthInfoUser(++iRecur); } if (char* p = strrchr(answer, '\r')) *p = '\0'; // remove last CRLF from error message if (GetStatus() != csCancelled) { ReportErrorAnswer("Authorization for server%i (%s) failed (Answer: %s)", answer); } return false; } bool NNTPConnection::AuthInfoPass(int iRecur) { if (iRecur > 10) { return false; } char tmp[1024]; snprintf(tmp, 1024, "AUTHINFO PASS %s\r\n", m_pNewsServer->GetPassword()); tmp[1024-1] = '\0'; WriteLine(tmp); char* answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL); if (!answer) { ReportErrorAnswer("Authorization for server%i (%s) failed: Connection closed by remote host", NULL); return false; } else if (!strncmp(answer, "2", 1)) { debug("Authorization for %s successful", GetHost()); return true; } else if (!strncmp(answer, "381", 3)) { return AuthInfoPass(++iRecur); } if (char* p = strrchr(answer, '\r')) *p = '\0'; // remove last CRLF from error message if (GetStatus() != csCancelled) { ReportErrorAnswer("Authorization for server%i (%s) failed (Answer: %s)", answer); } return false; } const char* NNTPConnection::JoinGroup(const char* grp) { if (m_szActiveGroup && !strcmp(m_szActiveGroup, grp)) { // already in group strcpy(m_szLineBuf, "211 "); return m_szLineBuf; } char tmp[1024]; snprintf(tmp, 1024, "GROUP %s\r\n", grp); tmp[1024-1] = '\0'; const char* answer = Request(tmp); if (answer && !strncmp(answer, "2", 1)) { debug("Changed group to %s on %s", grp, GetHost()); free(m_szActiveGroup); m_szActiveGroup = strdup(grp); } else { debug("Error changing group on %s to %s: %s.", GetHost(), grp, answer); } return answer; } bool NNTPConnection::Connect() { debug("Opening connection to %s", GetHost()); if (m_eStatus == csConnected) { return true; } if (!Connection::Connect()) { return false; } char* answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL); if (!answer) { ReportErrorAnswer("Connection to server%i (%s) failed: Connection closed by remote host", NULL); Disconnect(); return false; } if (strncmp(answer, "2", 1)) { ReportErrorAnswer("Connection to server%i (%s) failed (Answer: %s)", answer); Disconnect(); return false; } if ((strlen(m_pNewsServer->GetUser()) > 0 && strlen(m_pNewsServer->GetPassword()) > 0) && !Authenticate()) { return false; } debug("Connection to %s established", GetHost()); return true; } bool NNTPConnection::Disconnect() { if (m_eStatus == csConnected) { Request("quit\r\n"); free(m_szActiveGroup); m_szActiveGroup = NULL; } return Connection::Disconnect(); } void NNTPConnection::ReportErrorAnswer(const char* szMsgPrefix, const char* szAnswer) { char szErrStr[1024]; snprintf(szErrStr, 1024, szMsgPrefix, m_pNewsServer->GetID(), m_pNewsServer->GetHost(), szAnswer); szErrStr[1024-1] = '\0'; ReportError(szErrStr, NULL, false, 0); } nzbget-12.0+dfsg/NNTPConnection.h000066400000000000000000000034031226450633000166150ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2008 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 634 $ * $Date: 2013-04-17 22:34:15 +0200 (Wed, 17 Apr 2013) $ * */ #ifndef NNTPCONNECTION_H #define NNTPCONNECTION_H #include "NewsServer.h" #include "Connection.h" class NNTPConnection : public Connection { private: NewsServer* m_pNewsServer; char* m_szActiveGroup; char* m_szLineBuf; bool m_bAuthError; void Clear(); void ReportErrorAnswer(const char* szMsgPrefix, const char* szAnswer); bool Authenticate(); bool AuthInfoUser(int iRecur); bool AuthInfoPass(int iRecur); public: NNTPConnection(NewsServer* pNewsServer); virtual ~NNTPConnection(); virtual bool Connect(); virtual bool Disconnect(); NewsServer* GetNewsServer() { return m_pNewsServer; } const char* Request(const char* req); const char* JoinGroup(const char* grp); bool GetAuthError() { return m_bAuthError; } }; #endif nzbget-12.0+dfsg/NTService.cpp000066400000000000000000000112561226450633000162200ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 397 $ * $Date: 2011-05-24 14:52:41 +0200 (Tue, 24 May 2011) $ * */ #ifdef HAVE_CONFIG_H #include #endif #include "win32.h" #include #include "nzbget.h" #include "Log.h" #include "NTService.h" extern void ExitProc(); RunProc Run = NULL; 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; } 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"); } } 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 szExeName[1024]; GetModuleFileName(NULL, szExeName, 1024); szExeName[1024-1] = '\0'; char szCmdLine[1024]; snprintf(szCmdLine, 1024, "%s -D", szExeName); 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); } void InstallUninstallServiceCheck(int argc, char *argv[]) { if (argc > 1 && (!strcasecmp(argv[1], "/install") || !strcasecmp(argv[1], "-install"))) { InstallService(argc, argv); exit(0); } else if (argc > 1 && (!strcasecmp(argv[1], "/uninstall") || !strcasecmp(argv[1], "/remove") || !strcasecmp(argv[1], "-uninstall") || !strcasecmp(argv[1], "-remove"))) { UnInstallService(); exit(0); } } nzbget-12.0+dfsg/NTService.h000066400000000000000000000021271226450633000156620ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 397 $ * $Date: 2011-05-24 14:52:41 +0200 (Tue, 24 May 2011) $ * */ #ifndef NTSERVICE_H #define NTSERVICE_H typedef void (*RunProc)(void); void InstallUninstallServiceCheck(int argc, char *argv[]); void StartService(RunProc RunProcPtr); #endif nzbget-12.0+dfsg/NZBFile.cpp000066400000000000000000000512041226450633000156040ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 911 $ * $Date: 2013-11-24 20:29:52 +0100 (Sun, 24 Nov 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #ifdef WIN32 #include #import named_guids using namespace MSXML; #else #include #include #include #include #endif #include "nzbget.h" #include "NZBFile.h" #include "Log.h" #include "DownloadInfo.h" #include "Options.h" #include "DiskState.h" #include "Util.h" extern Options* g_pOptions; extern DiskState* g_pDiskState; NZBFile::NZBFile(const char* szFileName, const char* szCategory) { debug("Creating NZBFile"); m_szFileName = strdup(szFileName); m_szPassword = NULL; m_pNZBInfo = new NZBInfo(); m_pNZBInfo->Retain(); m_pNZBInfo->SetFilename(szFileName); m_pNZBInfo->SetCategory(szCategory); m_pNZBInfo->BuildDestDirName(); #ifndef WIN32 m_bPassword = false; m_pFileInfo = NULL; m_pArticle = NULL; m_szTagContent = NULL; m_iTagContentLen = 0; #endif m_FileInfos.clear(); } NZBFile::~NZBFile() { debug("Destroying NZBFile"); // Cleanup free(m_szFileName); free(m_szPassword); for (FileInfos::iterator it = m_FileInfos.begin(); it != m_FileInfos.end(); it++) { delete *it; } m_FileInfos.clear(); if (m_pNZBInfo) { m_pNZBInfo->Release(); } #ifndef WIN32 delete m_pFileInfo; free(m_szTagContent); #endif } void NZBFile::LogDebugInfo() { debug(" NZBFile %s", m_szFileName); } void NZBFile::DetachFileInfos() { m_FileInfos.clear(); } void NZBFile::AddArticle(FileInfo* pFileInfo, ArticleInfo* pArticleInfo) { // make Article-List big enough while ((int)pFileInfo->GetArticles()->size() < pArticleInfo->GetPartNumber()) pFileInfo->GetArticles()->push_back(NULL); int index = pArticleInfo->GetPartNumber() - 1; if ((*pFileInfo->GetArticles())[index]) { delete (*pFileInfo->GetArticles())[index]; } (*pFileInfo->GetArticles())[index] = pArticleInfo; } void NZBFile::AddFileInfo(FileInfo* pFileInfo) { // calculate file size and delete empty articles long long lSize = 0; long long lMissedSize = 0; long long lOneSize = 0; int iUncountedArticles = 0; int iMissedArticles = 0; FileInfo::Articles* pArticles = pFileInfo->GetArticles(); int iTotalArticles = (int)pArticles->size(); int i = 0; for (FileInfo::Articles::iterator it = pArticles->begin(); it != pArticles->end(); ) { ArticleInfo* pArticle = *it; if (!pArticle) { pArticles->erase(it); it = pArticles->begin() + i; iMissedArticles++; if (lOneSize > 0) { lMissedSize += lOneSize; } else { iUncountedArticles++; } } else { lSize += pArticle->GetSize(); if (lOneSize == 0) { lOneSize = pArticle->GetSize(); } it++; i++; } } if (pArticles->empty()) { delete pFileInfo; return; } lMissedSize += iUncountedArticles * lOneSize; lSize += lMissedSize; m_FileInfos.push_back(pFileInfo); pFileInfo->SetNZBInfo(m_pNZBInfo); pFileInfo->SetSize(lSize); pFileInfo->SetRemainingSize(lSize - lMissedSize); pFileInfo->SetMissedSize(lMissedSize); pFileInfo->SetTotalArticles(iTotalArticles); pFileInfo->SetMissedArticles(iMissedArticles); } void NZBFile::ParseSubject(FileInfo* pFileInfo, bool TryQuotes) { if (TryQuotes) { // try to use the filename in quatation marks char* p = (char*)pFileInfo->GetSubject(); char* start = strchr(p, '\"'); if (start) { start++; char* end = strchr(start + 1, '\"'); if (end) { int len = (int)(end - start); char* point = strchr(start + 1, '.'); if (point && point < end) { char* filename = (char*)malloc(len + 1); strncpy(filename, start, len); filename[len] = '\0'; pFileInfo->SetFilename(filename); free(filename); return; } } } } // 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 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 = (int)(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()); } } bool NZBFile::HasDuplicateFilenames() { for (FileInfos::iterator it = m_FileInfos.begin(); it != m_FileInfos.end(); it++) { FileInfo* pFileInfo1 = *it; int iDupe = 1; 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)) { return true; } } return false; } /** * Generate filenames from subjects and check if the parsing of subject was correct */ void NZBFile::BuildFilenames() { for (FileInfos::iterator it = m_FileInfos.begin(); it != m_FileInfos.end(); it++) { FileInfo* pFileInfo = *it; ParseSubject(pFileInfo, true); } if (HasDuplicateFilenames()) { for (FileInfos::iterator it = m_FileInfos.begin(); it != m_FileInfos.end(); it++) { FileInfo* pFileInfo = *it; ParseSubject(pFileInfo, false); } } if (HasDuplicateFilenames()) { m_pNZBInfo->SetManyDupeFiles(true); for (FileInfos::iterator it = m_FileInfos.begin(); it != m_FileInfos.end(); it++) { FileInfo* pFileInfo = *it; pFileInfo->SetFilename(pFileInfo->GetSubject()); } } } bool CompareFileInfo(FileInfo* pFirst, FileInfo* pSecond) { return strcmp(pFirst->GetFilename(), pSecond->GetFilename()) > 0; } void NZBFile::CalcHashes() { FileInfoList fileList; for (FileInfos::iterator it = m_FileInfos.begin(); it != m_FileInfos.end(); it++) { fileList.push_back(*it); } fileList.sort(CompareFileInfo); // split ExtCleanupDisk into tokens and create a list ExtList extList; char* szExtCleanupDisk = strdup(g_pOptions->GetExtCleanupDisk()); char* saveptr; char* szExt = strtok_r(szExtCleanupDisk, ",; ", &saveptr); while (szExt) { extList.push_back(szExt); szExt = strtok_r(NULL, ",; ", &saveptr); } unsigned int iFullContentHash = 0; unsigned int iFilteredContentHash = 0; int iUseForFilteredCount = 0; for (FileInfoList::iterator it = fileList.begin(); it != fileList.end(); it++) { FileInfo* pFileInfo = *it; // check file extension int iFilenameLen = strlen(pFileInfo->GetFilename()); bool bSkip = false; for (ExtList::iterator it = extList.begin(); it != extList.end(); it++) { const char* szExt = *it; int iExtLen = strlen(szExt); if (iFilenameLen >= iExtLen && !strcasecmp(szExt, pFileInfo->GetFilename() + iFilenameLen - iExtLen)) { bSkip = true; break; } } bSkip = bSkip && !pFileInfo->GetParFile(); for (FileInfo::Articles::iterator it = pFileInfo->GetArticles()->begin(); it != pFileInfo->GetArticles()->end(); it++) { ArticleInfo* pArticle = *it; int iLen = strlen(pArticle->GetMessageID()); iFullContentHash = Util::HashBJ96(pArticle->GetMessageID(), iLen, iFullContentHash); if (!bSkip) { iFilteredContentHash = Util::HashBJ96(pArticle->GetMessageID(), iLen, iFilteredContentHash); iUseForFilteredCount++; } } } free(szExtCleanupDisk); // if filtered hash is based on less than a half of files - do not use filtered hash at all if (iUseForFilteredCount < (int)fileList.size() / 2) { iFilteredContentHash = 0; } m_pNZBInfo->SetFullContentHash(iFullContentHash); m_pNZBInfo->SetFilteredContentHash(iFilteredContentHash); } void NZBFile::ProcessFiles() { BuildFilenames(); for (FileInfos::iterator it = m_FileInfos.begin(); it != m_FileInfos.end(); it++) { FileInfo* pFileInfo = *it; pFileInfo->MakeValidFilename(); 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 bool bParFile = strstr(szLoFileName, ".par2"); m_pNZBInfo->SetFileCount(m_pNZBInfo->GetFileCount() + 1); m_pNZBInfo->SetTotalArticles(m_pNZBInfo->GetTotalArticles() + pFileInfo->GetTotalArticles()); m_pNZBInfo->SetSize(m_pNZBInfo->GetSize() + pFileInfo->GetSize()); m_pNZBInfo->SetFailedSize(m_pNZBInfo->GetFailedSize() + pFileInfo->GetMissedSize()); m_pNZBInfo->SetCurrentFailedSize(m_pNZBInfo->GetFailedSize()); pFileInfo->SetParFile(bParFile); if (bParFile) { m_pNZBInfo->SetParSize(m_pNZBInfo->GetParSize() + pFileInfo->GetSize()); m_pNZBInfo->SetParFailedSize(m_pNZBInfo->GetParFailedSize() + pFileInfo->GetMissedSize()); m_pNZBInfo->SetParCurrentFailedSize(m_pNZBInfo->GetParFailedSize()); } } CalcHashes(); if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { for (FileInfos::iterator it = m_FileInfos.begin(); it != m_FileInfos.end(); it++) { FileInfo* pFileInfo = *it; g_pDiskState->SaveFile(pFileInfo); pFileInfo->ClearArticles(); } } if (m_szPassword) { ReadPassword(); } } /** * Password read using XML-parser may have special characters (such as TAB) stripped. * This function rereads password directly from file to keep all characters intact. */ void NZBFile::ReadPassword() { FILE* pFile = fopen(m_szFileName, "rb"); if (!pFile) { return; } // obtain file size. fseek(pFile , 0 , SEEK_END); int iSize = ftell(pFile); rewind(pFile); // reading first 4KB of the file // allocate memory to contain the whole file. char* buf = (char*)malloc(4096); iSize = iSize < 4096 ? iSize : 4096; // copy the file into the buffer. fread(buf, 1, iSize, pFile); fclose(pFile); buf[iSize-1] = '\0'; char* szMetaPassword = strstr(buf, ""); if (szMetaPassword) { szMetaPassword += 22; // length of '' char* szEnd = strstr(szMetaPassword, ""); if (szEnd) { *szEnd = '\0'; WebUtil::XmlDecode(szMetaPassword); free(m_szPassword); m_szPassword = strdup(szMetaPassword); } } free(buf); } #ifdef WIN32 NZBFile* NZBFile::Create(const char* szFileName, const char* szCategory) { CoInitialize(NULL); HRESULT hr; MSXML::IXMLDOMDocumentPtr doc; hr = doc.CreateInstance(MSXML::CLSID_DOMDocument); if (FAILED(hr)) { return NULL; } // Load the XML document file... doc->put_resolveExternals(VARIANT_FALSE); doc->put_validateOnParse(VARIANT_FALSE); doc->put_async(VARIANT_FALSE); // 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); VARIANT_BOOL success = doc->load(v); if (success == VARIANT_FALSE) { _bstr_t r(doc->GetparseError()->reason); const char* szErrMsg = r; error("Error parsing nzb-file: %s", szErrMsg); return NULL; } NZBFile* pFile = new NZBFile(szFileName, szCategory); if (pFile->ParseNZB(doc)) { pFile->ProcessFiles(); } else { delete pFile; pFile = NULL; } return pFile; } void NZBFile::EncodeURL(const char* szFilename, char* szURL) { while (char ch = *szFilename++) { if (('0' <= ch && ch <= '9') || ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') ) { *szURL++ = ch; } else { *szURL++ = '%'; int a = (unsigned char)ch >> 4; *szURL++ = a > 9 ? a - 10 + 'a' : a + '0'; a = ch & 0xF; *szURL++ = a > 9 ? a - 10 + 'a' : a + '0'; } } *szURL = NULL; } bool NZBFile::ParseNZB(IUnknown* nzb) { MSXML::IXMLDOMDocumentPtr doc = nzb; MSXML::IXMLDOMNodePtr root = doc->documentElement; MSXML::IXMLDOMNodePtr node = root->selectSingleNode("/nzb/head/meta[@type='password']"); if (node) { _bstr_t password(node->Gettext()); m_szPassword = strdup(password); } MSXML::IXMLDOMNodeListPtr fileList = root->selectNodes("/nzb/file"); for (int i = 0; i < fileList->Getlength(); i++) { node = fileList->Getitem(i); MSXML::IXMLDOMNodePtr attribute = node->Getattributes()->getNamedItem("subject"); if (!attribute) return false; _bstr_t subject(attribute->Gettext()); FileInfo* pFileInfo = new FileInfo(); pFileInfo->SetSubject(subject); attribute = node->Getattributes()->getNamedItem("date"); if (attribute) { _bstr_t date(attribute->Gettext()); pFileInfo->SetTime(atoi(date)); } MSXML::IXMLDOMNodeListPtr groupList = node->selectNodes("groups/group"); for (int g = 0; g < groupList->Getlength(); g++) { MSXML::IXMLDOMNodePtr node = groupList->Getitem(g); _bstr_t group = node->Gettext(); pFileInfo->GetGroups()->push_back(strdup((const char*)group)); } MSXML::IXMLDOMNodeListPtr segmentList = node->selectNodes("segments/segment"); for (int g = 0; g < segmentList->Getlength(); g++) { MSXML::IXMLDOMNodePtr node = segmentList->Getitem(g); _bstr_t id = node->Gettext(); char szId[2048]; snprintf(szId, 2048, "<%s>", (const char*)id); MSXML::IXMLDOMNodePtr attribute = node->Getattributes()->getNamedItem("number"); if (!attribute) return false; _bstr_t number(attribute->Gettext()); attribute = node->Getattributes()->getNamedItem("bytes"); if (!attribute) return false; _bstr_t bytes(attribute->Gettext()); int partNumber = atoi(number); int lsize = atoi(bytes); if (partNumber > 0) { ArticleInfo* pArticle = new ArticleInfo(); pArticle->SetPartNumber(partNumber); pArticle->SetMessageID(szId); pArticle->SetSize(lsize); AddArticle(pFileInfo, pArticle); } } AddFileInfo(pFileInfo); } return true; } #else NZBFile* NZBFile::Create(const char* szFileName, const char* szCategory) { NZBFile* pFile = new NZBFile(szFileName, szCategory); xmlSAXHandler SAX_handler = {0}; SAX_handler.startElement = reinterpret_cast(SAX_StartElement); SAX_handler.endElement = reinterpret_cast(SAX_EndElement); SAX_handler.characters = reinterpret_cast(SAX_characters); SAX_handler.error = reinterpret_cast(SAX_error); SAX_handler.getEntity = reinterpret_cast(SAX_getEntity); pFile->m_bIgnoreNextError = false; int ret = xmlSAXUserParseFile(&SAX_handler, pFile, szFileName); if (ret == 0) { pFile->ProcessFiles(); } else { error("Failed to parse nzb-file"); delete pFile; pFile = NULL; } return pFile; } void NZBFile::Parse_StartElement(const char *name, const char **atts) { if (m_szTagContent) { free(m_szTagContent); m_szTagContent = NULL; m_iTagContentLen = 0; } if (!strcmp("file", name)) { m_pFileInfo = new FileInfo(); m_pFileInfo->SetFilename(m_szFileName); for (int i = 0; atts[i]; i += 2) { const char* attrname = atts[i]; const char* attrvalue = atts[i + 1]; if (!strcmp("subject", attrname)) { m_pFileInfo->SetSubject(attrvalue); } if (!strcmp("date", attrname)) { m_pFileInfo->SetTime(atoi(attrvalue)); } } } else if (!strcmp("segment", name)) { if (!m_pFileInfo) { // error: bad nzb-file return; } long long lsize = -1; int partNumber = -1; for (int i = 0; atts[i]; i += 2) { const char* attrname = atts[i]; const char* attrvalue = atts[i + 1]; if (!strcmp("bytes", attrname)) { lsize = atol(attrvalue); } if (!strcmp("number", attrname)) { partNumber = atol(attrvalue); } } if (partNumber > 0) { // new segment, add it! m_pArticle = new ArticleInfo(); m_pArticle->SetPartNumber(partNumber); m_pArticle->SetSize(lsize); AddArticle(m_pFileInfo, m_pArticle); } } else if (!strcmp("meta", name)) { m_bPassword = atts[0] && atts[1] && !strcmp("type", atts[0]) && !strcmp("password", atts[1]); } } void NZBFile::Parse_EndElement(const char *name) { if (!strcmp("file", name)) { // Close the file element, add the new file to file-list AddFileInfo(m_pFileInfo); m_pFileInfo = NULL; m_pArticle = NULL; } else if (!strcmp("group", name)) { if (!m_pFileInfo) { // error: bad nzb-file return; } m_pFileInfo->GetGroups()->push_back(m_szTagContent); m_szTagContent = NULL; m_iTagContentLen = 0; } else if (!strcmp("segment", name)) { if (!m_pFileInfo || !m_pArticle) { // error: bad nzb-file return; } // Get the #text part char ID[2048]; snprintf(ID, 2048, "<%s>", m_szTagContent); m_pArticle->SetMessageID(ID); m_pArticle = NULL; } else if (!strcmp("meta", name) && m_bPassword) { m_szPassword = strdup(m_szTagContent); } } void NZBFile::Parse_Content(const char *buf, int len) { m_szTagContent = (char*)realloc(m_szTagContent, m_iTagContentLen + len + 1); strncpy(m_szTagContent + m_iTagContentLen, buf, len); m_iTagContentLen += len; m_szTagContent[m_iTagContentLen] = '\0'; } void NZBFile::SAX_StartElement(NZBFile* pFile, const char *name, const char **atts) { pFile->Parse_StartElement(name, atts); } void NZBFile::SAX_EndElement(NZBFile* pFile, const char *name) { pFile->Parse_EndElement(name); } void NZBFile::SAX_characters(NZBFile* pFile, const char * xmlstr, int len) { char* str = (char*)xmlstr; // trim starting blanks int off = 0; for (int i = 0; i < len; i++) { char ch = str[i]; if (ch == ' ' || ch == 10 || ch == 13 || ch == 9) { off++; } else { break; } } int newlen = len - off; // trim ending blanks for (int i = len - 1; i >= off; i--) { char ch = str[i]; if (ch == ' ' || ch == 10 || ch == 13 || ch == 9) { newlen--; } else { break; } } if (newlen > 0) { // interpret tag content pFile->Parse_Content(str + off, newlen); } } void* NZBFile::SAX_getEntity(NZBFile* pFile, const char * name) { xmlEntityPtr e = xmlGetPredefinedEntity((xmlChar* )name); if (!e) { warn("entity not found"); pFile->m_bIgnoreNextError = true; } return e; } void NZBFile::SAX_error(NZBFile* pFile, const char *msg, ...) { if (pFile->m_bIgnoreNextError) { pFile->m_bIgnoreNextError = false; return; } va_list argp; va_start(argp, msg); char szErrMsg[1024]; vsnprintf(szErrMsg, sizeof(szErrMsg), msg, argp); szErrMsg[1024-1] = '\0'; va_end(argp); // remove trailing CRLF for (char* pend = szErrMsg + strlen(szErrMsg) - 1; pend >= szErrMsg && (*pend == '\n' || *pend == '\r' || *pend == ' '); pend--) *pend = '\0'; error("Error parsing nzb-file: %s", szErrMsg); } #endif nzbget-12.0+dfsg/NZBFile.h000066400000000000000000000055451226450633000152600ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 843 $ * $Date: 2013-09-24 22:42:32 +0200 (Tue, 24 Sep 2013) $ * */ #ifndef NZBFILE_H #define NZBFILE_H #include #include #include "DownloadInfo.h" class NZBFile { public: typedef std::vector FileInfos; typedef std::list FileInfoList; typedef std::list ExtList; private: FileInfos m_FileInfos; NZBInfo* m_pNZBInfo; char* m_szFileName; char* m_szPassword; NZBFile(const char* szFileName, const char* szCategory); void AddArticle(FileInfo* pFileInfo, ArticleInfo* pArticleInfo); void AddFileInfo(FileInfo* pFileInfo); void ParseSubject(FileInfo* pFileInfo, bool TryQuotes); void BuildFilenames(); void ProcessFiles(); void CalcHashes(); bool HasDuplicateFilenames(); void ReadPassword(); #ifdef WIN32 bool ParseNZB(IUnknown* nzb); static void EncodeURL(const char* szFilename, char* szURL); #else FileInfo* m_pFileInfo; ArticleInfo* m_pArticle; char* m_szTagContent; int m_iTagContentLen; bool m_bIgnoreNextError; bool m_bPassword; static void SAX_StartElement(NZBFile* pFile, const char *name, const char **atts); static void SAX_EndElement(NZBFile* pFile, const char *name); static void SAX_characters(NZBFile* pFile, const char * xmlstr, int len); static void* SAX_getEntity(NZBFile* pFile, const char * name); static void SAX_error(NZBFile* pFile, const char *msg, ...); void Parse_StartElement(const char *name, const char **atts); void Parse_EndElement(const char *name); void Parse_Content(const char *buf, int len); #endif public: virtual ~NZBFile(); static NZBFile* Create(const char* szFileName, const char* szCategory); const char* GetFileName() const { return m_szFileName; } FileInfos* GetFileInfos() { return &m_FileInfos; } NZBInfo* GetNZBInfo() { return m_pNZBInfo; } const char* GetPassword() { return m_szPassword; } void DetachFileInfos(); void LogDebugInfo(); }; #endif nzbget-12.0+dfsg/NewsServer.cpp000066400000000000000000000041261226450633000164570ustar00rootroot00000000000000/* * This file if part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 911 $ * $Date: 2013-11-24 20:29:52 +0100 (Sun, 24 Nov 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #include "nzbget.h" #include "NewsServer.h" NewsServer::NewsServer(int iID, bool bActive, const char* szName, const char* szHost, int iPort, const char* szUser, const char* szPass, bool bJoinGroup, bool bTLS, const char* szCipher, int iMaxConnections, int iLevel, int iGroup) { m_iID = iID; m_iStateID = 0; m_bActive = bActive; m_iPort = iPort; m_iLevel = iLevel; m_iNormLevel = iLevel; m_iGroup = iGroup; m_iMaxConnections = iMaxConnections; m_bJoinGroup = bJoinGroup; m_bTLS = bTLS; m_szHost = strdup(szHost ? szHost : ""); m_szUser = strdup(szUser ? szUser : ""); m_szPassword = strdup(szPass ? szPass : ""); m_szCipher = strdup(szCipher ? szCipher : ""); if (szName && strlen(szName) > 0) { m_szName = strdup(szName); } else { m_szName = (char*)malloc(20); snprintf(m_szName, 20, "server%i", iID); m_szName[20-1] = '\0'; } } NewsServer::~NewsServer() { free(m_szName); free(m_szHost); free(m_szUser); free(m_szPassword); free(m_szCipher); } nzbget-12.0+dfsg/NewsServer.h000066400000000000000000000047671226450633000161370ustar00rootroot00000000000000/* * This file if part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 794 $ * $Date: 2013-08-16 23:53:32 +0200 (Fri, 16 Aug 2013) $ * */ #ifndef NEWSSERVER_H #define NEWSSERVER_H #include class NewsServer { private: int m_iID; int m_iStateID; bool m_bActive; char* m_szName; int m_iGroup; char* m_szHost; int m_iPort; char* m_szUser; char* m_szPassword; int m_iMaxConnections; int m_iLevel; int m_iNormLevel; bool m_bJoinGroup; bool m_bTLS; char* m_szCipher; public: NewsServer(int iID, bool bActive, const char* szName, const char* szHost, int iPort, const char* szUser, const char* szPass, bool bJoinGroup, bool bTLS, const char* szCipher, int iMaxConnections, int iLevel, int iGroup); ~NewsServer(); int GetID() { return m_iID; } int GetStateID() { return m_iStateID; } void SetStateID(int iStateID) { m_iStateID = iStateID; } bool GetActive() { return m_bActive; } void SetActive(bool bActive) { m_bActive = bActive; } const char* GetName() { return m_szName; } int GetGroup() { return m_iGroup; } const char* GetHost() { return m_szHost; } int GetPort() { return m_iPort; } const char* GetUser() { return m_szUser; } const char* GetPassword() { return m_szPassword; } int GetMaxConnections() { return m_iMaxConnections; } int GetLevel() { return m_iLevel; } int GetNormLevel() { return m_iNormLevel; } void SetNormLevel(int iLevel) { m_iNormLevel = iLevel; } int GetJoinGroup() { return m_bJoinGroup; } bool GetTLS() { return m_bTLS; } const char* GetCipher() { return m_szCipher; } }; typedef std::vector Servers; #endif nzbget-12.0+dfsg/Observer.cpp000066400000000000000000000030431226450633000161400ustar00rootroot00000000000000/* * This file if part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 424 $ * $Date: 2012-06-23 20:58:56 +0200 (Sat, 23 Jun 2012) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include "Observer.h" #include "Log.h" Subject::Subject() { m_Observers.clear(); } void Subject::Attach(Observer* Observer) { m_Observers.push_back(Observer); } void Subject::Detach(Observer* Observer) { m_Observers.remove(Observer); } void Subject::Notify(void* Aspect) { debug("Notifying observers"); for (std::list::iterator it = m_Observers.begin(); it != m_Observers.end(); it++) { Observer* Observer = *it; Observer->Update(this, Aspect); } } nzbget-12.0+dfsg/Observer.h000066400000000000000000000025531226450633000156120ustar00rootroot00000000000000/* * This file if part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 397 $ * $Date: 2011-05-24 14:52:41 +0200 (Tue, 24 May 2011) $ * */ #ifndef OBSERVER_H #define OBSERVER_H #include class Observer; class Subject { private: std::list m_Observers; public: Subject(); void Attach(Observer* Observer); void Detach(Observer* Observer); void Notify(void* Aspect); }; class Observer { public: virtual ~Observer() {}; virtual void Update(Subject* Caller, void* Aspect) = 0; }; #endif nzbget-12.0+dfsg/Options.cpp000066400000000000000000002541421226450633000160140ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 924 $ * $Date: 2013-12-21 22:39:49 +0100 (Sat, 21 Dec 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #include #include #include #ifdef WIN32 #include #else #include #include #endif #include "nzbget.h" #include "Util.h" #include "Options.h" #include "Log.h" #include "ServerPool.h" #include "NewsServer.h" #include "MessageBase.h" #include "Scheduler.h" #include "FeedCoordinator.h" extern ServerPool* g_pServerPool; extern Scheduler* g_pScheduler; extern FeedCoordinator* g_pFeedCoordinator; #ifdef HAVE_GETOPT_LONG static struct option long_options[] = { {"help", no_argument, 0, 'h'}, {"configfile", required_argument, 0, 'c'}, {"noconfigfile", no_argument, 0, 'n'}, {"printconfig", no_argument, 0, 'p'}, {"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'}, {"pause", no_argument, 0, 'P'}, {"unpause", no_argument, 0, 'U'}, {"rate", required_argument, 0, 'R'}, {"debug", no_argument, 0, 'B'}, {"log", required_argument, 0, 'G'}, {"top", no_argument, 0, 'T'}, {"edit", required_argument, 0, 'E'}, {"connect", no_argument, 0, 'C'}, {"quit", no_argument, 0, 'Q'}, {"reload", no_argument, 0, 'O'}, {"write", required_argument, 0, 'W'}, {"category", required_argument, 0, 'K'}, {"scan", no_argument, 0, 'S'}, {0, 0, 0, 0} }; #endif static char short_options[] = "c:hno:psvAB:DCE:G:K:LPR:STUQOVW:"; // Program options static const char* OPTION_CONFIGFILE = "ConfigFile"; static const char* OPTION_APPBIN = "AppBin"; static const char* OPTION_APPDIR = "AppDir"; static const char* OPTION_VERSION = "Version"; static const char* OPTION_MAINDIR = "MainDir"; static const char* OPTION_DESTDIR = "DestDir"; static const char* OPTION_INTERDIR = "InterDir"; static const char* OPTION_TEMPDIR = "TempDir"; static const char* OPTION_QUEUEDIR = "QueueDir"; static const char* OPTION_NZBDIR = "NzbDir"; static const char* OPTION_WEBDIR = "WebDir"; static const char* OPTION_CONFIGTEMPLATE = "ConfigTemplate"; static const char* OPTION_SCRIPTDIR = "ScriptDir"; static const char* OPTION_CREATELOG = "CreateLog"; static const char* OPTION_LOGFILE = "LogFile"; static const char* OPTION_APPENDCATEGORYDIR = "AppendCategoryDir"; 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_CONTROLIP = "ControlIp"; static const char* OPTION_CONTROLPORT = "ControlPort"; static const char* OPTION_CONTROLUSERNAME = "ControlUsername"; static const char* OPTION_CONTROLPASSWORD = "ControlPassword"; static const char* OPTION_SECURECONTROL = "SecureControl"; static const char* OPTION_SECUREPORT = "SecurePort"; static const char* OPTION_SECURECERT = "SecureCert"; static const char* OPTION_SECUREKEY = "SecureKey"; static const char* OPTION_AUTHORIZEDIP = "AuthorizedIP"; static const char* OPTION_CONNECTIONTIMEOUT = "ConnectionTimeout"; static const char* OPTION_SAVEQUEUE = "SaveQueue"; static const char* OPTION_RELOADQUEUE = "ReloadQueue"; static const char* OPTION_RELOADURLQUEUE = "ReloadUrlQueue"; static const char* OPTION_RELOADPOSTQUEUE = "ReloadPostQueue"; static const char* OPTION_CREATEBROKENLOG = "CreateBrokenLog"; static const char* OPTION_RESETLOG = "ResetLog"; static const char* OPTION_DECODE = "Decode"; 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_URLCONNECTIONS = "UrlConnections"; 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_DETAILTARGET = "DetailTarget"; static const char* OPTION_PARCHECK = "ParCheck"; static const char* OPTION_PARREPAIR = "ParRepair"; static const char* OPTION_PARSCAN = "ParScan"; static const char* OPTION_PARRENAME = "ParRename"; static const char* OPTION_HEALTHCHECK = "HealthCheck"; static const char* OPTION_NZBPROCESS = "NZBProcess"; static const char* OPTION_NZBADDEDPROCESS = "NZBAddedProcess"; 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_DIRECTWRITE = "DirectWrite"; static const char* OPTION_WRITEBUFFERSIZE = "WriteBufferSize"; static const char* OPTION_NZBDIRINTERVAL = "NzbDirInterval"; static const char* OPTION_NZBDIRFILEAGE = "NzbDirFileAge"; static const char* OPTION_PARCLEANUPQUEUE = "ParCleanupQueue"; static const char* OPTION_DISKSPACE = "DiskSpace"; static const char* OPTION_DUMPCORE = "DumpCore"; static const char* OPTION_PARPAUSEQUEUE = "ParPauseQueue"; static const char* OPTION_SCRIPTPAUSEQUEUE = "ScriptPauseQueue"; static const char* OPTION_NZBCLEANUPDISK = "NzbCleanupDisk"; static const char* OPTION_DELETECLEANUPDISK = "DeleteCleanupDisk"; static const char* OPTION_PARTIMELIMIT = "ParTimeLimit"; static const char* OPTION_KEEPHISTORY = "KeepHistory"; static const char* OPTION_ACCURATERATE = "AccurateRate"; static const char* OPTION_UNPACK = "Unpack"; static const char* OPTION_UNPACKCLEANUPDISK = "UnpackCleanupDisk"; static const char* OPTION_UNRARCMD = "UnrarCmd"; static const char* OPTION_SEVENZIPCMD = "SevenZipCmd"; static const char* OPTION_UNPACKPAUSEQUEUE = "UnpackPauseQueue"; static const char* OPTION_SCRIPTORDER = "ScriptOrder"; static const char* OPTION_DEFSCRIPT = "DefScript"; static const char* OPTION_EXTCLEANUPDISK = "ExtCleanupDisk"; static const char* OPTION_FEEDHISTORY = "FeedHistory"; static const char* OPTION_URLFORCE = "UrlForce"; static const char* OPTION_TIMECORRECTION = "TimeCorrection"; // obsolete options static const char* OPTION_POSTLOGKIND = "PostLogKind"; static const char* OPTION_NZBLOGKIND = "NZBLogKind"; static const char* OPTION_RETRYONCRCERROR = "RetryOnCrcError"; static const char* OPTION_ALLOWREPROCESS = "AllowReProcess"; static const char* OPTION_POSTPROCESS = "PostProcess"; static const char* OPTION_LOADPARS = "LoadPars"; static const char* OPTION_THREADLIMIT = "ThreadLimit"; static const char* OPTION_PROCESSLOGKIND = "ProcessLogKind"; static const char* OPTION_APPENDNZBDIR = "AppendNzbDir"; static const char* OPTION_RENAMEBROKEN = "RenameBroken"; static const char* OPTION_MERGENZB = "MergeNzb"; static const char* OPTION_STRICTPARNAME = "StrictParName"; const char* BoolNames[] = { "yes", "no", "true", "false", "1", "0", "on", "off", "enable", "disable", "enabled", "disabled" }; const int BoolValues[] = { 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 }; const int BoolCount = 12; static const char* PPSCRIPT_SIGNATURE = "### NZBGET POST-PROCESSING SCRIPT"; #ifndef WIN32 const char* PossibleConfigLocations[] = { "~/.nzbget", "/etc/nzbget.conf", "/usr/etc/nzbget.conf", "/usr/local/etc/nzbget.conf", "/opt/etc/nzbget.conf", NULL }; #endif Options::OptEntry::OptEntry() { m_szName = NULL; m_szValue = NULL; m_szDefValue = NULL; m_iLineNo = 0; } Options::OptEntry::OptEntry(const char* szName, const char* szValue) { m_szName = strdup(szName); m_szValue = strdup(szValue); m_szDefValue = NULL; m_iLineNo = 0; } Options::OptEntry::~OptEntry() { free(m_szName); free(m_szValue); free(m_szDefValue); } void Options::OptEntry::SetName(const char* szName) { free(m_szName); m_szName = strdup(szName); } void Options::OptEntry::SetValue(const char* szValue) { free(m_szValue); m_szValue = strdup(szValue); if (!m_szDefValue) { m_szDefValue = strdup(szValue); } } Options::OptEntries::~OptEntries() { for (iterator it = begin(); it != end(); it++) { delete *it; } } Options::OptEntry* Options::OptEntries::FindOption(const char* szName) { if (!szName) { return NULL; } for (iterator it = begin(); it != end(); it++) { OptEntry* pOptEntry = *it; if (!strcasecmp(pOptEntry->GetName(), szName)) { return pOptEntry; } } return NULL; } Options::ConfigTemplate::ConfigTemplate(const char* szName, const char* szDisplayName, const char* szTemplate) { m_szName = strdup(szName); m_szDisplayName = strdup(szDisplayName); m_szTemplate = strdup(szTemplate ? szTemplate : ""); } Options::ConfigTemplate::~ConfigTemplate() { free(m_szName); free(m_szDisplayName); free(m_szTemplate); } Options::ConfigTemplates::~ConfigTemplates() { for (iterator it = begin(); it != end(); it++) { delete *it; } } Options::Category::Category(const char* szName, const char* szDestDir, bool bUnpack, const char* szDefScript) { m_szName = strdup(szName); m_szDestDir = szDestDir ? strdup(szDestDir) : NULL; m_bUnpack = bUnpack; m_szDefScript = szDefScript ? strdup(szDefScript) : NULL; } Options::Category::~Category() { free(m_szName); free(m_szDestDir); free(m_szDefScript); for (NameList::iterator it = m_Aliases.begin(); it != m_Aliases.end(); it++) { free(*it); } } Options::Categories::~Categories() { for (iterator it = begin(); it != end(); it++) { delete *it; } } Options::Category* Options::Categories::FindCategory(const char* szName, bool bSearchAliases) { if (!szName) { return NULL; } for (iterator it = begin(); it != end(); it++) { Category* pCategory = *it; if (!strcasecmp(pCategory->GetName(), szName)) { return pCategory; } } if (bSearchAliases) { for (iterator it = begin(); it != end(); it++) { Category* pCategory = *it; for (NameList::iterator it2 = pCategory->GetAliases()->begin(); it2 != pCategory->GetAliases()->end(); it2++) { const char* szAlias = *it2; WildMask mask(szAlias); if (mask.Match(szName)) { return pCategory; } } } } return NULL; } Options::Script::Script(const char* szName, const char* szLocation) { m_szName = strdup(szName); m_szLocation = strdup(szLocation); m_szDisplayName = strdup(szName); } Options::Script::~Script() { free(m_szName); free(m_szLocation); free(m_szDisplayName); } void Options::Script::SetDisplayName(const char* szDisplayName) { free(m_szDisplayName); m_szDisplayName = strdup(szDisplayName); } Options::ScriptList::~ScriptList() { for (iterator it = begin(); it != end(); it++) { delete *it; } } Options::Script* Options::ScriptList::Find(const char* szName) { for (iterator it = begin(); it != end(); it++) { Script* pScript = *it; if (!strcmp(pScript->GetName(), szName)) { return pScript; } } return NULL; } Options::Options(int argc, char* argv[]) { m_bConfigErrors = false; m_iConfigLine = 0; // initialize options with default values m_bConfigInitialized = false; m_szConfigFilename = NULL; m_szDestDir = NULL; m_szInterDir = NULL; m_szTempDir = NULL; m_szQueueDir = NULL; m_szNzbDir = NULL; m_szWebDir = NULL; m_szConfigTemplate = NULL; m_szScriptDir = NULL; m_eInfoTarget = mtScreen; m_eWarningTarget = mtScreen; m_eErrorTarget = mtScreen; m_eDebugTarget = mtScreen; m_eDetailTarget = mtScreen; m_bDecode = true; m_bPauseDownload = false; m_bPauseDownload2 = false; m_bPausePostProcess = false; m_bPauseScan = false; m_bCreateBrokenLog = false; m_bResetLog = false; m_iDownloadRate = 0; m_iEditQueueAction = 0; m_pEditQueueIDList = NULL; m_iEditQueueIDCount = 0; m_iEditQueueOffset = 0; m_szEditQueueText = NULL; m_szArgFilename = NULL; m_szLastArg = NULL; m_szAddCategory = NULL; m_iAddPriority = 0; m_szAddNZBFilename = NULL; m_bAddPaused = false; m_iConnectionTimeout = 0; m_iTerminateTimeout = 0; m_bServerMode = false; m_bDaemonMode = false; m_bRemoteClientMode = false; m_bPrintOptions = false; m_bAddTop = false; m_bAppendCategoryDir = false; m_bContinuePartial = false; m_bSaveQueue = false; m_bDupeCheck = false; m_iRetries = 0; m_iRetryInterval = 0; m_iControlPort = 0; m_szControlIP = NULL; m_szControlUsername = NULL; m_szControlPassword = NULL; m_bSecureControl = false; m_iSecurePort = 0; m_szSecureCert = NULL; m_szSecureKey = NULL; m_szAuthorizedIP = NULL; m_szLockFile = NULL; m_szDaemonUsername = NULL; m_eOutputMode = omLoggable; m_bReloadQueue = false; m_bReloadUrlQueue = false; m_bReloadPostQueue = false; m_iUrlConnections = 0; m_iLogBufferSize = 0; m_iLogLines = 0; m_iWriteLogKind = 0; m_bCreateLog = false; m_szLogFile = NULL; m_eParCheck = pcManual; m_bParRepair = false; m_eParScan = psLimited; m_bParRename = false; m_eHealthCheck = hcNone; m_szScriptOrder = NULL; m_szDefScript = NULL; m_szNZBProcess = NULL; m_szNZBAddedProcess = NULL; m_bNoConfig = false; m_iUMask = 0; m_iUpdateInterval = 0; m_bCursesNZBName = false; m_bCursesTime = false; m_bCursesGroup = false; m_bCrcCheck = false; m_bDirectWrite = false; m_iWriteBufferSize = 0; m_iNzbDirInterval = 0; m_iNzbDirFileAge = 0; m_bParCleanupQueue = false; m_iDiskSpace = 0; m_bTestBacktrace = false; m_bTLS = false; m_bDumpCore = false; m_bParPauseQueue = false; m_bScriptPauseQueue = false; m_bNzbCleanupDisk = false; m_bDeleteCleanupDisk = false; m_iParTimeLimit = 0; m_iKeepHistory = 0; m_bAccurateRate = false; m_EMatchMode = mmID; m_tResumeTime = 0; m_bUnpack = false; m_bUnpackCleanupDisk = false; m_szUnrarCmd = NULL; m_szSevenZipCmd = NULL; m_bUnpackPauseQueue = false; m_szExtCleanupDisk = NULL; m_iFeedHistory = 0; m_bUrlForce = false; m_iTimeCorrection = 0; // Option "ConfigFile" will be initialized later, but we want // to see it at the top of option list, so we add it first SetOption(OPTION_CONFIGFILE, ""); char szFilename[MAX_PATH + 1]; #ifdef WIN32 GetModuleFileName(NULL, szFilename, sizeof(szFilename)); #else Util::ExpandFileName(argv[0], szFilename, sizeof(szFilename)); #endif Util::NormalizePathSeparators(szFilename); SetOption(OPTION_APPBIN, szFilename); char* end = strrchr(szFilename, PATH_SEPARATOR); if (end) *end = '\0'; SetOption(OPTION_APPDIR, szFilename); SetOption(OPTION_VERSION, Util::VersionRevision()); InitDefault(); InitCommandLine(argc, argv); if (argc == 1) { PrintUsage(argv[0]); } if (!m_szConfigFilename && !m_bNoConfig) { if (argc == 1) { printf("\n"); } printf("No configuration-file found\n"); #ifdef WIN32 printf("Please put configuration-file \"nzbget.conf\" into the directory with exe-file\n"); #else printf("Please use option \"-c\" or put configuration-file in one of the following locations:\n"); int p = 0; while (const char* szFilename = PossibleConfigLocations[p++]) { printf("%s\n", szFilename); } #endif exit(-1); } if (argc == 1) { exit(-1); } InitOptions(); CheckOptions(); if (!m_bPrintOptions) { InitFileArg(argc, argv); } InitServers(); InitCategories(); InitScheduler(); InitFeeds(); if (m_bPrintOptions) { Dump(); exit(-1); } if (m_bConfigErrors && m_eClientOperation == opClientNoOperation) { info("Pausing all activities due to errors in configuration"); m_bPauseDownload = true; m_bPauseDownload2= true; m_bPausePostProcess = true; m_bPauseScan = true; } } Options::~Options() { free(m_szConfigFilename); free(m_szDestDir); free(m_szInterDir); free(m_szTempDir); free(m_szQueueDir); free(m_szNzbDir); free(m_szWebDir); free(m_szConfigTemplate); free(m_szScriptDir); free(m_szArgFilename); free(m_szAddCategory); free(m_szEditQueueText); free(m_szLastArg); free(m_szControlIP); free(m_szControlUsername); free(m_szControlPassword); free(m_szSecureCert); free(m_szSecureKey); free(m_szAuthorizedIP); free(m_szLogFile); free(m_szLockFile); free(m_szDaemonUsername); free(m_szScriptOrder); free(m_szDefScript); free(m_szNZBProcess); free(m_szNZBAddedProcess); free(m_pEditQueueIDList); free(m_szAddNZBFilename); free(m_szUnrarCmd); free(m_szSevenZipCmd); free(m_szExtCleanupDisk); for (NameList::iterator it = m_EditQueueNameList.begin(); it != m_EditQueueNameList.end(); it++) { free(*it); } m_EditQueueNameList.clear(); } void Options::Dump() { for (OptEntries::iterator it = m_OptEntries.begin(); it != m_OptEntries.end(); it++) { OptEntry* pOptEntry = *it; printf("%s = \"%s\"\n", pOptEntry->GetName(), pOptEntry->GetValue()); } } void Options::ConfigError(const char* msg, ...) { char tmp2[1024]; va_list ap; va_start(ap, msg); vsnprintf(tmp2, 1024, msg, ap); tmp2[1024-1] = '\0'; va_end(ap); printf("%s(%i): %s\n", Util::BaseFileName(m_szConfigFilename), m_iConfigLine, tmp2); error("%s(%i): %s", Util::BaseFileName(m_szConfigFilename), m_iConfigLine, tmp2); m_bConfigErrors = true; } void Options::ConfigWarn(const char* msg, ...) { char tmp2[1024]; va_list ap; va_start(ap, msg); vsnprintf(tmp2, 1024, msg, ap); tmp2[1024-1] = '\0'; va_end(ap); printf("%s(%i): %s\n", Util::BaseFileName(m_szConfigFilename), m_iConfigLine, tmp2); warn("%s(%i): %s", Util::BaseFileName(m_szConfigFilename), m_iConfigLine, tmp2); } void Options::LocateOptionSrcPos(const char *szOptionName) { OptEntry* pOptEntry = FindOption(szOptionName); if (pOptEntry) { m_iConfigLine = pOptEntry->GetLineNo(); } else { m_iConfigLine = 0; } } void Options::InitDefault() { #ifdef WIN32 SetOption(OPTION_MAINDIR, "${AppDir}"); #else SetOption(OPTION_MAINDIR, "~/downloads"); #endif SetOption(OPTION_TEMPDIR, "${MainDir}/tmp"); SetOption(OPTION_DESTDIR, "${MainDir}/dst"); SetOption(OPTION_INTERDIR, ""); SetOption(OPTION_QUEUEDIR, "${MainDir}/queue"); SetOption(OPTION_NZBDIR, "${MainDir}/nzb"); SetOption(OPTION_LOCKFILE, "${MainDir}/nzbget.lock"); SetOption(OPTION_LOGFILE, "${DestDir}/nzbget.log"); SetOption(OPTION_WEBDIR, ""); SetOption(OPTION_CONFIGTEMPLATE, ""); SetOption(OPTION_SCRIPTDIR, "${MainDir}/ppscripts"); SetOption(OPTION_CREATELOG, "yes"); SetOption(OPTION_APPENDCATEGORYDIR, "yes"); SetOption(OPTION_OUTPUTMODE, "curses"); SetOption(OPTION_DUPECHECK, "yes"); SetOption(OPTION_DOWNLOADRATE, "0"); SetOption(OPTION_CONTROLIP, "0.0.0.0"); SetOption(OPTION_CONTROLUSERNAME, "nzbget"); SetOption(OPTION_CONTROLPASSWORD, "tegbzn6789"); SetOption(OPTION_CONTROLPORT, "6789"); SetOption(OPTION_SECURECONTROL, "no"); SetOption(OPTION_SECUREPORT, "6791"); SetOption(OPTION_SECURECERT, ""); SetOption(OPTION_SECUREKEY, ""); SetOption(OPTION_AUTHORIZEDIP, ""); SetOption(OPTION_CONNECTIONTIMEOUT, "60"); SetOption(OPTION_SAVEQUEUE, "yes"); SetOption(OPTION_RELOADQUEUE, "yes"); SetOption(OPTION_RELOADURLQUEUE, "yes"); SetOption(OPTION_RELOADPOSTQUEUE, "yes"); SetOption(OPTION_CREATEBROKENLOG, "yes"); SetOption(OPTION_RESETLOG, "no"); SetOption(OPTION_DECODE, "yes"); SetOption(OPTION_RETRIES, "3"); SetOption(OPTION_RETRYINTERVAL, "10"); SetOption(OPTION_TERMINATETIMEOUT, "600"); SetOption(OPTION_CONTINUEPARTIAL, "no"); SetOption(OPTION_URLCONNECTIONS, "4"); SetOption(OPTION_LOGBUFFERSIZE, "1000"); SetOption(OPTION_INFOTARGET, "both"); SetOption(OPTION_WARNINGTARGET, "both"); SetOption(OPTION_ERRORTARGET, "both"); SetOption(OPTION_DEBUGTARGET, "none"); SetOption(OPTION_DETAILTARGET, "both"); SetOption(OPTION_PARCHECK, "auto"); SetOption(OPTION_PARREPAIR, "yes"); SetOption(OPTION_PARSCAN, "limited"); SetOption(OPTION_PARRENAME, "yes"); SetOption(OPTION_HEALTHCHECK, "none"); SetOption(OPTION_SCRIPTORDER, ""); SetOption(OPTION_DEFSCRIPT, ""); SetOption(OPTION_NZBPROCESS, ""); SetOption(OPTION_NZBADDEDPROCESS, ""); 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_DIRECTWRITE, "yes"); SetOption(OPTION_WRITEBUFFERSIZE, "0"); SetOption(OPTION_NZBDIRINTERVAL, "5"); SetOption(OPTION_NZBDIRFILEAGE, "60"); SetOption(OPTION_PARCLEANUPQUEUE, "yes"); SetOption(OPTION_DISKSPACE, "250"); SetOption(OPTION_DUMPCORE, "no"); SetOption(OPTION_PARPAUSEQUEUE, "no"); SetOption(OPTION_SCRIPTPAUSEQUEUE, "no"); SetOption(OPTION_NZBCLEANUPDISK, "no"); SetOption(OPTION_DELETECLEANUPDISK, "no"); SetOption(OPTION_PARTIMELIMIT, "0"); SetOption(OPTION_KEEPHISTORY, "7"); SetOption(OPTION_ACCURATERATE, "no"); SetOption(OPTION_UNPACK, "no"); SetOption(OPTION_UNPACKCLEANUPDISK, "no"); #ifdef WIN32 SetOption(OPTION_UNRARCMD, "unrar.exe"); SetOption(OPTION_SEVENZIPCMD, "7z.exe"); #else SetOption(OPTION_UNRARCMD, "unrar"); SetOption(OPTION_SEVENZIPCMD, "7z"); #endif SetOption(OPTION_UNPACKPAUSEQUEUE, "no"); SetOption(OPTION_EXTCLEANUPDISK, ""); SetOption(OPTION_FEEDHISTORY, "7"); SetOption(OPTION_URLFORCE, "yes"); SetOption(OPTION_TIMECORRECTION, "0"); } void Options::InitOptFile() { if (m_bConfigInitialized) { return; } if (!m_szConfigFilename && !m_bNoConfig) { // search for config file in default locations #ifdef WIN32 char szFilename[MAX_PATH + 1]; GetModuleFileName(NULL, szFilename, MAX_PATH + 1); szFilename[MAX_PATH] = '\0'; Util::NormalizePathSeparators(szFilename); char* end = strrchr(szFilename, PATH_SEPARATOR); if (end) end[1] = '\0'; strcat(szFilename, "nzbget.conf"); if (Util::FileExists(szFilename)) { m_szConfigFilename = strdup(szFilename); } #else int p = 0; while (const char* szFilename = PossibleConfigLocations[p++]) { // substitute HOME-variable char szExpandedFilename[1024]; if (Util::ExpandHomePath(szFilename, szExpandedFilename, sizeof(szExpandedFilename))) { szFilename = szExpandedFilename; } if (Util::FileExists(szFilename)) { m_szConfigFilename = strdup(szFilename); break; } } #endif } if (m_szConfigFilename) { // normalize path in filename char szFilename[MAX_PATH + 1]; Util::ExpandFileName(m_szConfigFilename, szFilename, sizeof(szFilename)); szFilename[MAX_PATH] = '\0'; #ifndef WIN32 // substitute HOME-variable char szExpandedFilename[1024]; if (Util::ExpandHomePath(szFilename, szExpandedFilename, sizeof(szExpandedFilename))) { strncpy(szFilename, szExpandedFilename, sizeof(szFilename)); } #endif free(m_szConfigFilename); m_szConfigFilename = strdup(szFilename); SetOption(OPTION_CONFIGFILE, m_szConfigFilename); LoadConfigFile(); } m_bConfigInitialized = true; } void Options::CheckDir(char** dir, const char* szOptionName, bool bAllowEmpty, bool bCreate) { char* usedir = NULL; const char* tempdir = GetOption(szOptionName); if (tempdir && strlen(tempdir) > 0) { int len = strlen(tempdir); usedir = (char*) malloc(len + 2); strcpy(usedir, tempdir); char ch = usedir[len-1]; if (ch == ALT_PATH_SEPARATOR) { usedir[len-1] = PATH_SEPARATOR; } else if (ch != PATH_SEPARATOR) { usedir[len] = PATH_SEPARATOR; usedir[len + 1] = '\0'; } Util::NormalizePathSeparators(usedir); } else { if (!bAllowEmpty) { ConfigError("Invalid value for option \"%s\": ", szOptionName); } *dir = strdup(""); return; } // Ensure the dir is created char szErrBuf[1024]; if (bCreate && !Util::ForceDirectories(usedir, szErrBuf, sizeof(szErrBuf))) { ConfigError("Invalid value for option \"%s\" (%s): %s", szOptionName, usedir, szErrBuf); } *dir = usedir; } void Options::InitOptions() { CheckDir(&m_szDestDir, OPTION_DESTDIR, false, false); CheckDir(&m_szInterDir, OPTION_INTERDIR, true, true); CheckDir(&m_szTempDir, OPTION_TEMPDIR, false, true); CheckDir(&m_szQueueDir, OPTION_QUEUEDIR, false, true); CheckDir(&m_szWebDir, OPTION_WEBDIR, true, false); CheckDir(&m_szScriptDir, OPTION_SCRIPTDIR, true, false); m_szConfigTemplate = strdup(GetOption(OPTION_CONFIGTEMPLATE)); m_szScriptOrder = strdup(GetOption(OPTION_SCRIPTORDER)); m_szDefScript = strdup(GetOption(OPTION_DEFSCRIPT)); m_szNZBProcess = strdup(GetOption(OPTION_NZBPROCESS)); m_szNZBAddedProcess = strdup(GetOption(OPTION_NZBADDEDPROCESS)); m_szControlIP = strdup(GetOption(OPTION_CONTROLIP)); m_szControlUsername = strdup(GetOption(OPTION_CONTROLUSERNAME)); m_szControlPassword = strdup(GetOption(OPTION_CONTROLPASSWORD)); m_szSecureCert = strdup(GetOption(OPTION_SECURECERT)); m_szSecureKey = strdup(GetOption(OPTION_SECUREKEY)); m_szAuthorizedIP = strdup(GetOption(OPTION_AUTHORIZEDIP)); m_szLockFile = strdup(GetOption(OPTION_LOCKFILE)); m_szDaemonUsername = strdup(GetOption(OPTION_DAEMONUSERNAME)); m_szLogFile = strdup(GetOption(OPTION_LOGFILE)); m_szUnrarCmd = strdup(GetOption(OPTION_UNRARCMD)); m_szSevenZipCmd = strdup(GetOption(OPTION_SEVENZIPCMD)); m_szExtCleanupDisk = strdup(GetOption(OPTION_EXTCLEANUPDISK)); m_iDownloadRate = (int)(ParseFloatValue(OPTION_DOWNLOADRATE) * 1024); m_iConnectionTimeout = ParseIntValue(OPTION_CONNECTIONTIMEOUT, 10); m_iTerminateTimeout = ParseIntValue(OPTION_TERMINATETIMEOUT, 10); m_iRetries = ParseIntValue(OPTION_RETRIES, 10); m_iRetryInterval = ParseIntValue(OPTION_RETRYINTERVAL, 10); m_iControlPort = ParseIntValue(OPTION_CONTROLPORT, 10); m_iSecurePort = ParseIntValue(OPTION_SECUREPORT, 10); m_iUrlConnections = ParseIntValue(OPTION_URLCONNECTIONS, 10); m_iLogBufferSize = ParseIntValue(OPTION_LOGBUFFERSIZE, 10); m_iUMask = ParseIntValue(OPTION_UMASK, 8); m_iUpdateInterval = ParseIntValue(OPTION_UPDATEINTERVAL, 10); m_iWriteBufferSize = ParseIntValue(OPTION_WRITEBUFFERSIZE, 10); m_iNzbDirInterval = ParseIntValue(OPTION_NZBDIRINTERVAL, 10); m_iNzbDirFileAge = ParseIntValue(OPTION_NZBDIRFILEAGE, 10); m_iDiskSpace = ParseIntValue(OPTION_DISKSPACE, 10); m_iParTimeLimit = ParseIntValue(OPTION_PARTIMELIMIT, 10); m_iKeepHistory = ParseIntValue(OPTION_KEEPHISTORY, 10); m_iFeedHistory = ParseIntValue(OPTION_FEEDHISTORY, 10); m_iTimeCorrection = ParseIntValue(OPTION_TIMECORRECTION, 10); if (-24 <= m_iTimeCorrection && m_iTimeCorrection <= 24) { m_iTimeCorrection *= 60; } m_iTimeCorrection *= 60; CheckDir(&m_szNzbDir, OPTION_NZBDIR, m_iNzbDirInterval == 0, true); m_bCreateBrokenLog = (bool)ParseEnumValue(OPTION_CREATEBROKENLOG, BoolCount, BoolNames, BoolValues); m_bResetLog = (bool)ParseEnumValue(OPTION_RESETLOG, BoolCount, BoolNames, BoolValues); m_bAppendCategoryDir = (bool)ParseEnumValue(OPTION_APPENDCATEGORYDIR, BoolCount, BoolNames, BoolValues); m_bContinuePartial = (bool)ParseEnumValue(OPTION_CONTINUEPARTIAL, BoolCount, BoolNames, BoolValues); m_bSaveQueue = (bool)ParseEnumValue(OPTION_SAVEQUEUE, BoolCount, BoolNames, BoolValues); m_bDupeCheck = (bool)ParseEnumValue(OPTION_DUPECHECK, BoolCount, BoolNames, BoolValues); m_bCreateLog = (bool)ParseEnumValue(OPTION_CREATELOG, BoolCount, BoolNames, BoolValues); m_bParRepair = (bool)ParseEnumValue(OPTION_PARREPAIR, BoolCount, BoolNames, BoolValues); m_bParRename = (bool)ParseEnumValue(OPTION_PARRENAME, BoolCount, BoolNames, BoolValues); m_bReloadQueue = (bool)ParseEnumValue(OPTION_RELOADQUEUE, BoolCount, BoolNames, BoolValues); m_bReloadUrlQueue = (bool)ParseEnumValue(OPTION_RELOADURLQUEUE, BoolCount, BoolNames, BoolValues); m_bReloadPostQueue = (bool)ParseEnumValue(OPTION_RELOADPOSTQUEUE, BoolCount, BoolNames, BoolValues); m_bCursesNZBName = (bool)ParseEnumValue(OPTION_CURSESNZBNAME, BoolCount, BoolNames, BoolValues); m_bCursesTime = (bool)ParseEnumValue(OPTION_CURSESTIME, BoolCount, BoolNames, BoolValues); m_bCursesGroup = (bool)ParseEnumValue(OPTION_CURSESGROUP, BoolCount, BoolNames, BoolValues); m_bCrcCheck = (bool)ParseEnumValue(OPTION_CRCCHECK, BoolCount, BoolNames, BoolValues); m_bDirectWrite = (bool)ParseEnumValue(OPTION_DIRECTWRITE, BoolCount, BoolNames, BoolValues); m_bParCleanupQueue = (bool)ParseEnumValue(OPTION_PARCLEANUPQUEUE, BoolCount, BoolNames, BoolValues); m_bDecode = (bool)ParseEnumValue(OPTION_DECODE, BoolCount, BoolNames, BoolValues); m_bDumpCore = (bool)ParseEnumValue(OPTION_DUMPCORE, BoolCount, BoolNames, BoolValues); m_bParPauseQueue = (bool)ParseEnumValue(OPTION_PARPAUSEQUEUE, BoolCount, BoolNames, BoolValues); m_bScriptPauseQueue = (bool)ParseEnumValue(OPTION_SCRIPTPAUSEQUEUE, BoolCount, BoolNames, BoolValues); m_bNzbCleanupDisk = (bool)ParseEnumValue(OPTION_NZBCLEANUPDISK, BoolCount, BoolNames, BoolValues); m_bDeleteCleanupDisk = (bool)ParseEnumValue(OPTION_DELETECLEANUPDISK, BoolCount, BoolNames, BoolValues); m_bAccurateRate = (bool)ParseEnumValue(OPTION_ACCURATERATE, BoolCount, BoolNames, BoolValues); m_bSecureControl = (bool)ParseEnumValue(OPTION_SECURECONTROL, BoolCount, BoolNames, BoolValues); m_bUnpack = (bool)ParseEnumValue(OPTION_UNPACK, BoolCount, BoolNames, BoolValues); m_bUnpackCleanupDisk = (bool)ParseEnumValue(OPTION_UNPACKCLEANUPDISK, BoolCount, BoolNames, BoolValues); m_bUnpackPauseQueue = (bool)ParseEnumValue(OPTION_UNPACKPAUSEQUEUE, BoolCount, BoolNames, BoolValues); m_bUrlForce = (bool)ParseEnumValue(OPTION_URLFORCE, BoolCount, BoolNames, BoolValues); const char* OutputModeNames[] = { "loggable", "logable", "log", "colored", "color", "ncurses", "curses" }; const int OutputModeValues[] = { omLoggable, omLoggable, omLoggable, omColored, omColored, omNCurses, omNCurses }; const int OutputModeCount = 7; m_eOutputMode = (EOutputMode)ParseEnumValue(OPTION_OUTPUTMODE, OutputModeCount, OutputModeNames, OutputModeValues); const char* ParCheckNames[] = { "auto", "force", "manual", "yes", "no" }; // yes/no for compatibility with older versions const int ParCheckValues[] = { pcAuto, pcForce, pcManual, pcForce, pcAuto }; const int ParCheckCount = 5; m_eParCheck = (EParCheck)ParseEnumValue(OPTION_PARCHECK, ParCheckCount, ParCheckNames, ParCheckValues); const char* ParScanNames[] = { "limited", "full", "auto" }; const int ParScanValues[] = { psLimited, psFull, psAuto }; const int ParScanCount = 3; m_eParScan = (EParScan)ParseEnumValue(OPTION_PARSCAN, ParScanCount, ParScanNames, ParScanValues); const char* HealthCheckNames[] = { "pause", "delete", "none" }; const int HealthCheckValues[] = { hcPause, hcDelete, hcNone }; const int HealthCheckCount = 3; m_eHealthCheck = (EHealthCheck)ParseEnumValue(OPTION_HEALTHCHECK, HealthCheckCount, HealthCheckNames, HealthCheckValues); const char* TargetNames[] = { "screen", "log", "both", "none" }; const int TargetValues[] = { mtScreen, mtLog, mtBoth, mtNone }; const int TargetCount = 4; m_eInfoTarget = (EMessageTarget)ParseEnumValue(OPTION_INFOTARGET, TargetCount, TargetNames, TargetValues); m_eWarningTarget = (EMessageTarget)ParseEnumValue(OPTION_WARNINGTARGET, TargetCount, TargetNames, TargetValues); m_eErrorTarget = (EMessageTarget)ParseEnumValue(OPTION_ERRORTARGET, TargetCount, TargetNames, TargetValues); m_eDebugTarget = (EMessageTarget)ParseEnumValue(OPTION_DEBUGTARGET, TargetCount, TargetNames, TargetValues); m_eDetailTarget = (EMessageTarget)ParseEnumValue(OPTION_DETAILTARGET, TargetCount, TargetNames, TargetValues); } int Options::ParseEnumValue(const char* OptName, int argc, const char * argn[], const int argv[]) { OptEntry* pOptEntry = FindOption(OptName); if (!pOptEntry) { ConfigError("Undefined value for option \"%s\"", OptName); return argv[0]; //abort("FATAL ERROR: Undefined value for option \"%s\"\n", OptName); } int iDefNum = 0; for (int i = 0; i < argc; i++) { if (!strcasecmp(pOptEntry->GetValue(), argn[i])) { // normalizing option value in option list, for example "NO" -> "no" for (int j = 0; j < argc; j++) { if (argv[j] == argv[i]) { if (strcmp(argn[j], pOptEntry->GetValue())) { pOptEntry->SetValue(argn[j]); } break; } } return argv[i]; } if (!strcasecmp(pOptEntry->GetDefValue(), argn[i])) { iDefNum = i; } } m_iConfigLine = pOptEntry->GetLineNo(); ConfigError("Invalid value for option \"%s\": \"%s\"", OptName, pOptEntry->GetValue()); pOptEntry->SetValue(argn[iDefNum]); return argv[iDefNum]; } int Options::ParseIntValue(const char* OptName, int iBase) { OptEntry* pOptEntry = FindOption(OptName); if (!pOptEntry) { abort("FATAL ERROR: Undefined value for option \"%s\"\n", OptName); } char *endptr; int val = strtol(pOptEntry->GetValue(), &endptr, iBase); if (endptr && *endptr != '\0') { m_iConfigLine = pOptEntry->GetLineNo(); ConfigError("Invalid value for option \"%s\": \"%s\"", OptName, pOptEntry->GetValue()); pOptEntry->SetValue(pOptEntry->GetDefValue()); val = strtol(pOptEntry->GetDefValue(), NULL, iBase); } return val; } float Options::ParseFloatValue(const char* OptName) { OptEntry* pOptEntry = FindOption(OptName); if (!pOptEntry) { abort("FATAL ERROR: Undefined value for option \"%s\"\n", OptName); } char *endptr; float val = (float)strtod(pOptEntry->GetValue(), &endptr); if (endptr && *endptr != '\0') { m_iConfigLine = pOptEntry->GetLineNo(); ConfigError("Invalid value for option \"%s\": \"%s\"", OptName, pOptEntry->GetValue()); pOptEntry->SetValue(pOptEntry->GetDefValue()); val = (float)strtod(pOptEntry->GetDefValue(), NULL); } return val; } void Options::InitCommandLine(int argc, char* argv[]) { m_eClientOperation = opClientNoOperation; // default // reset getopt optind = 1; while (true) { 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; break; case 'h': PrintUsage(argv[0]); exit(0); break; case 'v': printf("nzbget version: %s\n", Util::VersionRevision()); exit(1); break; case 'p': m_bPrintOptions = true; break; case 'o': InitOptFile(); if (!SetOptionString(optarg)) { abort("FATAL ERROR: error in option \"%s\"\n", optarg); } break; case 's': m_bServerMode = true; break; case 'D': m_bServerMode = true; m_bDaemonMode = true; break; case 'A': m_eClientOperation = opClientRequestDownload; // default while (true) { optind++; optarg = optind > argc ? NULL : argv[optind-1]; if (optarg && !strcasecmp(optarg, "F")) { m_eClientOperation = opClientRequestDownload; } else if (optarg && !strcasecmp(optarg, "U")) { m_eClientOperation = opClientRequestDownloadUrl; } else if (optarg && !strcasecmp(optarg, "T")) { m_bAddTop = true; } else if (optarg && !strcasecmp(optarg, "P")) { m_bAddPaused = true; } else if (optarg && !strcasecmp(optarg, "I")) { optind++; if (optind > argc) { abort("FATAL ERROR: Could not parse value of option 'A'\n"); } m_iAddPriority = atoi(argv[optind-1]); } else if (optarg && !strcasecmp(optarg, "C")) { optind++; if (optind > argc) { abort("FATAL ERROR: Could not parse value of option 'A'\n"); } free(m_szAddCategory); m_szAddCategory = strdup(argv[optind-1]); } else if (optarg && !strcasecmp(optarg, "N")) { optind++; if (optind > argc) { abort("FATAL ERROR: Could not parse value of option 'A'\n"); } free(m_szAddNZBFilename); m_szAddNZBFilename = strdup(argv[optind-1]); } else { optind--; break; } } break; case 'L': optind++; optarg = optind > argc ? NULL : argv[optind-1]; if (!optarg || !strncmp(optarg, "-", 1)) { m_eClientOperation = opClientRequestListFiles; optind--; } else if (!strcasecmp(optarg, "F") || !strcasecmp(optarg, "FR")) { m_eClientOperation = opClientRequestListFiles; } else if (!strcasecmp(optarg, "G") || !strcasecmp(optarg, "GR")) { m_eClientOperation = opClientRequestListGroups; } else if (!strcasecmp(optarg, "O")) { m_eClientOperation = opClientRequestPostQueue; } else if (!strcasecmp(optarg, "S")) { m_eClientOperation = opClientRequestListStatus; } else if (!strcasecmp(optarg, "H")) { m_eClientOperation = opClientRequestHistory; } else if (!strcasecmp(optarg, "U")) { m_eClientOperation = opClientRequestUrlQueue; } else { abort("FATAL ERROR: Could not parse value of option 'L'\n"); } if (optarg && (!strcasecmp(optarg, "FR") || !strcasecmp(optarg, "GR"))) { m_EMatchMode = mmRegEx; optind++; if (optind > argc) { abort("FATAL ERROR: Could not parse value of option 'L'\n"); } m_szEditQueueText = strdup(argv[optind-1]); } break; case 'P': case 'U': optind++; optarg = optind > argc ? NULL : argv[optind-1]; if (!optarg || !strncmp(optarg, "-", 1)) { m_eClientOperation = c == 'P' ? opClientRequestDownloadPause : opClientRequestDownloadUnpause; optind--; } else if (!strcasecmp(optarg, "D")) { m_eClientOperation = c == 'P' ? opClientRequestDownloadPause : opClientRequestDownloadUnpause; } else if (!strcasecmp(optarg, "D2")) { m_eClientOperation = c == 'P' ? opClientRequestDownload2Pause : opClientRequestDownload2Unpause; } else if (!strcasecmp(optarg, "O")) { m_eClientOperation = c == 'P' ? opClientRequestPostPause : opClientRequestPostUnpause; } else if (!strcasecmp(optarg, "S")) { m_eClientOperation = c == 'P' ? opClientRequestScanPause : opClientRequestScanUnpause; } else { abort("FATAL ERROR: Could not parse value of option '%c'\n", c); } break; case 'R': m_eClientOperation = opClientRequestSetRate; m_iSetRate = (int)(atof(optarg)*1024); break; case 'B': if (!strcasecmp(optarg, "dump")) { m_eClientOperation = opClientRequestDumpDebug; } else if (!strcasecmp(optarg, "trace")) { m_bTestBacktrace = true; } else { abort("FATAL ERROR: Could not parse value of option 'B'\n"); } break; case 'G': m_eClientOperation = opClientRequestLog; m_iLogLines = atoi(optarg); if (m_iLogLines == 0) { abort("FATAL ERROR: Could not parse value of option 'G'\n"); } break; case 'T': m_bAddTop = true; break; case 'C': m_bRemoteClientMode = true; break; case 'E': { m_eClientOperation = opClientRequestEditQueue; bool bGroup = !strcasecmp(optarg, "G") || !strcasecmp(optarg, "GN") || !strcasecmp(optarg, "GR"); bool bFile = !strcasecmp(optarg, "F") || !strcasecmp(optarg, "FN") || !strcasecmp(optarg, "FR"); if (!strcasecmp(optarg, "GN") || !strcasecmp(optarg, "FN")) { m_EMatchMode = mmName; } else if (!strcasecmp(optarg, "GR") || !strcasecmp(optarg, "FR")) { m_EMatchMode = mmRegEx; } else { m_EMatchMode = mmID; }; bool bPost = !strcasecmp(optarg, "O"); bool bHistory = !strcasecmp(optarg, "H"); if (bGroup || bFile || bPost || bHistory) { optind++; if (optind > argc) { abort("FATAL ERROR: Could not parse value of option 'E'\n"); } optarg = argv[optind-1]; } if (bPost) { // edit-commands for post-processor-queue if (!strcasecmp(optarg, "T")) { m_iEditQueueAction = eRemoteEditActionPostMoveTop; } else if (!strcasecmp(optarg, "B")) { m_iEditQueueAction = eRemoteEditActionPostMoveBottom; } else if (!strcasecmp(optarg, "D")) { m_iEditQueueAction = eRemoteEditActionPostDelete; } else { m_iEditQueueOffset = atoi(optarg); if (m_iEditQueueOffset == 0) { abort("FATAL ERROR: Could not parse value of option 'E'\n"); } m_iEditQueueAction = eRemoteEditActionPostMoveOffset; } } else if (bHistory) { // edit-commands for history if (!strcasecmp(optarg, "D")) { m_iEditQueueAction = eRemoteEditActionHistoryDelete; } else if (!strcasecmp(optarg, "R")) { m_iEditQueueAction = eRemoteEditActionHistoryReturn; } else if (!strcasecmp(optarg, "P")) { m_iEditQueueAction = eRemoteEditActionHistoryProcess; } else if (!strcasecmp(optarg, "A")) { m_iEditQueueAction = eRemoteEditActionHistoryRedownload; } else if (!strcasecmp(optarg, "O")) { m_iEditQueueAction = eRemoteEditActionHistorySetParameter; optind++; if (optind > argc) { abort("FATAL ERROR: Could not parse value of option 'E'\n"); } m_szEditQueueText = strdup(argv[optind-1]); if (!strchr(m_szEditQueueText, '=')) { abort("FATAL ERROR: Could not parse value of option 'E'\n"); } } else if (!strcasecmp(optarg, "B")) { m_iEditQueueAction = eRemoteEditActionHistoryMarkBad; } else if (!strcasecmp(optarg, "G")) { m_iEditQueueAction = eRemoteEditActionHistoryMarkGood; } else { abort("FATAL ERROR: Could not parse value of option 'E'\n"); } } else { // edit-commands for download-queue if (!strcasecmp(optarg, "T")) { m_iEditQueueAction = bGroup ? eRemoteEditActionGroupMoveTop : eRemoteEditActionFileMoveTop; } else if (!strcasecmp(optarg, "B")) { m_iEditQueueAction = bGroup ? eRemoteEditActionGroupMoveBottom : eRemoteEditActionFileMoveBottom; } else if (!strcasecmp(optarg, "P")) { 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 = bGroup ? eRemoteEditActionGroupResume : eRemoteEditActionFileResume; } else if (!strcasecmp(optarg, "D")) { m_iEditQueueAction = bGroup ? eRemoteEditActionGroupDelete : eRemoteEditActionFileDelete; } else if (!strcasecmp(optarg, "C") || !strcasecmp(optarg, "K")) { // switch "K" is provided for compatibility with v. 0.8.0 and can be removed in future versions if (!bGroup) { abort("FATAL ERROR: Category can be set only for groups\n"); } m_iEditQueueAction = eRemoteEditActionGroupSetCategory; optind++; if (optind > argc) { abort("FATAL ERROR: Could not parse value of option 'E'\n"); } m_szEditQueueText = strdup(argv[optind-1]); } else if (!strcasecmp(optarg, "N")) { if (!bGroup) { abort("FATAL ERROR: Only groups can be renamed\n"); } m_iEditQueueAction = eRemoteEditActionGroupSetName; optind++; if (optind > argc) { abort("FATAL ERROR: Could not parse value of option 'E'\n"); } m_szEditQueueText = strdup(argv[optind-1]); } else if (!strcasecmp(optarg, "M")) { if (!bGroup) { abort("FATAL ERROR: Only groups can be merged\n"); } m_iEditQueueAction = eRemoteEditActionGroupMerge; } else if (!strcasecmp(optarg, "S")) { m_iEditQueueAction = eRemoteEditActionFileSplit; optind++; if (optind > argc) { abort("FATAL ERROR: Could not parse value of option 'E'\n"); } m_szEditQueueText = strdup(argv[optind-1]); } else if (!strcasecmp(optarg, "O")) { if (!bGroup) { abort("FATAL ERROR: Post-process parameter can be set only for groups\n"); } m_iEditQueueAction = eRemoteEditActionGroupSetParameter; optind++; if (optind > argc) { abort("FATAL ERROR: Could not parse value of option 'E'\n"); } m_szEditQueueText = strdup(argv[optind-1]); if (!strchr(m_szEditQueueText, '=')) { abort("FATAL ERROR: Could not parse value of option 'E'\n"); } } else if (!strcasecmp(optarg, "I")) { m_iEditQueueAction = bGroup ? eRemoteEditActionGroupSetPriority : eRemoteEditActionFileSetPriority; optind++; if (optind > argc) { abort("FATAL ERROR: Could not parse value of option 'E'\n"); } m_szEditQueueText = strdup(argv[optind-1]); if (atoi(m_szEditQueueText) == 0 && strcmp("0", m_szEditQueueText)) { abort("FATAL ERROR: Could not parse value of option 'E'\n"); } } else { m_iEditQueueOffset = atoi(optarg); if (m_iEditQueueOffset == 0) { abort("FATAL ERROR: Could not parse value of option 'E'\n"); } m_iEditQueueAction = bGroup ? eRemoteEditActionGroupMoveOffset : eRemoteEditActionFileMoveOffset; } } break; } case 'Q': m_eClientOperation = opClientRequestShutdown; break; case 'O': m_eClientOperation = opClientRequestReload; break; case 'V': m_eClientOperation = opClientRequestVersion; break; case 'W': m_eClientOperation = opClientRequestWriteLog; if (!strcasecmp(optarg, "I")) { m_iWriteLogKind = (int)Message::mkInfo; } else if (!strcasecmp(optarg, "W")) { m_iWriteLogKind = (int)Message::mkWarning; } else if (!strcasecmp(optarg, "E")) { m_iWriteLogKind = (int)Message::mkError; } else if (!strcasecmp(optarg, "D")) { m_iWriteLogKind = (int)Message::mkDetail; } else if (!strcasecmp(optarg, "G")) { m_iWriteLogKind = (int)Message::mkDebug; } else { abort("FATAL ERROR: Could not parse value of option 'W'\n"); } break; case 'K': // switch "K" is provided for compatibility with v. 0.8.0 and can be removed in future versions free(m_szAddCategory); m_szAddCategory = strdup(optarg); break; case 'S': optind++; optarg = optind > argc ? NULL : argv[optind-1]; if (!optarg || !strncmp(optarg, "-", 1)) { m_eClientOperation = opClientRequestScanAsync; optind--; } else if (!strcasecmp(optarg, "W")) { m_eClientOperation = opClientRequestScanSync; } else { abort("FATAL ERROR: Could not parse value of option '%c'\n", c); } break; case '?': exit(-1); break; } } if (m_bServerMode && (m_eClientOperation == opClientRequestDownloadPause || m_eClientOperation == opClientRequestDownload2Pause)) { m_bPauseDownload = m_eClientOperation == opClientRequestDownloadPause; m_bPauseDownload2 = m_eClientOperation == opClientRequestDownload2Pause; m_eClientOperation = opClientNoOperation; } InitOptFile(); } void Options::PrintUsage(char* com) { printf("Usage:\n" " %s [switches]\n\n" "Switches:\n" " -h, --help Print this help-message\n" " -v, --version Print version and exit\n" " -c, --configfile 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 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 server\n" " -O, --reload Reload config and restart all services\n" " -A, --append [F|U] [] Send file/url to server's\n" " download queue\n" " F Send file (default)\n" " U Send url\n" " are (multiple options must be separated with space):\n" " T Add file to the top (beginning) of queue\n" " P Pause added files\n" " C Assign category to nzb-file\n" " N Use this name as nzb-filename (only for URLs)\n" " I Set priority (signed integer)\n" " -C, --connect Attach client to server\n" " -L, --list [F|FR|G|GR|O|U|H|S] [RegEx] Request list of items from server\n" " F List individual files and server status (default)\n" " FR Like \"F\" but apply regular expression filter\n" " G List groups (nzb-files) and server status\n" " GR Like \"G\" but apply regular expression filter\n" " O List post-processor-queue\n" " U List url-queue\n" " H List history\n" " S Print only server status\n" " Regular expression (only with options \"FR\", \"GR\")\n" " using POSIX Extended Regular Expression Syntax\n" " -P, --pause [D|D2|O|S] Pause server\n" " D Pause download queue (default)\n" " D2 Pause download queue via second pause-register\n" " O Pause post-processor queue\n" " S Pause scan of incoming nzb-directory\n" " -U, --unpause [D|D2|O|S] Unpause server\n" " D Unpause download queue (default)\n" " D2 Unpause download queue via second pause-register\n" " O Unpause post-processor queue\n" " S Unpause scan of incoming nzb-directory\n" " -R, --rate Set download rate on server, in KB/s\n" " -G, --log Request last lines from server's screen-log\n" " -W, --write \"Text\" Send text to server's log\n" " -S, --scan [W] Scan incoming nzb-directory on server\n" " W Wait until scan completes (synchronous mode)\n" " -E, --edit [F|FN|FR|G|GN|GR|O|H] Edit items\n" " on server\n" " F Edit individual files (default)\n" " FN Like \"F\" but uses names (as \"group/file\")\n" " instead of IDs\n" " FR Like \"FN\" but with regular expressions\n" " G Edit all files in the group (same nzb-file)\n" " GN Like \"G\" but uses group names instead of IDs\n" " GR Like \"GN\" but with regular expressions\n" " O Edit post-processor-queue\n" " H Edit history\n" " is one of:\n" " - for files (F), groups (G) and post-jobs (O):\n" " <+offset|-offset> Move in queue relative to current position,\n" " offset is an integer value\n" " T Move to top of queue\n" " B Move to bottom of queue\n" " D Delete\n" " - for files (F) and groups (G):\n" " P Pause\n" " U Resume (unpause)\n" " I Set priority (signed integer)\n" " - for groups (G):\n" " A Pause all pars\n" " R Pause extra pars\n" " C Set category\n" " N Rename\n" " M Merge\n" " S Split - create new group from selected files\n" " O = Set post-process parameter\n" " - for history (H):\n" " D Delete\n" " P Post-process again\n" " R Download remaining files\n" " A Download again\n" " O = Set post-process parameter\n" " B Mark as bad\n" " G Mark as good\n" " Comma-separated list of file-ids or ranges\n" " of file-ids, e. g.: 1-5,3,10-22\n" " List of names (with options \"FN\" and \"GN\"),\n" " e. g.: \"my nzb download%cmyfile.nfo\" \"another nzb\"\n" " List of regular expressions (options \"FR\", \"GR\")\n" " using POSIX Extended Regular Expression Syntax", Util::BaseFileName(com), PATH_SEPARATOR); } void Options::InitFileArg(int argc, char* argv[]) { if (optind >= argc) { // no nzb-file passed if (!m_bServerMode && !m_bRemoteClientMode && (m_eClientOperation == opClientNoOperation || m_eClientOperation == opClientRequestDownload || m_eClientOperation == opClientRequestWriteLog)) { if (m_eClientOperation == opClientRequestWriteLog) { abort("FATAL ERROR: Log-text not specified\n"); } else { abort("FATAL ERROR: Nzb-file not specified\n"); } } } else if (m_eClientOperation == opClientRequestEditQueue) { if (m_EMatchMode == mmID) { ParseFileIDList(argc, argv, optind); } else { ParseFileNameList(argc, argv, optind); } } else { m_szLastArg = strdup(argv[optind]); // Check if the file-name is a relative path or an absolute path // If the path starts with '/' its an absolute, else relative const char* szFileName = argv[optind]; #ifdef WIN32 m_szArgFilename = strdup(szFileName); #else if (szFileName[0] == '/') { m_szArgFilename = strdup(szFileName); } else { // TEST char szFileNameWithPath[1024]; getcwd(szFileNameWithPath, 1024); strcat(szFileNameWithPath, "/"); strcat(szFileNameWithPath, szFileName); m_szArgFilename = strdup(szFileNameWithPath); } #endif if (m_bServerMode || m_bRemoteClientMode || !(m_eClientOperation == opClientNoOperation || m_eClientOperation == opClientRequestDownload || m_eClientOperation == opClientRequestDownloadUrl || m_eClientOperation == opClientRequestWriteLog)) { abort("FATAL ERROR: Too many arguments\n"); } } } void Options::SetOption(const char* optname, const char* value) { OptEntry* pOptEntry = FindOption(optname); if (!pOptEntry) { pOptEntry = new OptEntry(); pOptEntry->SetName(optname); m_OptEntries.push_back(pOptEntry); } char* curvalue = NULL; #ifndef WIN32 if (value && (value[0] == '~') && (value[1] == '/')) { char szExpandedPath[1024]; if (!Util::ExpandHomePath(value, szExpandedPath, sizeof(szExpandedPath))) { ConfigError("Invalid value for option\"%s\": unable to determine home-directory", optname); szExpandedPath[0] = '\0'; } curvalue = strdup(szExpandedPath); } else #endif { curvalue = strdup(value); } pOptEntry->SetLineNo(m_iConfigLine); bool bOK = true; // expand variables while (char* dollar = strstr(curvalue, "${")) { char* end = strchr(dollar, '}'); if (end) { int varlen = (int)(end - dollar - 2); char variable[101]; int maxlen = varlen < 100 ? varlen : 100; strncpy(variable, dollar + 2, maxlen); variable[maxlen] = '\0'; const char* varvalue = GetOption(variable); if (varvalue) { int newlen = strlen(varvalue); char* newvalue = (char*)malloc(strlen(curvalue) - varlen - 3 + newlen + 1); strncpy(newvalue, curvalue, dollar - curvalue); strncpy(newvalue + (dollar - curvalue), varvalue, newlen); strcpy(newvalue + (dollar - curvalue) + newlen, end + 1); free(curvalue); curvalue = newvalue; } else { bOK = false; break; } } else { bOK = false; break; } } pOptEntry->SetValue(curvalue); free(curvalue); } Options::OptEntry* Options::FindOption(const char* optname) { OptEntry* pOptEntry = m_OptEntries.FindOption(optname); // normalize option name in option list; for example "server1.joingroup" -> "Server1.JoinGroup" if (pOptEntry && strcmp(pOptEntry->GetName(), optname)) { pOptEntry->SetName(optname); } return pOptEntry; } const char* Options::GetOption(const char* optname) { OptEntry* pOptEntry = FindOption(optname); if (pOptEntry) { if (pOptEntry->GetLineNo() > 0) { m_iConfigLine = pOptEntry->GetLineNo(); } return pOptEntry->GetValue(); } return NULL; } void Options::InitServers() { int n = 1; while (true) { char optname[128]; sprintf(optname, "Server%i.Active", n); const char* nactive = GetOption(optname); bool bActive = true; if (nactive) { bActive = (bool)ParseEnumValue(optname, BoolCount, BoolNames, BoolValues); } sprintf(optname, "Server%i.Name", n); const char* nname = GetOption(optname); sprintf(optname, "Server%i.Level", n); const char* nlevel = GetOption(optname); sprintf(optname, "Server%i.Group", n); const char* ngroup = GetOption(optname); sprintf(optname, "Server%i.Host", n); const char* nhost = GetOption(optname); sprintf(optname, "Server%i.Port", n); const char* nport = GetOption(optname); sprintf(optname, "Server%i.Username", n); const char* nusername = GetOption(optname); sprintf(optname, "Server%i.Password", n); const char* npassword = GetOption(optname); sprintf(optname, "Server%i.JoinGroup", n); const char* njoingroup = GetOption(optname); bool bJoinGroup = false; if (njoingroup) { bJoinGroup = (bool)ParseEnumValue(optname, BoolCount, BoolNames, BoolValues); } sprintf(optname, "Server%i.Encryption", n); const char* ntls = GetOption(optname); bool bTLS = false; if (ntls) { bTLS = (bool)ParseEnumValue(optname, BoolCount, BoolNames, BoolValues); #ifdef DISABLE_TLS if (bTLS) { ConfigError("Invalid value for option \"%s\": program was compiled without TLS/SSL-support", optname); bTLS = false; } #endif m_bTLS |= bTLS; } sprintf(optname, "Server%i.Cipher", n); const char* ncipher = GetOption(optname); sprintf(optname, "Server%i.Connections", n); const char* nconnections = GetOption(optname); bool definition = nactive || nname || nlevel || ngroup || nhost || nport || nusername || npassword || nconnections || njoingroup || ntls || ncipher; bool completed = nhost && nport && nconnections; if (!definition) { break; } if (completed) { NewsServer* pNewsServer = new NewsServer(n, bActive, nname, nhost, atoi(nport), nusername, npassword, bJoinGroup, bTLS, ncipher, atoi((char*)nconnections), nlevel ? atoi((char*)nlevel) : 0, ngroup ? atoi((char*)ngroup) : 0); g_pServerPool->AddServer(pNewsServer); } else { ConfigError("Server definition not complete for \"Server%i\"", n); } n++; } g_pServerPool->SetTimeout(GetConnectionTimeout()); } void Options::InitCategories() { int n = 1; while (true) { char optname[128]; sprintf(optname, "Category%i.Name", n); const char* nname = GetOption(optname); char destdiroptname[128]; sprintf(destdiroptname, "Category%i.DestDir", n); const char* ndestdir = GetOption(destdiroptname); sprintf(optname, "Category%i.Unpack", n); const char* nunpack = GetOption(optname); bool bUnpack = true; if (nunpack) { bUnpack = (bool)ParseEnumValue(optname, BoolCount, BoolNames, BoolValues); } sprintf(optname, "Category%i.DefScript", n); const char* ndefscript = GetOption(optname); sprintf(optname, "Category%i.Aliases", n); const char* naliases = GetOption(optname); bool definition = nname || ndestdir || nunpack || ndefscript || naliases; bool completed = nname && strlen(nname) > 0; if (!definition) { break; } if (completed) { char* szDestDir = NULL; if (ndestdir && ndestdir[0] != '\0') { CheckDir(&szDestDir, destdiroptname, false, false); } Category* pCategory = new Category(nname, szDestDir, bUnpack, ndefscript); m_Categories.push_back(pCategory); free(szDestDir); // split Aliases into tokens and create items for each token if (naliases) { char* szAliases = strdup(naliases); char* saveptr; char* szAliasName = strtok_r(szAliases, ",;", &saveptr); while (szAliasName) { szAliasName = Util::Trim(szAliasName); if (szAliasName[0] != '\0') { pCategory->GetAliases()->push_back(strdup(szAliasName)); } szAliasName = strtok_r(NULL, ",;", &saveptr); } free(szAliases); } } else { ConfigError("Category definition not complete for \"Category%i\"", n); } n++; } } void Options::InitFeeds() { int n = 1; while (true) { char optname[128]; sprintf(optname, "Feed%i.Name", n); const char* nname = GetOption(optname); sprintf(optname, "Feed%i.URL", n); const char* nurl = GetOption(optname); sprintf(optname, "Feed%i.Filter", n); const char* nfilter = GetOption(optname); sprintf(optname, "Feed%i.Category", n); const char* ncategory = GetOption(optname); sprintf(optname, "Feed%i.PauseNzb", n); const char* npausenzb = GetOption(optname); bool bPauseNzb = false; if (npausenzb) { bPauseNzb = (bool)ParseEnumValue(optname, BoolCount, BoolNames, BoolValues); } sprintf(optname, "Feed%i.Interval", n); const char* ninterval = GetOption(optname); sprintf(optname, "Feed%i.Priority", n); const char* npriority = GetOption(optname); bool definition = nname || nurl || nfilter || ncategory || npausenzb || ninterval || npriority; bool completed = nurl; if (!definition) { break; } if (completed) { FeedInfo* pFeedInfo = new FeedInfo(n, nname, nurl, ninterval ? atoi(ninterval) : 0, nfilter, bPauseNzb, ncategory, npriority ? atoi(npriority) : 0); g_pFeedCoordinator->AddFeed(pFeedInfo); } else { ConfigError("Feed definition not complete for \"Feed%i\"", n); } n++; } } void Options::InitScheduler() { int n = 1; while (true) { char optname[128]; sprintf(optname, "Task%i.Time", n); const char* szTime = GetOption(optname); sprintf(optname, "Task%i.WeekDays", n); const char* szWeekDays = GetOption(optname); sprintf(optname, "Task%i.Command", n); const char* szCommand = GetOption(optname); sprintf(optname, "Task%i.DownloadRate", n); const char* szDownloadRate = GetOption(optname); sprintf(optname, "Task%i.Process", n); const char* szProcess = GetOption(optname); sprintf(optname, "Task%i.Param", n); const char* szParam = GetOption(optname); if (Util::EmptyStr(szParam) && !Util::EmptyStr(szProcess)) { szParam = szProcess; } if (Util::EmptyStr(szParam) && !Util::EmptyStr(szDownloadRate)) { szParam = szDownloadRate; } bool definition = szTime || szWeekDays || szCommand || szDownloadRate || szParam; bool completed = szTime && szCommand; if (!definition) { break; } bool bOK = true; if (!completed) { ConfigError("Task definition not complete for \"Task%i\"", n); bOK = false; } snprintf(optname, sizeof(optname), "Task%i.Command", n); optname[sizeof(optname)-1] = '\0'; const char* CommandNames[] = { "pausedownload", "pause", "unpausedownload", "resumedownload", "unpause", "resume", "downloadrate", "setdownloadrate", "rate", "speed", "script", "process", "pausescan", "unpausescan", "resumescan", "activateserver", "activateservers", "deactivateserver", "deactivateservers", "fetchfeed", "fetchfeeds" }; const int CommandValues[] = { Scheduler::scPauseDownload, Scheduler::scPauseDownload, Scheduler::scUnpauseDownload, Scheduler::scUnpauseDownload, Scheduler::scUnpauseDownload, Scheduler::scUnpauseDownload, Scheduler::scDownloadRate, Scheduler::scDownloadRate, Scheduler::scDownloadRate, Scheduler::scDownloadRate, Scheduler::scProcess, Scheduler::scProcess, Scheduler::scPauseScan, Scheduler::scUnpauseScan, Scheduler::scUnpauseScan, Scheduler::scActivateServer, Scheduler::scActivateServer, Scheduler::scDeactivateServer, Scheduler::scDeactivateServer, Scheduler::scFetchFeed, Scheduler::scFetchFeed }; const int CommandCount = 21; Scheduler::ECommand eCommand = (Scheduler::ECommand)ParseEnumValue(optname, CommandCount, CommandNames, CommandValues); if (szParam && strlen(szParam) > 0 && eCommand == Scheduler::scProcess && !Util::SplitCommandLine(szParam, NULL)) { ConfigError("Invalid value for option \"Task%i.Param\"", n); bOK = false; } int iWeekDays = 0; if (szWeekDays && !ParseWeekDays(szWeekDays, &iWeekDays)) { ConfigError("Invalid value for option \"Task%i.WeekDays\": \"%s\"", n, szWeekDays); bOK = false; } if (eCommand == Scheduler::scDownloadRate) { if (szParam) { char* szErr; int iDownloadRate = strtol(szParam, &szErr, 10); if (!szErr || *szErr != '\0' || iDownloadRate < 0) { ConfigError("Invalid value for option \"Task%i.Param\": \"%s\"", n, szDownloadRate); bOK = false; } } else { ConfigError("Task definition not complete for \"Task%i\". Option \"Task%i.Param\" is missing", n, n); bOK = false; } } if ((eCommand == Scheduler::scProcess || eCommand == Scheduler::scActivateServer || eCommand == Scheduler::scDeactivateServer || eCommand == Scheduler::scFetchFeed) && Util::EmptyStr(szParam)) { ConfigError("Task definition not complete for \"Task%i\". Option \"Task%i.Param\" is missing", n, n); bOK = false; } int iHours, iMinutes; const char** pTime = &szTime; while (*pTime) { if (!ParseTime(pTime, &iHours, &iMinutes)) { ConfigError("Invalid value for option \"Task%i.Time\": \"%s\"", n, pTime); break; } if (bOK) { if (iHours == -1) { for (int iEveryHour = 0; iEveryHour < 24; iEveryHour++) { Scheduler::Task* pTask = new Scheduler::Task(iEveryHour, iMinutes, iWeekDays, eCommand, szParam); g_pScheduler->AddTask(pTask); } } else { Scheduler::Task* pTask = new Scheduler::Task(iHours, iMinutes, iWeekDays, eCommand, szParam); g_pScheduler->AddTask(pTask); } } } n++; } } /* * Parses Time string and moves current string pointer to the next time token. */ bool Options::ParseTime(const char** pTime, int* pHours, int* pMinutes) { const char* szTime = *pTime; const char* szComma = strchr(szTime, ','); int iColons = 0; const char* p = szTime; while (*p && (!szComma || p != szComma)) { if (!strchr("0123456789: *", *p)) { return false; } if (*p == ':') { iColons++; } p++; } if (iColons != 1) { return false; } const char* szColon = strchr(szTime, ':'); if (!szColon) { return false; } if (szTime[0] == '*') { *pHours = -1; } else { *pHours = atoi(szTime); if (*pHours < 0 || *pHours > 23) { return false; } } if (szColon[1] == '*') { return false; } *pMinutes = atoi(szColon + 1); if (*pMinutes < 0 || *pMinutes > 59) { return false; } if (szComma) { *pTime = szComma + 1; } else { *pTime = NULL; } return true; } bool Options::ParseWeekDays(const char* szWeekDays, int* pWeekDaysBits) { *pWeekDaysBits = 0; const char* p = szWeekDays; int iFirstDay = 0; bool bRange = false; while (*p) { if (strchr("1234567", *p)) { int iDay = *p - '0'; if (bRange) { if (iDay <= iFirstDay || iFirstDay == 0) { return false; } for (int i = iFirstDay; i <= iDay; i++) { *pWeekDaysBits |= 1 << (i - 1); } iFirstDay = 0; } else { *pWeekDaysBits |= 1 << (iDay - 1); iFirstDay = iDay; } bRange = false; } else if (*p == ',') { bRange = false; } else if (*p == '-') { bRange = true; } else if (*p == ' ') { // skip spaces } else { return false; } p++; } return true; } void Options::LoadConfigFile() { FILE* infile = fopen(m_szConfigFilename, "rb"); if (!infile) { abort("FATAL ERROR: Could not open file %s\n", m_szConfigFilename); } m_iConfigLine = 0; int iBufLen = (int)Util::FileSize(m_szConfigFilename) + 1; char* buf = (char*)malloc(iBufLen); int iLine = 0; while (fgets(buf, iBufLen - 1, infile)) { m_iConfigLine = ++iLine; if (buf[0] != 0 && buf[strlen(buf)-1] == '\n') { buf[strlen(buf)-1] = 0; // remove traling '\n' } if (buf[0] != 0 && buf[strlen(buf)-1] == '\r') { buf[strlen(buf)-1] = 0; // remove traling '\r' (for windows line endings) } if (buf[0] == 0 || buf[0] == '#' || strspn(buf, " ") == strlen(buf)) { continue; } SetOptionString(buf); } fclose(infile); free(buf); m_iConfigLine = 0; } bool Options::SetOptionString(const char* option) { char* optname; char* optvalue; if (!SplitOptionString(option, &optname, &optvalue)) { ConfigError("Invalid option \"%s\"", option); return false; } bool bOK = ValidateOptionName(optname); if (bOK) { SetOption(optname, optvalue); } else { ConfigError("Invalid option \"%s\"", optname); } free(optname); free(optvalue); return bOK; } /* * Splits option string into name and value; * Converts old names and values if necessary; * Allocates buffers for name and value; * Returns true if the option string has name and value; * If "true" is returned the caller is responsible for freeing optname and optvalue. */ bool Options::SplitOptionString(const char* option, char** pOptName, char** pOptValue) { const char* eq = strchr(option, '='); if (!eq) { return false; } const char* value = eq + 1; char optname[1001]; char optvalue[1001]; int maxlen = (int)(eq - option < 1000 ? eq - option : 1000); strncpy(optname, option, maxlen); optname[maxlen] = '\0'; strncpy(optvalue, eq + 1, 1000); optvalue[1000] = '\0'; if (strlen(optname) == 0) { return false; } ConvertOldOption(optname, sizeof(optname), optvalue, sizeof(optvalue)); // if value was (old-)converted use the new value, which is linited to 1000 characters, // otherwise use original (length-unlimited) value if (strncmp(value, optvalue, 1000)) { value = optvalue; } *pOptName = strdup(optname); *pOptValue = strdup(value); return true; } bool Options::ValidateOptionName(const char * optname) { if (!strcasecmp(optname, OPTION_CONFIGFILE) || !strcasecmp(optname, OPTION_APPBIN) || !strcasecmp(optname, OPTION_APPDIR) || !strcasecmp(optname, OPTION_VERSION)) { // read-only options return false; } const char* v = GetOption(optname); if (v) { // it's predefined option, OK return true; } if (!strncasecmp(optname, "server", 6)) { char* p = (char*)optname + 6; while (*p >= '0' && *p <= '9') p++; if (p && (!strcasecmp(p, ".active") || !strcasecmp(p, ".name") || !strcasecmp(p, ".level") || !strcasecmp(p, ".host") || !strcasecmp(p, ".port") || !strcasecmp(p, ".username") || !strcasecmp(p, ".password") || !strcasecmp(p, ".joingroup") || !strcasecmp(p, ".encryption") || !strcasecmp(p, ".connections") || !strcasecmp(p, ".cipher") || !strcasecmp(p, ".group"))) { return true; } } if (!strncasecmp(optname, "task", 4)) { char* p = (char*)optname + 4; while (*p >= '0' && *p <= '9') p++; if (p && (!strcasecmp(p, ".time") || !strcasecmp(p, ".weekdays") || !strcasecmp(p, ".command") || !strcasecmp(p, ".param") || !strcasecmp(p, ".downloadrate") || !strcasecmp(p, ".process"))) { return true; } } if (!strncasecmp(optname, "category", 8)) { char* p = (char*)optname + 8; while (*p >= '0' && *p <= '9') p++; if (p && (!strcasecmp(p, ".name") || !strcasecmp(p, ".destdir") || !strcasecmp(p, ".defscript") || !strcasecmp(p, ".unpack") || !strcasecmp(p, ".aliases"))) { return true; } } if (!strncasecmp(optname, "feed", 4)) { char* p = (char*)optname + 4; while (*p >= '0' && *p <= '9') p++; if (p && (!strcasecmp(p, ".name") || !strcasecmp(p, ".url") || !strcasecmp(p, ".interval") || !strcasecmp(p, ".filter") || !strcasecmp(p, ".pausenzb") || !strcasecmp(p, ".category") || !strcasecmp(p, ".priority"))) { return true; } } // post-processing scripts options if (strchr(optname, ':')) { return true; } // print warning messages for obsolete options if (!strcasecmp(optname, OPTION_RETRYONCRCERROR) || !strcasecmp(optname, OPTION_ALLOWREPROCESS) || !strcasecmp(optname, OPTION_LOADPARS) || !strcasecmp(optname, OPTION_THREADLIMIT) || !strcasecmp(optname, OPTION_POSTLOGKIND) || !strcasecmp(optname, OPTION_NZBLOGKIND) || !strcasecmp(optname, OPTION_PROCESSLOGKIND) || !strcasecmp(optname, OPTION_APPENDNZBDIR) || !strcasecmp(optname, OPTION_RENAMEBROKEN) || !strcasecmp(optname, OPTION_MERGENZB) || !strcasecmp(optname, OPTION_STRICTPARNAME)) { ConfigWarn("Option \"%s\" is obsolete, ignored", optname); return true; } if (!strcasecmp(optname, OPTION_POSTPROCESS)) { ConfigError("Option \"%s\" is obsolete, ignored, use \"%s\" and \"%s\" instead", optname, OPTION_SCRIPTDIR, OPTION_DEFSCRIPT); return true; } return false; } void Options::ConvertOldOption(char *szOption, int iOptionBufLen, char *szValue, int iValueBufLen) { // for compatibility with older versions accept old option names if (!strcasecmp(szOption, "$MAINDIR")) { strncpy(szOption, "MainDir", iOptionBufLen); } if (!strcasecmp(szOption, "ServerIP")) { strncpy(szOption, "ControlIP", iOptionBufLen); } if (!strcasecmp(szOption, "ServerPort")) { strncpy(szOption, "ControlPort", iOptionBufLen); } if (!strcasecmp(szOption, "ServerPassword")) { strncpy(szOption, "ControlPassword", iOptionBufLen); } if (!strcasecmp(szOption, "PostPauseQueue")) { strncpy(szOption, "ScriptPauseQueue", iOptionBufLen); } if (!strcasecmp(szOption, "ParCheck") && !strcasecmp(szValue, "yes")) { strncpy(szValue, "force", iValueBufLen); } if (!strcasecmp(szOption, "ParCheck") && !strcasecmp(szValue, "no")) { strncpy(szValue, "auto", iValueBufLen); } szOption[iOptionBufLen-1] = '\0'; szOption[iValueBufLen-1] = '\0'; } void Options::CheckOptions() { #ifdef DISABLE_PARCHECK if (m_eParCheck != pcManual) { LocateOptionSrcPos(OPTION_PARCHECK); ConfigError("Invalid value for option \"%s\": program was compiled without parcheck-support", OPTION_PARCHECK); } if (m_bParRename) { LocateOptionSrcPos(OPTION_PARRENAME); ConfigError("Invalid value for option \"%s\": program was compiled without parcheck-support", OPTION_PARRENAME); } #endif #ifdef DISABLE_CURSES if (m_eOutputMode == omNCurses) { LocateOptionSrcPos(OPTION_OUTPUTMODE); ConfigError("Invalid value for option \"%s\": program was compiled without curses-support", OPTION_OUTPUTMODE); } #endif #ifdef DISABLE_TLS if (m_bSecureControl) { LocateOptionSrcPos(OPTION_SECURECONTROL); ConfigError("Invalid value for option \"%s\": program was compiled without TLS/SSL-support", OPTION_SECURECONTROL); } #endif if (!m_bDecode) { m_bDirectWrite = false; } // if option "ConfigTemplate" is not set, use "WebDir" as default location for template // (for compatibility with versions 9 and 10). if (!m_szConfigTemplate || m_szConfigTemplate[0] == '\0') { free(m_szConfigTemplate); int iLen = strlen(m_szWebDir) + 15; m_szConfigTemplate = (char*)malloc(iLen); snprintf(m_szConfigTemplate, iLen, "%s%s", m_szWebDir, "nzbget.conf"); m_szConfigTemplate[iLen-1] = '\0'; if (!Util::FileExists(m_szConfigTemplate)) { free(m_szConfigTemplate); m_szConfigTemplate = strdup(""); } } } void Options::ParseFileIDList(int argc, char* argv[], int optind) { std::vector 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 = (int)(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]; } } void Options::ParseFileNameList(int argc, char* argv[], int optind) { while (optind < argc) { m_EditQueueNameList.push_back(strdup(argv[optind++])); } } Options::OptEntries* Options::LockOptEntries() { m_mutexOptEntries.Lock(); return &m_OptEntries; } void Options::UnlockOptEntries() { m_mutexOptEntries.Unlock(); } bool Options::LoadConfig(OptEntries* pOptEntries) { // read config file FILE* infile = fopen(m_szConfigFilename, "rb"); if (!infile) { return false; } int iBufLen = (int)Util::FileSize(m_szConfigFilename) + 1; char* buf = (char*)malloc(iBufLen); while (fgets(buf, iBufLen - 1, infile)) { // remove trailing '\n' and '\r' and spaces Util::TrimRight(buf); // skip comments and empty lines if (buf[0] == 0 || buf[0] == '#' || strspn(buf, " ") == strlen(buf)) { continue; } char* optname; char* optvalue; if (SplitOptionString(buf, &optname, &optvalue)) { OptEntry* pOptEntry = new OptEntry(); pOptEntry->SetName(optname); pOptEntry->SetValue(optvalue); pOptEntries->push_back(pOptEntry); free(optname); free(optvalue); } } fclose(infile); free(buf); return true; } bool Options::SaveConfig(OptEntries* pOptEntries) { // save to config file FILE* infile = fopen(m_szConfigFilename, "r+b"); if (!infile) { return false; } std::vector config; std::set writtenOptions; // read config file into memory array int iBufLen = (int)Util::FileSize(m_szConfigFilename) + 1; char* buf = (char*)malloc(iBufLen); while (fgets(buf, iBufLen - 1, infile)) { config.push_back(strdup(buf)); } free(buf); // write config file back to disk, replace old values of existing options with new values rewind(infile); for (std::vector::iterator it = config.begin(); it != config.end(); it++) { char* buf = *it; const char* eq = strchr(buf, '='); if (eq && buf[0] != '#') { // remove trailing '\n' and '\r' and spaces Util::TrimRight(buf); char* optname; char* optvalue; if (SplitOptionString(buf, &optname, &optvalue)) { OptEntry *pOptEntry = pOptEntries->FindOption(optname); if (pOptEntry) { fputs(pOptEntry->GetName(), infile); fputs("=", infile); fputs(pOptEntry->GetValue(), infile); fputs("\n", infile); writtenOptions.insert(pOptEntry); } free(optname); free(optvalue); } } else { fputs(buf, infile); } free(buf); } // write new options for (Options::OptEntries::iterator it = pOptEntries->begin(); it != pOptEntries->end(); it++) { Options::OptEntry* pOptEntry = *it; std::set::iterator fit = writtenOptions.find(pOptEntry); if (fit == writtenOptions.end()) { fputs(pOptEntry->GetName(), infile); fputs("=", infile); fputs(pOptEntry->GetValue(), infile); fputs("\n", infile); } } // close and truncate the file int pos = ftell(infile); fclose(infile); Util::TruncateFile(m_szConfigFilename, pos); return true; } bool Options::LoadConfigTemplates(ConfigTemplates* pConfigTemplates) { char* szBuffer; int iLength; if (!Util::LoadFileIntoBuffer(m_szConfigTemplate, &szBuffer, &iLength)) { return false; } ConfigTemplate* pConfigTemplate = new ConfigTemplate("", "", szBuffer); pConfigTemplates->push_back(pConfigTemplate); free(szBuffer); if (!m_szScriptDir) { return true; } ScriptList scriptList; LoadScriptList(&scriptList); for (ScriptList::iterator it = scriptList.begin(); it != scriptList.end(); it++) { Script* pScript = *it; FILE* infile = fopen(pScript->GetLocation(), "rb"); if (!infile) { ConfigTemplate* pConfigTemplate = new ConfigTemplate(pScript->GetName(), pScript->GetDisplayName(), ""); pConfigTemplates->push_back(pConfigTemplate); continue; } const int iConfigSignatureLen = strlen(PPSCRIPT_SIGNATURE); StringBuilder stringBuilder; char buf[1024]; bool bInConfig = false; while (fgets(buf, sizeof(buf) - 1, infile)) { if (!strncmp(buf, PPSCRIPT_SIGNATURE, iConfigSignatureLen)) { if (bInConfig) { break; } bInConfig = true; continue; } if (bInConfig) { stringBuilder.Append(buf); } } fclose(infile); ConfigTemplate* pConfigTemplate = new ConfigTemplate(pScript->GetName(), pScript->GetDisplayName(), stringBuilder.GetBuffer()); pConfigTemplates->push_back(pConfigTemplate); } return true; } void Options::LoadScriptList(ScriptList* pScriptList) { if (strlen(m_szScriptDir) == 0) { return; } ScriptList tmpScriptList; LoadScriptDir(&tmpScriptList, m_szScriptDir, false); tmpScriptList.sort(CompareScripts); // first add all scripts from m_szScriptOrder char* szScriptOrder = strdup(m_szScriptOrder); char* saveptr; char* szScriptName = strtok_r(szScriptOrder, ",;", &saveptr); while (szScriptName) { szScriptName = Util::Trim(szScriptName); if (szScriptName[0] != '\0') { Script* pScript = tmpScriptList.Find(szScriptName); if (pScript) { pScriptList->push_back(new Script(pScript->GetName(), pScript->GetLocation())); } } szScriptName = strtok_r(NULL, ",;", &saveptr); } free(szScriptOrder); // second add all other scripts from scripts directory for (ScriptList::iterator it = tmpScriptList.begin(); it != tmpScriptList.end(); it++) { Script* pScript = *it; if (!pScriptList->Find(pScript->GetName())) { pScriptList->push_back(new Script(pScript->GetName(), pScript->GetLocation())); } } BuildScriptDisplayNames(pScriptList); } void Options::LoadScriptDir(ScriptList* pScriptList, const char* szDirectory, bool bIsSubDir) { int iBufSize = 1024*10; char* szBuffer = (char*)malloc(iBufSize+1); DirBrowser dir(szDirectory); while (const char* szFilename = dir.Next()) { if (szFilename[0] != '.' && szFilename[0] != '_') { char szFullFilename[1024]; snprintf(szFullFilename, 1024, "%s%s", szDirectory, szFilename); szFullFilename[1024-1] = '\0'; if (!Util::DirectoryExists(szFullFilename)) { // check if the file contains pp-script-signature FILE* infile = fopen(szFullFilename, "rb"); if (infile) { // read first 10KB of the file and look for signature int iReadBytes = fread(szBuffer, 1, iBufSize, infile); fclose(infile); szBuffer[iReadBytes] = 0; if (strstr(szBuffer, PPSCRIPT_SIGNATURE)) { char szScriptName[1024]; if (bIsSubDir) { char szDirectory2[1024]; snprintf(szDirectory2, 1024, "%s", szDirectory); szDirectory2[1024-1] = '\0'; int iLen = strlen(szDirectory2); if (szDirectory2[iLen-1] == PATH_SEPARATOR || szDirectory2[iLen-1] == ALT_PATH_SEPARATOR) { // trim last path-separator szDirectory2[iLen-1] = '\0'; } snprintf(szScriptName, 1024, "%s%c%s", Util::BaseFileName(szDirectory2), PATH_SEPARATOR, szFilename); } else { snprintf(szScriptName, 1024, "%s", szFilename); } szScriptName[1024-1] = '\0'; Script* pScript = new Script(szScriptName, szFullFilename); pScriptList->push_back(pScript); } } } else if (!bIsSubDir) { snprintf(szFullFilename, 1024, "%s%s%c", szDirectory, szFilename, PATH_SEPARATOR); szFullFilename[1024-1] = '\0'; LoadScriptDir(pScriptList, szFullFilename, true); } } } free(szBuffer); } bool Options::CompareScripts(Script* pScript1, Script* pScript2) { return strcmp(pScript1->GetName(), pScript2->GetName()) < 0; } void Options::BuildScriptDisplayNames(ScriptList* pScriptList) { // trying to use short name without path and extension. // if there are other scripts with the same short name - using a longer name instead (with ot without extension) for (ScriptList::iterator it = pScriptList->begin(); it != pScriptList->end(); it++) { Script* pScript = *it; char szShortName[256]; strncpy(szShortName, pScript->GetName(), 256); szShortName[256-1] = '\0'; if (char* ext = strrchr(szShortName, '.')) *ext = '\0'; // strip file extension const char* szDisplayName = Util::BaseFileName(szShortName); for (ScriptList::iterator it2 = pScriptList->begin(); it2 != pScriptList->end(); it2++) { Script* pScript2 = *it2; char szShortName2[256]; strncpy(szShortName2, pScript2->GetName(), 256); szShortName2[256-1] = '\0'; if (char* ext = strrchr(szShortName2, '.')) *ext = '\0'; // strip file extension const char* szDisplayName2 = Util::BaseFileName(szShortName2); if (!strcmp(szDisplayName, szDisplayName2) && pScript->GetName() != pScript2->GetName()) { if (!strcmp(szShortName, szShortName2)) { szDisplayName = pScript->GetName(); } else { szDisplayName = szShortName; } break; } } pScript->SetDisplayName(szDisplayName); } } nzbget-12.0+dfsg/Options.h000066400000000000000000000416741226450633000154650ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 914 $ * $Date: 2013-11-28 22:03:01 +0100 (Thu, 28 Nov 2013) $ * */ #ifndef OPTIONS_H #define OPTIONS_H #include #include #include #include "Thread.h" #include "Util.h" class Options { public: enum EClientOperation { opClientNoOperation, opClientRequestDownload, opClientRequestListFiles, opClientRequestListGroups, opClientRequestListStatus, opClientRequestSetRate, opClientRequestDumpDebug, opClientRequestEditQueue, opClientRequestLog, opClientRequestShutdown, opClientRequestReload, opClientRequestVersion, opClientRequestPostQueue, opClientRequestWriteLog, opClientRequestScanSync, opClientRequestScanAsync, opClientRequestDownloadPause, opClientRequestDownloadUnpause, opClientRequestDownload2Pause, opClientRequestDownload2Unpause, opClientRequestPostPause, opClientRequestPostUnpause, opClientRequestScanPause, opClientRequestScanUnpause, opClientRequestHistory, opClientRequestDownloadUrl, opClientRequestUrlQueue }; enum EMessageTarget { mtNone, mtScreen, mtLog, mtBoth }; enum EOutputMode { omLoggable, omColored, omNCurses }; enum EParCheck { pcAuto, pcForce, pcManual }; enum EParScan { psLimited, psFull, psAuto }; enum EHealthCheck { hcPause, hcDelete, hcNone }; enum EScriptLogKind { slNone, slDetail, slInfo, slWarning, slError, slDebug }; enum EMatchMode { mmID = 1, mmName, mmRegEx }; class OptEntry { private: char* m_szName; char* m_szValue; char* m_szDefValue; int m_iLineNo; void SetName(const char* szName); void SetValue(const char* szValue); void SetLineNo(int iLineNo) { m_iLineNo = iLineNo; } friend class Options; public: OptEntry(); OptEntry(const char* szName, const char* szValue); ~OptEntry(); const char* GetName() { return m_szName; } const char* GetValue() { return m_szValue; } const char* GetDefValue() { return m_szDefValue; } int GetLineNo() { return m_iLineNo; } }; typedef std::vector OptEntriesBase; class OptEntries: public OptEntriesBase { public: ~OptEntries(); OptEntry* FindOption(const char* szName); }; class ConfigTemplate { private: char* m_szName; char* m_szDisplayName; char* m_szTemplate; friend class Options; public: ConfigTemplate(const char* szName, const char* szDisplayName, const char* szTemplate); ~ConfigTemplate(); const char* GetName() { return m_szName; } const char* GetDisplayName() { return m_szDisplayName; } const char* GetTemplate() { return m_szTemplate; } }; typedef std::vector ConfigTemplatesBase; class ConfigTemplates: public ConfigTemplatesBase { public: ~ConfigTemplates(); }; typedef std::vector NameList; class Category { private: char* m_szName; char* m_szDestDir; bool m_bUnpack; char* m_szDefScript; NameList m_Aliases; public: Category(const char* szName, const char* szDestDir, bool bUnpack, const char* szDefScript); ~Category(); const char* GetName() { return m_szName; } const char* GetDestDir() { return m_szDestDir; } bool GetUnpack() { return m_bUnpack; } const char* GetDefScript() { return m_szDefScript; } NameList* GetAliases() { return &m_Aliases; } }; typedef std::vector CategoriesBase; class Categories: public CategoriesBase { public: ~Categories(); Category* FindCategory(const char* szName, bool bSearchAliases); }; class Script { private: char* m_szName; char* m_szLocation; char* m_szDisplayName; public: Script(const char* szName, const char* szLocation); ~Script(); const char* GetName() { return m_szName; } const char* GetLocation() { return m_szLocation; } void SetDisplayName(const char* szDisplayName); const char* GetDisplayName() { return m_szDisplayName; } }; typedef std::list ScriptListBase; class ScriptList: public ScriptListBase { public: ~ScriptList(); Script* Find(const char* szName); }; private: OptEntries m_OptEntries; bool m_bConfigInitialized; Mutex m_mutexOptEntries; Categories m_Categories; // Options bool m_bConfigErrors; int m_iConfigLine; char* m_szConfigFilename; char* m_szDestDir; char* m_szInterDir; char* m_szTempDir; char* m_szQueueDir; char* m_szNzbDir; char* m_szWebDir; char* m_szConfigTemplate; char* m_szScriptDir; EMessageTarget m_eInfoTarget; EMessageTarget m_eWarningTarget; EMessageTarget m_eErrorTarget; EMessageTarget m_eDebugTarget; EMessageTarget m_eDetailTarget; bool m_bDecode; bool m_bCreateBrokenLog; bool m_bResetLog; int m_iConnectionTimeout; int m_iTerminateTimeout; bool m_bAppendCategoryDir; bool m_bContinuePartial; int m_iRetries; int m_iRetryInterval; bool m_bSaveQueue; bool m_bDupeCheck; char* m_szControlIP; char* m_szControlUsername; char* m_szControlPassword; int m_iControlPort; bool m_bSecureControl; int m_iSecurePort; char* m_szSecureCert; char* m_szSecureKey; char* m_szAuthorizedIP; char* m_szLockFile; char* m_szDaemonUsername; EOutputMode m_eOutputMode; bool m_bReloadQueue; bool m_bReloadUrlQueue; bool m_bReloadPostQueue; int m_iUrlConnections; int m_iLogBufferSize; bool m_bCreateLog; char* m_szLogFile; EParCheck m_eParCheck; bool m_bParRepair; EParScan m_eParScan; bool m_bParRename; EHealthCheck m_eHealthCheck; char* m_szDefScript; char* m_szScriptOrder; char* m_szNZBProcess; char* m_szNZBAddedProcess; bool m_bNoConfig; int m_iUMask; int m_iUpdateInterval; bool m_bCursesNZBName; bool m_bCursesTime; bool m_bCursesGroup; bool m_bCrcCheck; bool m_bDirectWrite; int m_iWriteBufferSize; int m_iNzbDirInterval; int m_iNzbDirFileAge; bool m_bParCleanupQueue; int m_iDiskSpace; bool m_bTLS; bool m_bDumpCore; bool m_bParPauseQueue; bool m_bScriptPauseQueue; bool m_bNzbCleanupDisk; bool m_bDeleteCleanupDisk; int m_iParTimeLimit; int m_iKeepHistory; bool m_bAccurateRate; bool m_bUnpack; bool m_bUnpackCleanupDisk; char* m_szUnrarCmd; char* m_szSevenZipCmd; bool m_bUnpackPauseQueue; char* m_szExtCleanupDisk; int m_iFeedHistory; bool m_bUrlForce; int m_iTimeCorrection; // Parsed command-line parameters bool m_bServerMode; bool m_bDaemonMode; bool m_bRemoteClientMode; int m_iEditQueueAction; int m_iEditQueueOffset; int* m_pEditQueueIDList; int m_iEditQueueIDCount; NameList m_EditQueueNameList; EMatchMode m_EMatchMode; char* m_szEditQueueText; char* m_szArgFilename; char* m_szAddCategory; int m_iAddPriority; bool m_bAddPaused; char* m_szAddNZBFilename; char* m_szLastArg; bool m_bPrintOptions; bool m_bAddTop; int m_iSetRate; int m_iLogLines; int m_iWriteLogKind; bool m_bTestBacktrace; // Current state bool m_bPauseDownload; bool m_bPauseDownload2; bool m_bPausePostProcess; bool m_bPauseScan; int m_iDownloadRate; EClientOperation m_eClientOperation; time_t m_tResumeTime; void InitDefault(); void InitOptFile(); void InitCommandLine(int argc, char* argv[]); void InitOptions(); void InitFileArg(int argc, char* argv[]); void InitServers(); void InitCategories(); void InitScheduler(); void InitFeeds(); void CheckOptions(); void PrintUsage(char* com); void Dump(); int ParseEnumValue(const char* OptName, int argc, const char* argn[], const int argv[]); int ParseIntValue(const char* OptName, int iBase); float ParseFloatValue(const char* OptName); OptEntry* FindOption(const char* optname); const char* GetOption(const char* optname); void SetOption(const char* optname, const char* value); bool SetOptionString(const char* option); bool SplitOptionString(const char* option, char** pOptName, char** pOptValue); bool ValidateOptionName(const char* optname); void LoadConfigFile(); void CheckDir(char** dir, const char* szOptionName, bool bAllowEmpty, bool bCreate); void ParseFileIDList(int argc, char* argv[], int optind); void ParseFileNameList(int argc, char* argv[], int optind); bool ParseTime(const char** pTime, int* pHours, int* pMinutes); bool ParseWeekDays(const char* szWeekDays, int* pWeekDaysBits); void ConfigError(const char* msg, ...); void ConfigWarn(const char* msg, ...); void LocateOptionSrcPos(const char *szOptionName); void ConvertOldOption(char *szOption, int iOptionBufLen, char *szValue, int iValueBufLen); static bool CompareScripts(Script* pScript1, Script* pScript2); void LoadScriptDir(ScriptList* pScriptList, const char* szDirectory, bool bIsSubDir); void BuildScriptDisplayNames(ScriptList* pScriptList); public: Options(int argc, char* argv[]); ~Options(); bool LoadConfig(OptEntries* pOptEntries); bool SaveConfig(OptEntries* pOptEntries); bool LoadConfigTemplates(ConfigTemplates* pConfigTemplates); void LoadScriptList(ScriptList* pScriptList); // Options OptEntries* LockOptEntries(); void UnlockOptEntries(); const char* GetConfigFilename() { return m_szConfigFilename; } const char* GetDestDir() { return m_szDestDir; } const char* GetInterDir() { return m_szInterDir; } const char* GetTempDir() { return m_szTempDir; } const char* GetQueueDir() { return m_szQueueDir; } const char* GetNzbDir() { return m_szNzbDir; } const char* GetWebDir() { return m_szWebDir; } const char* GetConfigTemplate() { return m_szConfigTemplate; } const char* GetScriptDir() { return m_szScriptDir; } bool GetCreateBrokenLog() const { return m_bCreateBrokenLog; } bool GetResetLog() const { return m_bResetLog; } EMessageTarget GetInfoTarget() const { return m_eInfoTarget; } EMessageTarget GetWarningTarget() const { return m_eWarningTarget; } EMessageTarget GetErrorTarget() const { return m_eErrorTarget; } EMessageTarget GetDebugTarget() const { return m_eDebugTarget; } EMessageTarget GetDetailTarget() const { return m_eDetailTarget; } int GetConnectionTimeout() { return m_iConnectionTimeout; } int GetTerminateTimeout() { return m_iTerminateTimeout; } bool GetDecode() { return m_bDecode; }; bool GetAppendCategoryDir() { return m_bAppendCategoryDir; } bool GetContinuePartial() { return m_bContinuePartial; } int GetRetries() { return m_iRetries; } int GetRetryInterval() { return m_iRetryInterval; } bool GetSaveQueue() { return m_bSaveQueue; } bool GetDupeCheck() { return m_bDupeCheck; } const char* GetControlIP() { return m_szControlIP; } const char* GetControlUsername() { return m_szControlUsername; } const char* GetControlPassword() { return m_szControlPassword; } int GetControlPort() { return m_iControlPort; } bool GetSecureControl() { return m_bSecureControl; } int GetSecurePort() { return m_iSecurePort; } const char* GetSecureCert() { return m_szSecureCert; } const char* GetSecureKey() { return m_szSecureKey; } const char* GetAuthorizedIP() { return m_szAuthorizedIP; } const char* GetLockFile() { return m_szLockFile; } const char* GetDaemonUsername() { return m_szDaemonUsername; } EOutputMode GetOutputMode() { return m_eOutputMode; } bool GetReloadQueue() { return m_bReloadQueue; } bool GetReloadUrlQueue() { return m_bReloadUrlQueue; } bool GetReloadPostQueue() { return m_bReloadPostQueue; } int GetUrlConnections() { return m_iUrlConnections; } int GetLogBufferSize() { return m_iLogBufferSize; } bool GetCreateLog() { return m_bCreateLog; } const char* GetLogFile() { return m_szLogFile; } EParCheck GetParCheck() { return m_eParCheck; } bool GetParRepair() { return m_bParRepair; } EParScan GetParScan() { return m_eParScan; } bool GetParRename() { return m_bParRename; } EHealthCheck GetHealthCheck() { return m_eHealthCheck; } const char* GetScriptOrder() { return m_szScriptOrder; } const char* GetDefScript() { return m_szDefScript; } const char* GetNZBProcess() { return m_szNZBProcess; } const char* GetNZBAddedProcess() { return m_szNZBAddedProcess; } 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 GetDirectWrite() { return m_bDirectWrite; } int GetWriteBufferSize() { return m_iWriteBufferSize; } int GetNzbDirInterval() { return m_iNzbDirInterval; } int GetNzbDirFileAge() { return m_iNzbDirFileAge; } bool GetParCleanupQueue() { return m_bParCleanupQueue; } int GetDiskSpace() { return m_iDiskSpace; } bool GetTLS() { return m_bTLS; } bool GetDumpCore() { return m_bDumpCore; } bool GetParPauseQueue() { return m_bParPauseQueue; } bool GetScriptPauseQueue() { return m_bScriptPauseQueue; } bool GetNzbCleanupDisk() { return m_bNzbCleanupDisk; } bool GetDeleteCleanupDisk() { return m_bDeleteCleanupDisk; } int GetParTimeLimit() { return m_iParTimeLimit; } int GetKeepHistory() { return m_iKeepHistory; } bool GetAccurateRate() { return m_bAccurateRate; } bool GetUnpack() { return m_bUnpack; } bool GetUnpackCleanupDisk() { return m_bUnpackCleanupDisk; } const char* GetUnrarCmd() { return m_szUnrarCmd; } const char* GetSevenZipCmd() { return m_szSevenZipCmd; } bool GetUnpackPauseQueue() { return m_bUnpackPauseQueue; } const char* GetExtCleanupDisk() { return m_szExtCleanupDisk; } int GetFeedHistory() { return m_iFeedHistory; } bool GetUrlForce() { return m_bUrlForce; } int GetTimeCorrection() { return m_iTimeCorrection; } Category* FindCategory(const char* szName, bool bSearchAliases) { return m_Categories.FindCategory(szName, bSearchAliases); } // Parsed command-line parameters bool GetServerMode() { return m_bServerMode; } bool GetDaemonMode() { return m_bDaemonMode; } bool GetRemoteClientMode() { return m_bRemoteClientMode; } EClientOperation GetClientOperation() { return m_eClientOperation; } int GetEditQueueAction() { return m_iEditQueueAction; } int GetEditQueueOffset() { return m_iEditQueueOffset; } int* GetEditQueueIDList() { return m_pEditQueueIDList; } int GetEditQueueIDCount() { return m_iEditQueueIDCount; } NameList* GetEditQueueNameList() { return &m_EditQueueNameList; } EMatchMode GetMatchMode() { return m_EMatchMode; } const char* GetEditQueueText() { return m_szEditQueueText; } const char* GetArgFilename() { return m_szArgFilename; } const char* GetAddCategory() { return m_szAddCategory; } bool GetAddPaused() { return m_bAddPaused; } const char* GetLastArg() { return m_szLastArg; } int GetAddPriority() { return m_iAddPriority; } char* GetAddNZBFilename() { return m_szAddNZBFilename; } bool GetAddTop() { return m_bAddTop; } int GetSetRate() { return m_iSetRate; } int GetLogLines() { return m_iLogLines; } int GetWriteLogKind() { return m_iWriteLogKind; } bool GetTestBacktrace() { return m_bTestBacktrace; } // Current state void SetPauseDownload(bool bPauseDownload) { m_bPauseDownload = bPauseDownload; } bool GetPauseDownload() const { return m_bPauseDownload; } void SetPauseDownload2(bool bPauseDownload2) { m_bPauseDownload2 = bPauseDownload2; } bool GetPauseDownload2() const { return m_bPauseDownload2; } void SetPausePostProcess(bool bPausePostProcess) { m_bPausePostProcess = bPausePostProcess; } bool GetPausePostProcess() const { return m_bPausePostProcess; } void SetPauseScan(bool bPauseScan) { m_bPauseScan = bPauseScan; } bool GetPauseScan() const { return m_bPauseScan; } void SetDownloadRate(int iRate) { m_iDownloadRate = iRate; } int GetDownloadRate() const { return m_iDownloadRate; } void SetResumeTime(time_t tResumeTime) { m_tResumeTime = tResumeTime; } time_t GetResumeTime() const { return m_tResumeTime; } }; #endif nzbget-12.0+dfsg/ParChecker.cpp000066400000000000000000000616361226450633000163740ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 911 $ * $Date: 2013-11-24 20:29:52 +0100 (Sun, 24 Nov 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #ifndef DISABLE_PARCHECK #include #include #include #include #ifdef WIN32 #include #include #else #include #include #include #endif #include #include "nzbget.h" #include "ParChecker.h" #include "ParCoordinator.h" #include "Log.h" #include "Options.h" #include "Util.h" extern Options* g_pOptions; const char* Par2CmdLineErrStr[] = { "OK", "data files are damaged and there is enough recovery data available to repair them", "data files are damaged and there is insufficient recovery data available to be able to repair them", "there was something wrong with the command line arguments", "the PAR2 files did not contain sufficient information about the data files to be able to verify them", "repair completed but the data files still appear to be damaged", "an error occured when accessing files", "internal error occurred", "out of memory" }; class Repairer : public Par2Repairer { private: CommandLine commandLine; public: Result PreProcess(const char *szParFilename); Result Process(bool dorepair); friend class ParChecker; }; Result Repairer::PreProcess(const char *szParFilename) { #ifdef HAVE_PAR2_BUGFIXES_V2 // Ensure linking against the patched version of libpar2 BugfixesPatchVersion2(); #endif if (g_pOptions->GetParScan() == Options::psFull) { char szWildcardParam[1024]; strncpy(szWildcardParam, szParFilename, 1024); szWildcardParam[1024-1] = '\0'; char* szBasename = Util::BaseFileName(szWildcardParam); if (szBasename != szWildcardParam && strlen(szBasename) > 0) { szBasename[0] = '*'; szBasename[1] = '\0'; } const char* argv[] = { "par2", "r", "-v", "-v", szParFilename, szWildcardParam }; if (!commandLine.Parse(6, (char**)argv)) { return eInvalidCommandLineArguments; } } else { const char* argv[] = { "par2", "r", "-v", "-v", szParFilename }; if (!commandLine.Parse(5, (char**)argv)) { return eInvalidCommandLineArguments; } } return Par2Repairer::PreProcess(commandLine); } Result Repairer::Process(bool dorepair) { return Par2Repairer::Process(commandLine, dorepair); } class MissingFilesComparator { private: const char* m_szBaseParFilename; public: MissingFilesComparator(const char* szBaseParFilename) : m_szBaseParFilename(szBaseParFilename) {} bool operator()(CommandLine::ExtraFile* pFirst, CommandLine::ExtraFile* pSecond) const; }; /* * Files with the same name as in par-file (and a differnt extension) are * placed at the top of the list to be scanned first. */ bool MissingFilesComparator::operator()(CommandLine::ExtraFile* pFile1, CommandLine::ExtraFile* pFile2) const { char name1[1024]; strncpy(name1, Util::BaseFileName(pFile1->FileName().c_str()), 1024); name1[1024-1] = '\0'; if (char* ext = strrchr(name1, '.')) *ext = '\0'; // trim extension char name2[1024]; strncpy(name2, Util::BaseFileName(pFile2->FileName().c_str()), 1024); name2[1024-1] = '\0'; if (char* ext = strrchr(name2, '.')) *ext = '\0'; // trim extension return strcmp(name1, m_szBaseParFilename) == 0 && strcmp(name1, name2) != 0; } ParChecker::ParChecker() { debug("Creating ParChecker"); m_eStatus = psFailed; m_szDestDir = NULL; m_szNZBName = NULL; m_szParFilename = NULL; m_szInfoName = NULL; m_szErrMsg = NULL; m_szProgressLabel = (char*)malloc(1024); m_pRepairer = NULL; m_iFileProgress = 0; m_iStageProgress = 0; m_iExtraFiles = 0; m_bVerifyingExtraFiles = false; m_bCancelled = false; m_eStage = ptLoadingPars; } ParChecker::~ParChecker() { debug("Destroying ParChecker"); free(m_szDestDir); free(m_szNZBName); free(m_szInfoName); free(m_szProgressLabel); Cleanup(); } void ParChecker::Cleanup() { delete (Repairer*)m_pRepairer; m_pRepairer = NULL; for (FileList::iterator it = m_QueuedParFiles.begin(); it != m_QueuedParFiles.end() ;it++) { free(*it); } m_QueuedParFiles.clear(); for (FileList::iterator it = m_ProcessedFiles.begin(); it != m_ProcessedFiles.end() ;it++) { free(*it); } m_ProcessedFiles.clear(); m_sourceFiles.clear(); free(m_szErrMsg); m_szErrMsg = NULL; } void ParChecker::SetDestDir(const char * szDestDir) { free(m_szDestDir); m_szDestDir = strdup(szDestDir); } void ParChecker::SetNZBName(const char * szNZBName) { free(m_szNZBName); m_szNZBName = strdup(szNZBName); } void ParChecker::SetInfoName(const char * szInfoName) { free(m_szInfoName); m_szInfoName = strdup(szInfoName); } void ParChecker::Run() { ParCoordinator::FileList fileList; if (!ParCoordinator::FindMainPars(m_szDestDir, &fileList)) { PrintMessage(Message::mkError, "Could not start par-check for %s. Could not find any par-files", m_szNZBName); m_eStatus = psFailed; Completed(); return; } m_eStatus = psRepairNotNeeded; m_bCancelled = false; for (ParCoordinator::FileList::iterator it = fileList.begin(); it != fileList.end(); it++) { char* szParFilename = *it; debug("Found par: %s", szParFilename); if (!IsStopped() && !m_bCancelled) { char szFullParFilename[1024]; snprintf(szFullParFilename, 1024, "%s%c%s", m_szDestDir, (int)PATH_SEPARATOR, szParFilename); szFullParFilename[1024-1] = '\0'; char szInfoName[1024]; int iBaseLen = 0; ParCoordinator::ParseParFilename(szParFilename, &iBaseLen, NULL); int maxlen = iBaseLen < 1024 ? iBaseLen : 1024 - 1; strncpy(szInfoName, szParFilename, maxlen); szInfoName[maxlen] = '\0'; char szParInfoName[1024]; snprintf(szParInfoName, 1024, "%s%c%s", m_szNZBName, (int)PATH_SEPARATOR, szInfoName); szParInfoName[1024-1] = '\0'; SetInfoName(szParInfoName); EStatus eStatus = RunParCheck(szFullParFilename); // accumulate total status, the worst status has priority if (m_eStatus > eStatus) { m_eStatus = eStatus; } if (g_pOptions->GetCreateBrokenLog()) { WriteBrokenLog(eStatus); } } free(szParFilename); } Completed(); } ParChecker::EStatus ParChecker::RunParCheck(const char* szParFilename) { Cleanup(); m_szParFilename = szParFilename; m_eStage = ptLoadingPars; m_iProcessedFiles = 0; m_iExtraFiles = 0; m_bVerifyingExtraFiles = false; EStatus eStatus = psFailed; PrintMessage(Message::mkInfo, "Verifying %s", m_szInfoName); debug("par: %s", m_szParFilename); snprintf(m_szProgressLabel, 1024, "Verifying %s", m_szInfoName); m_szProgressLabel[1024-1] = '\0'; m_iFileProgress = 0; m_iStageProgress = 0; UpdateProgress(); Result res = (Result)PreProcessPar(); if (IsStopped() || res != eSuccess) { Cleanup(); return psFailed; } m_eStage = ptVerifyingSources; Repairer* pRepairer = (Repairer*)m_pRepairer; res = pRepairer->Process(false); debug("ParChecker: Process-result=%i", res); if (!IsStopped() && pRepairer->missingfilecount > 0 && g_pOptions->GetParScan() == Options::psAuto && AddMissingFiles()) { res = pRepairer->Process(false); debug("ParChecker: Process-result=%i", res); } if (!IsStopped() && res == eRepairNotPossible && CheckSplittedFragments()) { pRepairer->UpdateVerificationResults(); res = pRepairer->Process(false); debug("ParChecker: Process-result=%i", res); } if (!IsStopped() && res == eRepairNotPossible) { res = (Result)ProcessMorePars(); } if (IsStopped()) { Cleanup(); return psFailed; } eStatus = psFailed; if (res == eSuccess) { PrintMessage(Message::mkInfo, "Repair not needed for %s", m_szInfoName); eStatus = psRepairNotNeeded; } else if (res == eRepairPossible) { eStatus = psRepairPossible; if (g_pOptions->GetParRepair()) { PrintMessage(Message::mkInfo, "Repairing %s", m_szInfoName); SaveSourceList(); snprintf(m_szProgressLabel, 1024, "Repairing %s", m_szInfoName); m_szProgressLabel[1024-1] = '\0'; m_iFileProgress = 0; m_iStageProgress = 0; m_iProcessedFiles = 0; m_eStage = ptRepairing; m_iFilesToRepair = pRepairer->damagedfilecount + pRepairer->missingfilecount; UpdateProgress(); res = pRepairer->Process(true); debug("ParChecker: Process-result=%i", res); if (res == eSuccess) { PrintMessage(Message::mkInfo, "Successfully repaired %s", m_szInfoName); eStatus = psRepaired; DeleteLeftovers(); } } else { PrintMessage(Message::mkInfo, "Repair possible for %s", m_szInfoName); } } if (m_bCancelled) { if (m_eStage >= ptRepairing) { PrintMessage(Message::mkWarning, "Repair cancelled for %s", m_szInfoName); m_szErrMsg = strdup("repair cancelled"); eStatus = psRepairPossible; } else { PrintMessage(Message::mkWarning, "Par-check cancelled for %s", m_szInfoName); m_szErrMsg = strdup("par-check cancelled"); eStatus = psFailed; } } else if (eStatus == psFailed) { if (!m_szErrMsg && (int)res >= 0 && (int)res <= 8) { m_szErrMsg = strdup(Par2CmdLineErrStr[res]); } PrintMessage(Message::mkError, "Repair failed for %s: %s", m_szInfoName, m_szErrMsg ? m_szErrMsg : ""); } Cleanup(); return eStatus; } int ParChecker::PreProcessPar() { Result res = eRepairFailed; while (!IsStopped() && res != eSuccess) { Cleanup(); Repairer* pRepairer = new Repairer(); m_pRepairer = pRepairer; pRepairer->sig_filename.connect(sigc::mem_fun(*this, &ParChecker::signal_filename)); pRepairer->sig_progress.connect(sigc::mem_fun(*this, &ParChecker::signal_progress)); pRepairer->sig_done.connect(sigc::mem_fun(*this, &ParChecker::signal_done)); res = pRepairer->PreProcess(m_szParFilename); debug("ParChecker: PreProcess-result=%i", res); if (IsStopped()) { PrintMessage(Message::mkError, "Could not verify %s: stopping", m_szInfoName); m_szErrMsg = strdup("par-check was stopped"); return eRepairFailed; } if (res == eInvalidCommandLineArguments) { PrintMessage(Message::mkError, "Could not start par-check for %s. Par-file: %s", m_szInfoName, m_szParFilename); m_szErrMsg = strdup("Command line could not be parsed"); return res; } if (res != eSuccess) { PrintMessage(Message::mkWarning, "Could not verify %s: par2-file could not be processed", m_szInfoName); PrintMessage(Message::mkInfo, "Requesting more par2-files for %s", m_szInfoName); bool bHasMorePars = LoadMainParBak(); if (!bHasMorePars) { PrintMessage(Message::mkWarning, "No more par2-files found"); break; } } } if (res != eSuccess) { PrintMessage(Message::mkError, "Could not verify %s: par2-file could not be processed", m_szInfoName); m_szErrMsg = strdup("par2-file could not be processed"); return res; } return res; } bool ParChecker::LoadMainParBak() { while (!IsStopped()) { m_mutexQueuedParFiles.Lock(); bool hasMorePars = !m_QueuedParFiles.empty(); for (FileList::iterator it = m_QueuedParFiles.begin(); it != m_QueuedParFiles.end() ;it++) { free(*it); } m_QueuedParFiles.clear(); m_mutexQueuedParFiles.Unlock(); if (hasMorePars) { return true; } int iBlockFound = 0; bool requested = RequestMorePars(1, &iBlockFound); if (requested) { strncpy(m_szProgressLabel, "Awaiting additional par-files", 1024); m_szProgressLabel[1024-1] = '\0'; m_iFileProgress = 0; UpdateProgress(); } m_mutexQueuedParFiles.Lock(); hasMorePars = !m_QueuedParFiles.empty(); m_bQueuedParFilesChanged = false; m_mutexQueuedParFiles.Unlock(); if (!requested && !hasMorePars) { return false; } if (!hasMorePars) { // wait until new files are added by "AddParFile" or a change is signaled by "QueueChanged" bool bQueuedParFilesChanged = false; while (!bQueuedParFilesChanged && !IsStopped()) { m_mutexQueuedParFiles.Lock(); bQueuedParFilesChanged = m_bQueuedParFilesChanged; m_mutexQueuedParFiles.Unlock(); usleep(100 * 1000); } } } return false; } int ParChecker::ProcessMorePars() { Result res = eRepairNotPossible; Repairer* pRepairer = (Repairer*)m_pRepairer; bool bMoreFilesLoaded = true; while (!IsStopped() && res == eRepairNotPossible) { int missingblockcount = pRepairer->missingblockcount - pRepairer->recoverypacketmap.size(); if (bMoreFilesLoaded) { PrintMessage(Message::mkInfo, "Need more %i par-block(s) for %s", missingblockcount, m_szInfoName); } m_mutexQueuedParFiles.Lock(); bool hasMorePars = !m_QueuedParFiles.empty(); m_mutexQueuedParFiles.Unlock(); if (!hasMorePars) { int iBlockFound = 0; bool requested = RequestMorePars(missingblockcount, &iBlockFound); if (requested) { strncpy(m_szProgressLabel, "Awaiting additional par-files", 1024); m_szProgressLabel[1024-1] = '\0'; m_iFileProgress = 0; UpdateProgress(); } m_mutexQueuedParFiles.Lock(); hasMorePars = !m_QueuedParFiles.empty(); m_bQueuedParFilesChanged = false; m_mutexQueuedParFiles.Unlock(); if (!requested && !hasMorePars) { m_szErrMsg = (char*)malloc(1024); snprintf(m_szErrMsg, 1024, "not enough par-blocks, %i block(s) needed, but %i block(s) available", missingblockcount, iBlockFound); m_szErrMsg[1024-1] = '\0'; break; } if (!hasMorePars) { // wait until new files are added by "AddParFile" or a change is signaled by "QueueChanged" bool bQueuedParFilesChanged = false; while (!bQueuedParFilesChanged && !IsStopped()) { m_mutexQueuedParFiles.Lock(); bQueuedParFilesChanged = m_bQueuedParFilesChanged; m_mutexQueuedParFiles.Unlock(); usleep(100 * 1000); } } } if (IsStopped()) { break; } bMoreFilesLoaded = LoadMorePars(); if (bMoreFilesLoaded) { pRepairer->UpdateVerificationResults(); res = pRepairer->Process(false); debug("ParChecker: Process-result=%i", res); } } return res; } bool ParChecker::LoadMorePars() { m_mutexQueuedParFiles.Lock(); FileList moreFiles; moreFiles.assign(m_QueuedParFiles.begin(), m_QueuedParFiles.end()); m_QueuedParFiles.clear(); m_mutexQueuedParFiles.Unlock(); for (FileList::iterator it = moreFiles.begin(); it != moreFiles.end() ;it++) { char* szParFilename = *it; bool loadedOK = ((Repairer*)m_pRepairer)->LoadPacketsFromFile(szParFilename); if (loadedOK) { PrintMessage(Message::mkInfo, "File %s successfully loaded for par-check", Util::BaseFileName(szParFilename), m_szInfoName); } else { PrintMessage(Message::mkInfo, "Could not load file %s for par-check", Util::BaseFileName(szParFilename), m_szInfoName); } free(szParFilename); } return !moreFiles.empty(); } void ParChecker::AddParFile(const char * szParFilename) { m_mutexQueuedParFiles.Lock(); m_QueuedParFiles.push_back(strdup(szParFilename)); m_bQueuedParFilesChanged = true; m_mutexQueuedParFiles.Unlock(); } void ParChecker::QueueChanged() { m_mutexQueuedParFiles.Lock(); m_bQueuedParFilesChanged = true; m_mutexQueuedParFiles.Unlock(); } bool ParChecker::CheckSplittedFragments() { bool bFragmentsAdded = false; for (std::vector::iterator it = ((Repairer*)m_pRepairer)->sourcefiles.begin(); it != ((Repairer*)m_pRepairer)->sourcefiles.end(); it++) { Par2RepairerSourceFile *sourcefile = *it; if (AddSplittedFragments(sourcefile->TargetFileName().c_str())) { bFragmentsAdded = true; } } return bFragmentsAdded; } bool ParChecker::AddSplittedFragments(const char* szFilename) { char szDirectory[1024]; strncpy(szDirectory, szFilename, 1024); szDirectory[1024-1] = '\0'; char* szBasename = Util::BaseFileName(szDirectory); if (szBasename == szDirectory) { return false; } szBasename[-1] = '\0'; int iBaseLen = strlen(szBasename); std::list extrafiles; DirBrowser dir(szDirectory); while (const char* filename = dir.Next()) { if (!strncasecmp(filename, szBasename, iBaseLen)) { const char* p = filename + iBaseLen; if (*p == '.') { for (p++; *p && strchr("0123456789", *p); p++) ; if (!*p) { debug("Found splitted fragment %s", filename); char fullfilename[1024]; snprintf(fullfilename, 1024, "%s%c%s", szDirectory, PATH_SEPARATOR, filename); fullfilename[1024-1] = '\0'; CommandLine::ExtraFile extrafile(fullfilename, Util::FileSize(fullfilename)); extrafiles.push_back(extrafile); } } } } bool bFragmentsAdded = false; if (!extrafiles.empty()) { m_iExtraFiles += extrafiles.size(); m_bVerifyingExtraFiles = true; bFragmentsAdded = ((Repairer*)m_pRepairer)->VerifyExtraFiles(extrafiles); m_bVerifyingExtraFiles = false; } return bFragmentsAdded; } bool ParChecker::AddMissingFiles() { PrintMessage(Message::mkInfo, "Performing extra par-scan for %s", m_szInfoName); char szDirectory[1024]; strncpy(szDirectory, m_szParFilename, 1024); szDirectory[1024-1] = '\0'; char* szBasename = Util::BaseFileName(szDirectory); if (szBasename == szDirectory) { return false; } szBasename[-1] = '\0'; std::list extrafiles; DirBrowser dir(szDirectory); while (const char* filename = dir.Next()) { if (strcmp(filename, ".") && strcmp(filename, "..") && strcmp(filename, "_brokenlog.txt")) { bool bAlreadyScanned = false; for (FileList::iterator it = m_ProcessedFiles.begin(); it != m_ProcessedFiles.end(); it++) { const char* szProcessedFilename = *it; if (!strcasecmp(Util::BaseFileName(szProcessedFilename), filename)) { bAlreadyScanned = true; break; } } if (!bAlreadyScanned) { char fullfilename[1024]; snprintf(fullfilename, 1024, "%s%c%s", szDirectory, PATH_SEPARATOR, filename); fullfilename[1024-1] = '\0'; extrafiles.push_back(new CommandLine::ExtraFile(fullfilename, Util::FileSize(fullfilename))); } } } // Sort the list char* szBaseParFilename = strdup(Util::BaseFileName(m_szParFilename)); if (char* ext = strrchr(szBaseParFilename, '.')) *ext = '\0'; // trim extension extrafiles.sort(MissingFilesComparator(szBaseParFilename)); free(szBaseParFilename); // Scan files bool bFilesAdded = false; if (!extrafiles.empty()) { m_iExtraFiles += extrafiles.size(); m_bVerifyingExtraFiles = true; std::list extrafiles1; // adding files one by one until all missing files are found while (!IsStopped() && !m_bCancelled && extrafiles.size() > 0 && ((Repairer*)m_pRepairer)->missingfilecount > 0) { CommandLine::ExtraFile* pExtraFile = extrafiles.front(); extrafiles.pop_front(); extrafiles1.clear(); extrafiles1.push_back(*pExtraFile); bFilesAdded = ((Repairer*)m_pRepairer)->VerifyExtraFiles(extrafiles1) || bFilesAdded; ((Repairer*)m_pRepairer)->UpdateVerificationResults(); delete pExtraFile; } m_bVerifyingExtraFiles = false; // free any remaining objects for (std::list::iterator it = extrafiles.begin(); it != extrafiles.end() ;it++) { delete *it; } } return bFilesAdded; } void ParChecker::signal_filename(std::string str) { const char* szStageMessage[] = { "Loading file", "Verifying file", "Repairing file", "Verifying repaired file" }; if (m_eStage == ptRepairing) { m_eStage = ptVerifyingRepaired; } PrintMessage(Message::mkInfo, "%s %s", szStageMessage[m_eStage], str.c_str()); if (m_eStage == ptLoadingPars || m_eStage == ptVerifyingSources) { m_ProcessedFiles.push_back(strdup(str.c_str())); } snprintf(m_szProgressLabel, 1024, "%s %s", szStageMessage[m_eStage], str.c_str()); m_szProgressLabel[1024-1] = '\0'; m_iFileProgress = 0; UpdateProgress(); } void ParChecker::signal_progress(double progress) { m_iFileProgress = (int)progress; if (m_eStage == ptRepairing) { // calculating repair-data for all files m_iStageProgress = m_iFileProgress; } else { // processing individual files int iTotalFiles = 0; if (m_eStage == ptVerifyingRepaired) { // repairing individual files iTotalFiles = m_iFilesToRepair; } else { // verifying individual files iTotalFiles = ((Repairer*)m_pRepairer)->sourcefiles.size() + m_iExtraFiles; } if (iTotalFiles > 0) { if (m_iFileProgress < 1000) { m_iStageProgress = (m_iProcessedFiles * 1000 + m_iFileProgress) / iTotalFiles; } else { m_iStageProgress = m_iProcessedFiles * 1000 / iTotalFiles; } } else { m_iStageProgress = 0; } } debug("Current-progres: %i, Total-progress: %i", m_iFileProgress, m_iStageProgress); UpdateProgress(); } void ParChecker::signal_done(std::string str, int available, int total) { m_iProcessedFiles++; if (m_eStage == ptVerifyingSources) { if (available < total && !m_bVerifyingExtraFiles) { bool bFileExists = true; for (std::vector::iterator it = ((Repairer*)m_pRepairer)->sourcefiles.begin(); it != ((Repairer*)m_pRepairer)->sourcefiles.end(); it++) { Par2RepairerSourceFile *sourcefile = *it; if (sourcefile && !strcmp(str.c_str(), Util::BaseFileName(sourcefile->TargetFileName().c_str())) && !sourcefile->GetTargetExists()) { bFileExists = false; break; } } if (bFileExists) { PrintMessage(Message::mkWarning, "File %s has %i bad block(s) of total %i block(s)", str.c_str(), total - available, total); } else { PrintMessage(Message::mkWarning, "File %s with %i block(s) is missing", str.c_str(), total); } } } } void ParChecker::Cancel() { #ifdef HAVE_PAR2_CANCEL ((Repairer*)m_pRepairer)->cancelled = true; m_bCancelled = true; #else PrintMessage(Message::mkError, "Could not cancel par-repair. The program was compiled using version of libpar2 which doesn't support cancelling of par-repair. Please apply libpar2-patches supplied with NZBGet and recompile libpar2 and NZBGet (see README for details)."); #endif } void ParChecker::WriteBrokenLog(EStatus eStatus) { char szBrokenLogName[1024]; snprintf(szBrokenLogName, 1024, "%s%c_brokenlog.txt", m_szDestDir, (int)PATH_SEPARATOR); szBrokenLogName[1024-1] = '\0'; if (eStatus != psRepairNotNeeded || Util::FileExists(szBrokenLogName)) { FILE* file = fopen(szBrokenLogName, "ab"); if (file) { if (eStatus == psFailed) { if (m_bCancelled) { fprintf(file, "Repair cancelled for %s\n", m_szInfoName); } else { fprintf(file, "Repair failed for %s: %s\n", m_szInfoName, m_szErrMsg ? m_szErrMsg : ""); } } else if (eStatus == psRepairPossible) { fprintf(file, "Repair possible for %s\n", m_szInfoName); } else if (eStatus == psRepaired) { fprintf(file, "Successfully repaired %s\n", m_szInfoName); } else if (eStatus == psRepairNotNeeded) { fprintf(file, "Repair not needed for %s\n", m_szInfoName); } fclose(file); } else { PrintMessage(Message::mkError, "Could not open file %s", szBrokenLogName); } } } void ParChecker::SaveSourceList() { // Buliding a list of DiskFile-objects, marked as source-files for (std::vector::iterator it = ((Repairer*)m_pRepairer)->sourcefiles.begin(); it != ((Repairer*)m_pRepairer)->sourcefiles.end(); it++) { Par2RepairerSourceFile* sourcefile = (Par2RepairerSourceFile*)*it; vector::iterator it2 = sourcefile->SourceBlocks(); for (int i = 0; i < (int)sourcefile->BlockCount(); i++, it2++) { DataBlock block = *it2; DiskFile* pSourceFile = block.GetDiskFile(); if (pSourceFile && std::find(m_sourceFiles.begin(), m_sourceFiles.end(), pSourceFile) == m_sourceFiles.end()) { m_sourceFiles.push_back(pSourceFile); } } } } void ParChecker::DeleteLeftovers() { // After repairing check if all DiskFile-objects saved by "SaveSourceList()" have // corresponding target-files. If not - the source file was replaced. In this case // the DiskFile-object points to the renamed bak-file, which we can delete. for (SourceList::iterator it = m_sourceFiles.begin(); it != m_sourceFiles.end(); it++) { DiskFile* pSourceFile = (DiskFile*)*it; bool bFound = false; for (std::vector::iterator it2 = ((Repairer*)m_pRepairer)->sourcefiles.begin(); it2 != ((Repairer*)m_pRepairer)->sourcefiles.end(); it2++) { Par2RepairerSourceFile* sourcefile = *it2; if (sourcefile->GetTargetFile() == pSourceFile) { bFound = true; break; } } if (!bFound) { PrintMessage(Message::mkInfo, "Deleting file %s", Util::BaseFileName(pSourceFile->FileName().c_str())); remove(pSourceFile->FileName().c_str()); } } } #endif nzbget-12.0+dfsg/ParChecker.h000066400000000000000000000072731226450633000160360ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 898 $ * $Date: 2013-10-30 21:06:18 +0100 (Wed, 30 Oct 2013) $ * */ #ifndef PARCHECKER_H #define PARCHECKER_H #ifndef DISABLE_PARCHECK #include #include #include "Thread.h" #include "Log.h" class ParChecker : public Thread { public: enum EStatus { psFailed, psRepairPossible, psRepaired, psRepairNotNeeded }; enum EStage { ptLoadingPars, ptVerifyingSources, ptRepairing, ptVerifyingRepaired, }; typedef std::deque FileList; typedef std::deque SourceList; private: char* m_szInfoName; char* m_szDestDir; char* m_szNZBName; const char* m_szParFilename; EStatus m_eStatus; EStage m_eStage; void* m_pRepairer; // declared as void* to prevent the including of libpar2-headers into this header-file char* m_szErrMsg; FileList m_QueuedParFiles; Mutex m_mutexQueuedParFiles; bool m_bQueuedParFilesChanged; FileList m_ProcessedFiles; int m_iProcessedFiles; int m_iFilesToRepair; int m_iExtraFiles; bool m_bVerifyingExtraFiles; char* m_szProgressLabel; int m_iFileProgress; int m_iStageProgress; bool m_bCancelled; SourceList m_sourceFiles; void Cleanup(); EStatus RunParCheck(const char* szParFilename); int PreProcessPar(); bool LoadMainParBak(); int ProcessMorePars(); bool LoadMorePars(); bool CheckSplittedFragments(); bool AddSplittedFragments(const char* szFilename); bool AddMissingFiles(); void WriteBrokenLog(EStatus eStatus); void SaveSourceList(); void DeleteLeftovers(); void signal_filename(std::string str); void signal_progress(double progress); void signal_done(std::string str, int available, int total); protected: /** * Unpause par2-files * returns true, if the files with required number of blocks were unpaused, * or false if there are no more files in queue for this collection or not enough blocks */ virtual bool RequestMorePars(int iBlockNeeded, int* pBlockFound) = 0; virtual void UpdateProgress() {} virtual void Completed() {} virtual void PrintMessage(Message::EKind eKind, const char* szFormat, ...) {} EStage GetStage() { return m_eStage; } const char* GetProgressLabel() { return m_szProgressLabel; } int GetFileProgress() { return m_iFileProgress; } int GetStageProgress() { return m_iStageProgress; } public: ParChecker(); virtual ~ParChecker(); virtual void Run(); void SetDestDir(const char* szDestDir); const char* GetParFilename() { return m_szParFilename; } const char* GetInfoName() { return m_szInfoName; } void SetInfoName(const char* szInfoName); void SetNZBName(const char* szNZBName); EStatus GetStatus() { return m_eStatus; } void AddParFile(const char* szParFilename); void QueueChanged(); void Cancel(); bool GetCancelled() { return m_bCancelled; } }; #endif #endif nzbget-12.0+dfsg/ParCoordinator.cpp000066400000000000000000000466261226450633000173150ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 919 $ * $Date: 2013-12-13 00:43:38 +0100 (Fri, 13 Dec 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #include #include #ifdef WIN32 #include #else #include #endif #include "nzbget.h" #include "ParCoordinator.h" #include "Options.h" #include "Log.h" #include "Util.h" #include "QueueCoordinator.h" #include "DiskState.h" extern QueueCoordinator* g_pQueueCoordinator; extern Options* g_pOptions; extern DiskState* g_pDiskState; #ifndef DISABLE_PARCHECK bool ParCoordinator::PostParChecker::RequestMorePars(int iBlockNeeded, int* pBlockFound) { return m_pOwner->RequestMorePars(m_pPostInfo->GetNZBInfo(), GetParFilename(), iBlockNeeded, pBlockFound); } void ParCoordinator::PostParChecker::UpdateProgress() { m_pOwner->UpdateParCheckProgress(); } void ParCoordinator::PostParChecker::PrintMessage(Message::EKind eKind, const char* szFormat, ...) { char szText[1024]; va_list args; va_start(args, szFormat); vsnprintf(szText, 1024, szFormat, args); va_end(args); szText[1024-1] = '\0'; m_pOwner->PrintMessage(m_pPostInfo, eKind, "%s", szText); } void ParCoordinator::PostParRenamer::UpdateProgress() { m_pOwner->UpdateParRenameProgress(); } void ParCoordinator::PostParRenamer::PrintMessage(Message::EKind eKind, const char* szFormat, ...) { char szText[1024]; va_list args; va_start(args, szFormat); vsnprintf(szText, 1024, szFormat, args); va_end(args); szText[1024-1] = '\0'; m_pOwner->PrintMessage(m_pPostInfo, eKind, "%s", szText); } #endif ParCoordinator::ParCoordinator() { debug("Creating ParCoordinator"); #ifndef DISABLE_PARCHECK m_bStopped = false; m_ParChecker.m_pOwner = this; m_ParRenamer.m_pOwner = this; #endif } ParCoordinator::~ParCoordinator() { debug("Destroying ParCoordinator"); } #ifndef DISABLE_PARCHECK void ParCoordinator::Stop() { debug("Stopping ParCoordinator"); m_bStopped = true; if (m_ParChecker.IsRunning()) { m_ParChecker.Stop(); int iMSecWait = 5000; while (m_ParChecker.IsRunning() && iMSecWait > 0) { usleep(50 * 1000); iMSecWait -= 50; } if (m_ParChecker.IsRunning()) { warn("Terminating par-check for %s", m_ParChecker.GetInfoName()); m_ParChecker.Kill(); } } } #endif void ParCoordinator::PausePars(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo) { debug("ParCoordinator: Pausing pars"); for (FileQueue::iterator it = pDownloadQueue->GetFileQueue()->begin(); it != pDownloadQueue->GetFileQueue()->end(); it++) { FileInfo* pFileInfo = *it; if (pFileInfo->GetNZBInfo() == pNZBInfo) { g_pQueueCoordinator->GetQueueEditor()->LockedEditEntry(pDownloadQueue, pFileInfo->GetID(), false, QueueEditor::eaGroupPauseExtraPars, 0, NULL); break; } } } bool ParCoordinator::FindMainPars(const char* szPath, FileList* pFileList) { if (pFileList) { pFileList->clear(); } DirBrowser dir(szPath); while (const char* filename = dir.Next()) { int iBaseLen = 0; if (ParseParFilename(filename, &iBaseLen, NULL)) { if (!pFileList) { return true; } // check if the base file already added to list bool exists = false; for (FileList::iterator it = pFileList->begin(); it != pFileList->end(); it++) { const char* filename2 = *it; exists = SameParCollection(filename, filename2); if (exists) { break; } } if (!exists) { pFileList->push_back(strdup(filename)); } } } return pFileList && !pFileList->empty(); } bool ParCoordinator::SameParCollection(const char* szFilename1, const char* szFilename2) { int iBaseLen1 = 0, iBaseLen2 = 0; return ParseParFilename(szFilename1, &iBaseLen1, NULL) && ParseParFilename(szFilename2, &iBaseLen2, NULL) && iBaseLen1 == iBaseLen2 && !strncasecmp(szFilename1, szFilename2, iBaseLen1); } bool ParCoordinator::ParseParFilename(const char* szParFilename, int* iBaseNameLen, int* iBlocks) { char szFilename[1024]; strncpy(szFilename, szParFilename, 1024); szFilename[1024-1] = '\0'; for (char* p = szFilename; *p; p++) *p = tolower(*p); // convert string to lowercase int iLen = strlen(szFilename); if (iLen < 6) { return false; } // find last occurence of ".par2" and trim filename after it char* szEnd = szFilename; while (char* p = strstr(szEnd, ".par2")) szEnd = p + 5; *szEnd = '\0'; iLen = strlen(szFilename); if (iLen < 6) { return false; } if (strcasecmp(szFilename + iLen - 5, ".par2")) { return false; } *(szFilename + iLen - 5) = '\0'; int blockcnt = 0; char* p = strrchr(szFilename, '.'); if (p && !strncasecmp(p, ".vol", 4)) { char* b = strchr(p, '+'); if (!b) { b = strchr(p, '-'); } if (b) { blockcnt = atoi(b+1); *p = '\0'; } } if (iBaseNameLen) { *iBaseNameLen = strlen(szFilename); } if (iBlocks) { *iBlocks = blockcnt; } return true; } #ifndef DISABLE_PARCHECK /** * DownloadQueue must be locked prior to call of this function. */ void ParCoordinator::StartParCheckJob(PostInfo* pPostInfo) { m_eCurrentJob = jkParCheck; m_ParChecker.SetPostInfo(pPostInfo); m_ParChecker.SetDestDir(pPostInfo->GetNZBInfo()->GetDestDir()); m_ParChecker.SetNZBName(pPostInfo->GetNZBInfo()->GetName()); m_ParChecker.PrintMessage(Message::mkInfo, "Checking pars for %s", pPostInfo->GetInfoName()); pPostInfo->SetWorking(true); m_ParChecker.Start(); } /** * DownloadQueue must be locked prior to call of this function. */ void ParCoordinator::StartParRenameJob(PostInfo* pPostInfo) { const char* szDestDir = pPostInfo->GetNZBInfo()->GetDestDir(); char szFinalDir[1024]; if (pPostInfo->GetNZBInfo()->GetUnpackStatus() == NZBInfo::usSuccess) { pPostInfo->GetNZBInfo()->BuildFinalDirName(szFinalDir, 1024); szFinalDir[1024-1] = '\0'; szDestDir = szFinalDir; } m_eCurrentJob = jkParRename; m_ParRenamer.SetPostInfo(pPostInfo); m_ParRenamer.SetDestDir(szDestDir); m_ParRenamer.SetInfoName(pPostInfo->GetNZBInfo()->GetName()); m_ParRenamer.PrintMessage(Message::mkInfo, "Checking renamed files for %s", pPostInfo->GetNZBInfo()->GetName()); pPostInfo->SetWorking(true); m_ParRenamer.Start(); } bool ParCoordinator::Cancel() { if (m_eCurrentJob == jkParCheck) { #ifdef HAVE_PAR2_CANCEL if (!m_ParChecker.GetCancelled()) { debug("Cancelling par-repair for %s", m_ParChecker.GetInfoName()); m_ParChecker.Cancel(); return true; } #else warn("Cannot cancel par-repair for %s, used version of libpar2 does not support cancelling", m_ParChecker.GetInfoName()); #endif } else if (m_eCurrentJob == jkParRename) { if (!m_ParRenamer.GetCancelled()) { debug("Cancelling par-rename for %s", m_ParRenamer.GetInfoName()); m_ParRenamer.Cancel(); return true; } } return false; } /** * DownloadQueue must be locked prior to call of this function. */ bool ParCoordinator::AddPar(FileInfo* pFileInfo, bool bDeleted) { bool bSameCollection = m_ParChecker.IsRunning() && pFileInfo->GetNZBInfo() == m_ParChecker.GetPostInfo()->GetNZBInfo() && SameParCollection(pFileInfo->GetFilename(), Util::BaseFileName(m_ParChecker.GetParFilename())); if (bSameCollection && !bDeleted) { char szFullFilename[1024]; snprintf(szFullFilename, 1024, "%s%c%s", pFileInfo->GetNZBInfo()->GetDestDir(), (int)PATH_SEPARATOR, pFileInfo->GetFilename()); szFullFilename[1024-1] = '\0'; m_ParChecker.AddParFile(szFullFilename); if (g_pOptions->GetParPauseQueue()) { PauseDownload(); } } else { m_ParChecker.QueueChanged(); } return bSameCollection; } void ParCoordinator::ParCheckCompleted() { DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue(); PostInfo* pPostInfo = m_ParChecker.GetPostInfo(); // Update ParStatus (accumulate result) if ((m_ParChecker.GetStatus() == ParChecker::psRepaired || m_ParChecker.GetStatus() == ParChecker::psRepairNotNeeded) && pPostInfo->GetNZBInfo()->GetParStatus() <= NZBInfo::psSkipped) { pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::psSuccess); } else if (m_ParChecker.GetStatus() == ParChecker::psRepairPossible && pPostInfo->GetNZBInfo()->GetParStatus() != NZBInfo::psFailure) { pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::psRepairPossible); } else { pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::psFailure); } pPostInfo->SetWorking(false); pPostInfo->SetStage(PostInfo::ptQueued); if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState->SaveDownloadQueue(pDownloadQueue); } g_pQueueCoordinator->UnlockQueue(); } /** * Unpause par2-files * returns true, if the files with required number of blocks were unpaused, * or false if there are no more files in queue for this collection or not enough blocks */ bool ParCoordinator::RequestMorePars(NZBInfo* pNZBInfo, const char* szParFilename, int iBlockNeeded, int* pBlockFound) { DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue(); Blocks blocks; blocks.clear(); int iBlockFound = 0; int iCurBlockFound = 0; FindPars(pDownloadQueue, pNZBInfo, szParFilename, &blocks, true, true, &iCurBlockFound); iBlockFound += iCurBlockFound; if (iBlockFound < iBlockNeeded) { FindPars(pDownloadQueue, pNZBInfo, szParFilename, &blocks, true, false, &iCurBlockFound); iBlockFound += iCurBlockFound; } if (iBlockFound < iBlockNeeded) { FindPars(pDownloadQueue, pNZBInfo, szParFilename, &blocks, false, false, &iCurBlockFound); iBlockFound += iCurBlockFound; } if (iBlockFound >= iBlockNeeded) { // 1. first unpause all files with par-blocks less or equal iBlockNeeded // starting from the file with max block count. // if par-collection was built exponentially and all par-files present, // this step selects par-files with exact number of blocks we need. while (iBlockNeeded > 0) { BlockInfo* pBestBlockInfo = NULL; for (Blocks::iterator it = blocks.begin(); it != blocks.end(); it++) { BlockInfo* pBlockInfo = *it; if (pBlockInfo->m_iBlockCount <= iBlockNeeded && (!pBestBlockInfo || pBestBlockInfo->m_iBlockCount < pBlockInfo->m_iBlockCount)) { pBestBlockInfo = pBlockInfo; } } if (pBestBlockInfo) { if (pBestBlockInfo->m_pFileInfo->GetPaused()) { m_ParChecker.PrintMessage(Message::mkInfo, "Unpausing %s%c%s for par-recovery", pNZBInfo->GetName(), (int)PATH_SEPARATOR, pBestBlockInfo->m_pFileInfo->GetFilename()); pBestBlockInfo->m_pFileInfo->SetPaused(false); pBestBlockInfo->m_pFileInfo->SetExtraPriority(true); } iBlockNeeded -= pBestBlockInfo->m_iBlockCount; blocks.remove(pBestBlockInfo); delete pBestBlockInfo; } else { break; } } // 2. then unpause other files // this step only needed if the par-collection was built not exponentially // or not all par-files present (or some of them were corrupted) // this step is not optimal, but we hope, that the first step will work good // in most cases and we will not need the second step often while (iBlockNeeded > 0) { BlockInfo* pBlockInfo = blocks.front(); if (pBlockInfo->m_pFileInfo->GetPaused()) { m_ParChecker.PrintMessage(Message::mkInfo, "Unpausing %s%c%s for par-recovery", pNZBInfo->GetName(), (int)PATH_SEPARATOR, pBlockInfo->m_pFileInfo->GetFilename()); pBlockInfo->m_pFileInfo->SetPaused(false); pBlockInfo->m_pFileInfo->SetExtraPriority(true); } iBlockNeeded -= pBlockInfo->m_iBlockCount; } } g_pQueueCoordinator->UnlockQueue(); if (pBlockFound) { *pBlockFound = iBlockFound; } for (Blocks::iterator it = blocks.begin(); it != blocks.end(); it++) { delete *it; } blocks.clear(); bool bOK = iBlockNeeded <= 0; if (bOK && g_pOptions->GetParPauseQueue()) { UnpauseDownload(); } return bOK; } void ParCoordinator::FindPars(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, const char* szParFilename, Blocks* pBlocks, bool bStrictParName, bool bExactParName, int* pBlockFound) { *pBlockFound = 0; // extract base name from m_szParFilename (trim .par2-extension and possible .vol-part) char* szBaseParFilename = Util::BaseFileName(szParFilename); char szMainBaseFilename[1024]; int iMainBaseLen = 0; if (!ParseParFilename(szBaseParFilename, &iMainBaseLen, NULL)) { // should not happen error("Internal error: could not parse filename %s", szBaseParFilename); return; } int maxlen = iMainBaseLen < 1024 ? iMainBaseLen : 1024 - 1; strncpy(szMainBaseFilename, szBaseParFilename, maxlen); szMainBaseFilename[maxlen] = '\0'; for (char* p = szMainBaseFilename; *p; p++) *p = tolower(*p); // convert string to lowercase for (FileQueue::iterator it = pDownloadQueue->GetFileQueue()->begin(); it != pDownloadQueue->GetFileQueue()->end(); it++) { FileInfo* pFileInfo = *it; int iBlocks = 0; if (pFileInfo->GetNZBInfo() == pNZBInfo && ParseParFilename(pFileInfo->GetFilename(), NULL, &iBlocks) && iBlocks > 0) { bool bUseFile = true; if (bExactParName) { bUseFile = SameParCollection(pFileInfo->GetFilename(), Util::BaseFileName(szParFilename)); } else if (bStrictParName) { // the pFileInfo->GetFilename() may be not confirmed and may contain // additional texts if Subject could not be parsed correctly char szLoFileName[1024]; strncpy(szLoFileName, pFileInfo->GetFilename(), 1024); szLoFileName[1024-1] = '\0'; for (char* p = szLoFileName; *p; p++) *p = tolower(*p); // convert string to lowercase char szCandidateFileName[1024]; snprintf(szCandidateFileName, 1024, "%s.par2", szMainBaseFilename); szCandidateFileName[1024-1] = '\0'; if (!strstr(szLoFileName, szCandidateFileName)) { snprintf(szCandidateFileName, 1024, "%s.vol", szMainBaseFilename); szCandidateFileName[1024-1] = '\0'; bUseFile = strstr(szLoFileName, szCandidateFileName); } } bool bAlreadyAdded = false; // check if file is not in the list already if (bUseFile) { for (Blocks::iterator it = pBlocks->begin(); it != pBlocks->end(); it++) { BlockInfo* pBlockInfo = *it; if (pBlockInfo->m_pFileInfo == pFileInfo) { bAlreadyAdded = true; break; } } } // if it is a par2-file with blocks and it was from the same NZB-request // and it belongs to the same file collection (same base name), // then OK, we can use it if (bUseFile && !bAlreadyAdded) { BlockInfo* pBlockInfo = new BlockInfo(); pBlockInfo->m_pFileInfo = pFileInfo; pBlockInfo->m_iBlockCount = iBlocks; pBlocks->push_back(pBlockInfo); *pBlockFound += iBlocks; } } } } void ParCoordinator::UpdateParCheckProgress() { g_pQueueCoordinator->LockQueue(); PostInfo* pPostInfo = m_ParChecker.GetPostInfo(); if (m_ParChecker.GetFileProgress() == 0) { pPostInfo->SetProgressLabel(m_ParChecker.GetProgressLabel()); } pPostInfo->SetFileProgress(m_ParChecker.GetFileProgress()); pPostInfo->SetStageProgress(m_ParChecker.GetStageProgress()); PostInfo::EStage StageKind[] = { PostInfo::ptLoadingPars, PostInfo::ptVerifyingSources, PostInfo::ptRepairing, PostInfo::ptVerifyingRepaired }; PostInfo::EStage eStage = StageKind[m_ParChecker.GetStage()]; time_t tCurrent = time(NULL); if (!pPostInfo->GetStartTime()) { pPostInfo->SetStartTime(tCurrent); } if (pPostInfo->GetStage() != eStage) { pPostInfo->SetStage(eStage); pPostInfo->SetStageTime(tCurrent); } bool bParCancel = false; #ifdef HAVE_PAR2_CANCEL if (!m_ParChecker.GetCancelled()) { if ((g_pOptions->GetParTimeLimit() > 0) && m_ParChecker.GetStage() == ParChecker::ptRepairing && ((g_pOptions->GetParTimeLimit() > 5 && tCurrent - pPostInfo->GetStageTime() > 5 * 60) || (g_pOptions->GetParTimeLimit() <= 5 && tCurrent - pPostInfo->GetStageTime() > 1 * 60))) { // first five (or one) minutes elapsed, now can check the estimated time int iEstimatedRepairTime = (int)((tCurrent - pPostInfo->GetStartTime()) * 1000 / (pPostInfo->GetStageProgress() > 0 ? pPostInfo->GetStageProgress() : 1)); if (iEstimatedRepairTime > g_pOptions->GetParTimeLimit() * 60) { debug("Estimated repair time %i seconds", iEstimatedRepairTime); m_ParChecker.PrintMessage(Message::mkWarning, "Cancelling par-repair for %s, estimated repair time (%i minutes) exceeds allowed repair time", m_ParChecker.GetInfoName(), iEstimatedRepairTime / 60); bParCancel = true; } } } #endif if (bParCancel) { m_ParChecker.Cancel(); } g_pQueueCoordinator->UnlockQueue(); CheckPauseState(pPostInfo); } void ParCoordinator::CheckPauseState(PostInfo* pPostInfo) { if (g_pOptions->GetPausePostProcess()) { time_t tStageTime = pPostInfo->GetStageTime(); time_t tStartTime = pPostInfo->GetStartTime(); time_t tWaitTime = time(NULL); // wait until Post-processor is unpaused while (g_pOptions->GetPausePostProcess() && !m_bStopped) { usleep(100 * 1000); // update time stamps time_t tDelta = time(NULL) - tWaitTime; if (tStageTime > 0) { pPostInfo->SetStageTime(tStageTime + tDelta); } if (tStartTime > 0) { pPostInfo->SetStartTime(tStartTime + tDelta); } } } } void ParCoordinator::ParRenameCompleted() { DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue(); PostInfo* pPostInfo = m_ParRenamer.GetPostInfo(); pPostInfo->GetNZBInfo()->SetRenameStatus(m_ParRenamer.GetStatus() == ParRenamer::psSuccess ? NZBInfo::rsSuccess : NZBInfo::rsFailure); pPostInfo->SetWorking(false); pPostInfo->SetStage(PostInfo::ptQueued); if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState->SaveDownloadQueue(pDownloadQueue); } g_pQueueCoordinator->UnlockQueue(); } void ParCoordinator::UpdateParRenameProgress() { g_pQueueCoordinator->LockQueue(); PostInfo* pPostInfo = m_ParRenamer.GetPostInfo(); pPostInfo->SetProgressLabel(m_ParRenamer.GetProgressLabel()); pPostInfo->SetStageProgress(m_ParRenamer.GetStageProgress()); time_t tCurrent = time(NULL); if (!pPostInfo->GetStartTime()) { pPostInfo->SetStartTime(tCurrent); } if (pPostInfo->GetStage() != PostInfo::ptRenaming) { pPostInfo->SetStage(PostInfo::ptRenaming); pPostInfo->SetStageTime(tCurrent); } g_pQueueCoordinator->UnlockQueue(); CheckPauseState(pPostInfo); } void ParCoordinator::PrintMessage(PostInfo* pPostInfo, Message::EKind eKind, const char* szFormat, ...) { char szText[1024]; va_list args; va_start(args, szFormat); vsnprintf(szText, 1024, szFormat, args); va_end(args); szText[1024-1] = '\0'; pPostInfo->AppendMessage(eKind, szText); switch (eKind) { case Message::mkDetail: detail("%s", szText); break; case Message::mkInfo: info("%s", szText); break; case Message::mkWarning: warn("%s", szText); break; case Message::mkError: error("%s", szText); break; case Message::mkDebug: debug("%s", szText); break; } } #endif nzbget-12.0+dfsg/ParCoordinator.h000066400000000000000000000073041226450633000167500ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 679 $ * $Date: 2013-05-14 22:52:39 +0200 (Tue, 14 May 2013) $ * */ #ifndef PARCOORDINATOR_H #define PARCOORDINATOR_H #include #include #include "DownloadInfo.h" #ifndef DISABLE_PARCHECK #include "ParChecker.h" #include "ParRenamer.h" #endif class ParCoordinator { private: #ifndef DISABLE_PARCHECK class PostParChecker: public ParChecker { private: ParCoordinator* m_pOwner; PostInfo* m_pPostInfo; protected: virtual bool RequestMorePars(int iBlockNeeded, int* pBlockFound); virtual void UpdateProgress(); virtual void Completed() { m_pOwner->ParCheckCompleted(); } virtual void PrintMessage(Message::EKind eKind, const char* szFormat, ...); public: PostInfo* GetPostInfo() { return m_pPostInfo; } void SetPostInfo(PostInfo* pPostInfo) { m_pPostInfo = pPostInfo; } friend class ParCoordinator; }; class PostParRenamer: public ParRenamer { private: ParCoordinator* m_pOwner; PostInfo* m_pPostInfo; protected: virtual void UpdateProgress(); virtual void Completed() { m_pOwner->ParRenameCompleted(); } virtual void PrintMessage(Message::EKind eKind, const char* szFormat, ...); public: PostInfo* GetPostInfo() { return m_pPostInfo; } void SetPostInfo(PostInfo* pPostInfo) { m_pPostInfo = pPostInfo; } friend class ParCoordinator; }; struct BlockInfo { FileInfo* m_pFileInfo; int m_iBlockCount; }; typedef std::list Blocks; enum EJobKind { jkParCheck, jkParRename }; private: PostParChecker m_ParChecker; bool m_bStopped; PostParRenamer m_ParRenamer; EJobKind m_eCurrentJob; protected: virtual bool PauseDownload() = 0; virtual bool UnpauseDownload() = 0; void UpdateParCheckProgress(); void UpdateParRenameProgress(); void ParCheckCompleted(); void ParRenameCompleted(); void CheckPauseState(PostInfo* pPostInfo); bool RequestMorePars(NZBInfo* pNZBInfo, const char* szParFilename, int iBlockNeeded, int* pBlockFound); void PrintMessage(PostInfo* pPostInfo, Message::EKind eKind, const char* szFormat, ...); #endif public: typedef std::deque FileList; public: ParCoordinator(); virtual ~ParCoordinator(); static bool FindMainPars(const char* szPath, FileList* pFileList); static bool ParseParFilename(const char* szParFilename, int* iBaseNameLen, int* iBlocks); static bool SameParCollection(const char* szFilename1, const char* szFilename2); void PausePars(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo); #ifndef DISABLE_PARCHECK bool AddPar(FileInfo* pFileInfo, bool bDeleted); void FindPars(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, const char* szParFilename, Blocks* pBlocks, bool bStrictParName, bool bExactParName, int* pBlockFound); void StartParCheckJob(PostInfo* pPostInfo); void StartParRenameJob(PostInfo* pPostInfo); void Stop(); bool Cancel(); #endif }; #endif nzbget-12.0+dfsg/ParRenamer.cpp000066400000000000000000000173601226450633000164140ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 911 $ * $Date: 2013-11-24 20:29:52 +0100 (Sun, 24 Nov 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #ifndef DISABLE_PARCHECK #include #include #include #include #ifdef WIN32 #include #include #include #else #include #include #include #include #endif #include "nzbget.h" #include "ParRenamer.h" #include "ParCoordinator.h" #include "Log.h" #include "Options.h" #include "Util.h" extern Options* g_pOptions; class ParRenamerRepairer : public Par2Repairer { public: friend class ParRenamer; }; ParRenamer::FileHash::FileHash(const char* szFilename, const char* szHash) { m_szFilename = strdup(szFilename); m_szHash = strdup(szHash); } ParRenamer::FileHash::~FileHash() { free(m_szFilename); free(m_szHash); } ParRenamer::ParRenamer() { debug("Creating ParRenamer"); m_eStatus = psFailed; m_szDestDir = NULL; m_szInfoName = NULL; m_szProgressLabel = (char*)malloc(1024); m_iStageProgress = 0; m_bCancelled = false; } ParRenamer::~ParRenamer() { debug("Destroying ParRenamer"); free(m_szDestDir); free(m_szInfoName); free(m_szProgressLabel); Cleanup(); } void ParRenamer::Cleanup() { ClearHashList(); for (DirList::iterator it = m_DirList.begin(); it != m_DirList.end(); it++) { free(*it); } m_DirList.clear(); } void ParRenamer::ClearHashList() { for (FileHashList::iterator it = m_FileHashList.begin(); it != m_FileHashList.end(); it++) { delete *it; } m_FileHashList.clear(); } void ParRenamer::SetDestDir(const char * szDestDir) { free(m_szDestDir); m_szDestDir = strdup(szDestDir); } void ParRenamer::SetInfoName(const char * szInfoName) { free(m_szInfoName); m_szInfoName = strdup(szInfoName); } void ParRenamer::Cancel() { m_bCancelled = true; } void ParRenamer::Run() { Cleanup(); m_bCancelled = false; m_iFileCount = 0; m_iCurFile = 0; m_iRenamedCount = 0; m_eStatus = psFailed; snprintf(m_szProgressLabel, 1024, "Checking renamed files for %s", m_szInfoName); m_szProgressLabel[1024-1] = '\0'; m_iStageProgress = 0; UpdateProgress(); BuildDirList(m_szDestDir); for (DirList::iterator it = m_DirList.begin(); it != m_DirList.end(); it++) { char* szDestDir = *it; debug("Checking %s", szDestDir); ClearHashList(); LoadParFiles(szDestDir); CheckFiles(szDestDir); } if (m_bCancelled) { PrintMessage(Message::mkWarning, "Renaming cancelled for %s", m_szInfoName); } else if (m_iRenamedCount > 0) { PrintMessage(Message::mkInfo, "Successfully renamed %i file(s) for %s", m_iRenamedCount, m_szInfoName); m_eStatus = psSuccess; } else { PrintMessage(Message::mkInfo, "No renamed files found for %s", m_szInfoName); } Cleanup(); Completed(); } void ParRenamer::BuildDirList(const char* szDestDir) { m_DirList.push_back(strdup(szDestDir)); char* szFullFilename = (char*)malloc(1024); DirBrowser* pDirBrowser = new DirBrowser(szDestDir); while (const char* filename = pDirBrowser->Next()) { if (strcmp(filename, ".") && strcmp(filename, "..") && !m_bCancelled) { snprintf(szFullFilename, 1024, "%s%c%s", szDestDir, PATH_SEPARATOR, filename); szFullFilename[1024-1] = '\0'; if (Util::DirectoryExists(szFullFilename)) { BuildDirList(szFullFilename); } else { m_iFileCount++; } } } free(szFullFilename); delete pDirBrowser; } void ParRenamer::LoadParFiles(const char* szDestDir) { ParCoordinator::FileList parFileList; ParCoordinator::FindMainPars(szDestDir, &parFileList); for (ParCoordinator::FileList::iterator it = parFileList.begin(); it != parFileList.end(); it++) { char* szParFilename = *it; char szFullParFilename[1024]; snprintf(szFullParFilename, 1024, "%s%c%s", szDestDir, PATH_SEPARATOR, szParFilename); szFullParFilename[1024-1] = '\0'; LoadParFile(szFullParFilename); free(*it); } } void ParRenamer::LoadParFile(const char* szParFilename) { ParRenamerRepairer* pRepairer = new ParRenamerRepairer(); if (!pRepairer->LoadPacketsFromFile(szParFilename)) { PrintMessage(Message::mkWarning, "Could not load par2-file %s", szParFilename); delete pRepairer; return; } for (map::iterator it = pRepairer->sourcefilemap.begin(); it != pRepairer->sourcefilemap.end(); it++) { if (m_bCancelled) { break; } Par2RepairerSourceFile* sourceFile = (*it).second; m_FileHashList.push_back(new FileHash(sourceFile->GetDescriptionPacket()->FileName().c_str(), sourceFile->GetDescriptionPacket()->Hash16k().print().c_str())); } delete pRepairer; } void ParRenamer::CheckFiles(const char* szDestDir) { DirBrowser dir(szDestDir); while (const char* filename = dir.Next()) { if (strcmp(filename, ".") && strcmp(filename, "..") && !m_bCancelled) { char szFullFilename[1024]; snprintf(szFullFilename, 1024, "%s%c%s", szDestDir, PATH_SEPARATOR, filename); szFullFilename[1024-1] = '\0'; if (!Util::DirectoryExists(szFullFilename)) { snprintf(m_szProgressLabel, 1024, "Checking file %s", filename); m_szProgressLabel[1024-1] = '\0'; m_iStageProgress = m_iCurFile * 1000 / m_iFileCount; UpdateProgress(); m_iCurFile++; CheckFile(szDestDir, szFullFilename); } } } } void ParRenamer::CheckFile(const char* szDestDir, const char* szFilename) { debug("Computing hash for %s", szFilename); const int iBlockSize = 16*1024; FILE* pFile = fopen(szFilename, "rb"); if (!pFile) { PrintMessage(Message::mkError, "Could not open file %s", szFilename); return; } // load first 16K of the file into buffer void* pBuffer = malloc(iBlockSize); int iReadBytes = fread(pBuffer, 1, iBlockSize, pFile); int iError = ferror(pFile); if (iReadBytes != iBlockSize && iError) { PrintMessage(Message::mkError, "Could not read file %s", szFilename); return; } fclose(pFile); MD5Hash hash16k; MD5Context context; context.Update(pBuffer, iReadBytes); context.Final(hash16k); free(pBuffer); debug("file: %s; hash16k: %s", Util::BaseFileName(szFilename), hash16k.print().c_str()); for (FileHashList::iterator it = m_FileHashList.begin(); it != m_FileHashList.end(); it++) { FileHash* pFileHash = *it; if (!strcmp(pFileHash->GetHash(), hash16k.print().c_str())) { debug("Found correct filename: %s", pFileHash->GetFilename()); char szDstFilename[1024]; snprintf(szDstFilename, 1024, "%s%c%s", szDestDir, PATH_SEPARATOR, pFileHash->GetFilename()); szDstFilename[1024-1] = '\0'; if (!Util::FileExists(szDstFilename)) { PrintMessage(Message::mkInfo, "Renaming %s to %s", Util::BaseFileName(szFilename), pFileHash->GetFilename()); if (Util::MoveFile(szFilename, szDstFilename)) { m_iRenamedCount++; } else { PrintMessage(Message::mkError, "Could not rename %s to %s", szFilename, szDstFilename); } } break; } } } #endif nzbget-12.0+dfsg/ParRenamer.h000066400000000000000000000053511226450633000160560ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 783 $ * $Date: 2013-08-10 11:04:33 +0200 (Sat, 10 Aug 2013) $ * */ #ifndef PARRENAMER_H #define PARRENAMER_H #ifndef DISABLE_PARCHECK #include #include "Thread.h" #include "Log.h" class ParRenamer : public Thread { public: enum EStatus { psFailed, psSuccess }; class FileHash { private: char* m_szFilename; char* m_szHash; public: FileHash(const char* szFilename, const char* szHash); ~FileHash(); const char* GetFilename() { return m_szFilename; } const char* GetHash() { return m_szHash; } }; typedef std::deque FileHashList; typedef std::deque DirList; private: char* m_szInfoName; char* m_szDestDir; EStatus m_eStatus; char* m_szProgressLabel; int m_iStageProgress; bool m_bCancelled; DirList m_DirList; FileHashList m_FileHashList; int m_iFileCount; int m_iCurFile; int m_iRenamedCount; void Cleanup(); void ClearHashList(); void BuildDirList(const char* szDestDir); void CheckDir(const char* szDestDir); void LoadParFiles(const char* szDestDir); void LoadParFile(const char* szParFilename); void CheckFiles(const char* szDestDir); void CheckFile(const char* szDestDir, const char* szFilename); protected: virtual void UpdateProgress() {} virtual void Completed() {} virtual void PrintMessage(Message::EKind eKind, const char* szFormat, ...) {} const char* GetProgressLabel() { return m_szProgressLabel; } int GetStageProgress() { return m_iStageProgress; } public: ParRenamer(); virtual ~ParRenamer(); virtual void Run(); void SetDestDir(const char* szDestDir); const char* GetInfoName() { return m_szInfoName; } void SetInfoName(const char* szInfoName); void SetStatus(EStatus eStatus); EStatus GetStatus() { return m_eStatus; } void Cancel(); bool GetCancelled() { return m_bCancelled; } }; #endif #endif nzbget-12.0+dfsg/PrePostProcessor.cpp000066400000000000000000001276021226450633000176550ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 924 $ * $Date: 2013-12-21 22:39:49 +0100 (Sat, 21 Dec 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #ifdef WIN32 #include #else #include #endif #include #include #include "nzbget.h" #include "PrePostProcessor.h" #include "Options.h" #include "Log.h" #include "QueueCoordinator.h" #include "ScriptController.h" #include "DiskState.h" #include "Util.h" #include "Scheduler.h" #include "Scanner.h" #include "Unpack.h" #include "NZBFile.h" extern QueueCoordinator* g_pQueueCoordinator; extern Options* g_pOptions; extern DiskState* g_pDiskState; extern Scheduler* g_pScheduler; extern Scanner* g_pScanner; void PrePostProcessor::PostDupeCoordinator::HistoryRedownload(DownloadQueue* pDownloadQueue, HistoryInfo* pHistoryInfo) { HistoryList::iterator it = std::find(pDownloadQueue->GetHistoryList()->begin(), pDownloadQueue->GetHistoryList()->end(), pHistoryInfo); m_pOwner->HistoryRedownload(pDownloadQueue, it, pHistoryInfo, true); } PrePostProcessor::PrePostProcessor() { debug("Creating PrePostProcessor"); m_bHasMoreJobs = false; m_bPostPause = false; m_QueueCoordinatorObserver.m_pOwner = this; g_pQueueCoordinator->Attach(&m_QueueCoordinatorObserver); #ifndef DISABLE_PARCHECK m_ParCoordinator.m_pOwner = this; #endif m_DupeCoordinator.m_pOwner = this; } PrePostProcessor::~PrePostProcessor() { debug("Destroying PrePostProcessor"); } void PrePostProcessor::Cleanup() { debug("Cleaning up PrePostProcessor"); DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue(); for (PostQueue::iterator it = pDownloadQueue->GetPostQueue()->begin(); it != pDownloadQueue->GetPostQueue()->end(); it++) { delete *it; } pDownloadQueue->GetPostQueue()->clear(); for (FileQueue::iterator it = pDownloadQueue->GetParkedFiles()->begin(); it != pDownloadQueue->GetParkedFiles()->end(); it++) { delete *it; } pDownloadQueue->GetParkedFiles()->clear(); for (HistoryList::iterator it = pDownloadQueue->GetHistoryList()->begin(); it != pDownloadQueue->GetHistoryList()->end(); it++) { delete *it; } pDownloadQueue->GetHistoryList()->clear(); g_pQueueCoordinator->UnlockQueue(); } void PrePostProcessor::Run() { debug("Entering PrePostProcessor-loop"); if (g_pOptions->GetServerMode() && g_pOptions->GetSaveQueue() && g_pOptions->GetReloadQueue() && g_pOptions->GetReloadPostQueue()) { DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue(); SanitisePostQueue(pDownloadQueue->GetPostQueue()); g_pQueueCoordinator->UnlockQueue(); } g_pScheduler->FirstCheck(); ApplySchedulerState(); int iDiskSpaceInterval = 1000; int iSchedulerInterval = 1000; int iHistoryInterval = 600000; const int iStepMSec = 200; while (!IsStopped()) { // check incoming nzb directory g_pScanner->Check(); if (!(g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2()) && g_pOptions->GetDiskSpace() > 0 && !g_pQueueCoordinator->GetStandBy() && iDiskSpaceInterval >= 1000) { // check free disk space every 1 second CheckDiskSpace(); iDiskSpaceInterval = 0; } iDiskSpaceInterval += iStepMSec; // check post-queue every 200 msec CheckPostQueue(); if (iSchedulerInterval >= 1000) { // check scheduler tasks every 1 second g_pScheduler->IntervalCheck(); ApplySchedulerState(); iSchedulerInterval = 0; CheckScheduledResume(); } iSchedulerInterval += iStepMSec; if (iHistoryInterval >= 600000) { // check history (remove old entries) every 10 minutes CheckHistory(); iHistoryInterval = 0; } iHistoryInterval += iStepMSec; usleep(iStepMSec * 1000); } Cleanup(); debug("Exiting PrePostProcessor-loop"); } void PrePostProcessor::Stop() { Thread::Stop(); DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue(); #ifndef DISABLE_PARCHECK m_ParCoordinator.Stop(); #endif if (!pDownloadQueue->GetPostQueue()->empty()) { PostInfo* pPostInfo = pDownloadQueue->GetPostQueue()->front(); if ((pPostInfo->GetStage() == PostInfo::ptUnpacking || pPostInfo->GetStage() == PostInfo::ptExecutingScript) && pPostInfo->GetPostThread()) { Thread* pPostThread = pPostInfo->GetPostThread(); pPostInfo->SetPostThread(NULL); pPostThread->SetAutoDestroy(true); pPostThread->Stop(); } } g_pQueueCoordinator->UnlockQueue(); } void PrePostProcessor::QueueCoordinatorUpdate(Subject * Caller, void * Aspect) { if (IsStopped()) { return; } QueueCoordinator::Aspect* pAspect = (QueueCoordinator::Aspect*)Aspect; if (pAspect->eAction == QueueCoordinator::eaNZBFileFound) { NZBFound(pAspect->pDownloadQueue, pAspect->pNZBInfo); } else if (pAspect->eAction == QueueCoordinator::eaNZBFileAdded) { NZBAdded(pAspect->pDownloadQueue, pAspect->pNZBInfo); } else if ((pAspect->eAction == QueueCoordinator::eaFileCompleted || pAspect->eAction == QueueCoordinator::eaFileDeleted)) { if ( #ifndef DISABLE_PARCHECK !m_ParCoordinator.AddPar(pAspect->pFileInfo, pAspect->eAction == QueueCoordinator::eaFileDeleted) && #endif IsNZBFileCompleted(pAspect->pDownloadQueue, pAspect->pNZBInfo, true, false) && (!pAspect->pFileInfo->GetPaused() || IsNZBFileCompleted(pAspect->pDownloadQueue, pAspect->pNZBInfo, false, false))) { if ((pAspect->eAction == QueueCoordinator::eaFileCompleted || (pAspect->pFileInfo->GetAutoDeleted() && IsNZBFileCompleted(pAspect->pDownloadQueue, pAspect->pNZBInfo, false, true))) && pAspect->pFileInfo->GetNZBInfo()->GetDeleteStatus() != NZBInfo::dsHealth) { info("Collection %s completely downloaded", pAspect->pNZBInfo->GetName()); NZBDownloaded(pAspect->pDownloadQueue, pAspect->pNZBInfo); } else if ((pAspect->eAction == QueueCoordinator::eaFileDeleted || (pAspect->eAction == QueueCoordinator::eaFileCompleted && pAspect->pFileInfo->GetNZBInfo()->GetDeleteStatus() > NZBInfo::dsNone)) && !pAspect->pNZBInfo->GetParCleanup() && !pAspect->pNZBInfo->GetPostProcess() && IsNZBFileCompleted(pAspect->pDownloadQueue, pAspect->pNZBInfo, false, true)) { info("Collection %s deleted from queue", pAspect->pNZBInfo->GetName()); NZBDeleted(pAspect->pDownloadQueue, pAspect->pNZBInfo); } } } } void PrePostProcessor::NZBFound(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo) { if (g_pOptions->GetDupeCheck() && pNZBInfo->GetDupeMode() != dmForce) { m_DupeCoordinator.NZBFound(pDownloadQueue, pNZBInfo); } } void PrePostProcessor::NZBAdded(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo) { if (g_pOptions->GetParCheck() != Options::pcForce) { m_ParCoordinator.PausePars(pDownloadQueue, pNZBInfo); } if (g_pOptions->GetDupeCheck() && pNZBInfo->GetDupeMode() != dmForce && pNZBInfo->GetDeleteStatus() == NZBInfo::dsDupe) { NZBCompleted(pDownloadQueue, pNZBInfo, false); } if (strlen(g_pOptions->GetNZBAddedProcess()) > 0) { NZBAddedScriptController::StartScript(pDownloadQueue, pNZBInfo, g_pOptions->GetNZBAddedProcess()); } } void PrePostProcessor::NZBDownloaded(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo) { if (!pNZBInfo->GetPostProcess() && g_pOptions->GetDecode()) { info("Queueing %s for post-processing", pNZBInfo->GetName()); PostInfo* pPostInfo = new PostInfo(); pPostInfo->SetNZBInfo(pNZBInfo); pPostInfo->SetInfoName(pNZBInfo->GetName()); if (pNZBInfo->GetParStatus() == NZBInfo::psNone && g_pOptions->GetParCheck() != Options::pcForce) { pNZBInfo->SetParStatus(NZBInfo::psSkipped); } if (pNZBInfo->GetRenameStatus() == NZBInfo::rsNone && !g_pOptions->GetParRename()) { pNZBInfo->SetRenameStatus(NZBInfo::rsSkipped); } if (pNZBInfo->GetDeleteStatus() != NZBInfo::dsNone) { pNZBInfo->SetParStatus(NZBInfo::psFailure); pNZBInfo->SetUnpackStatus(NZBInfo::usFailure); pNZBInfo->SetRenameStatus(NZBInfo::rsFailure); pNZBInfo->SetMoveStatus(NZBInfo::msFailure); } pNZBInfo->SetPostProcess(true); pDownloadQueue->GetPostQueue()->push_back(pPostInfo); SaveQueue(pDownloadQueue); m_bHasMoreJobs = true; } else { NZBCompleted(pDownloadQueue, pNZBInfo, true); } } void PrePostProcessor::NZBDeleted(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo) { if (pNZBInfo->GetDeleteStatus() == NZBInfo::dsNone) { pNZBInfo->SetDeleteStatus(NZBInfo::dsManual); } pNZBInfo->SetDeleting(false); if ((g_pOptions->GetDeleteCleanupDisk() && pNZBInfo->GetCleanupDisk()) || pNZBInfo->GetDeleteStatus() == NZBInfo::dsDupe) { // download was cancelled, deleting already downloaded files from disk for (NZBInfo::Files::reverse_iterator it = pNZBInfo->GetCompletedFiles()->rbegin(); it != pNZBInfo->GetCompletedFiles()->rend(); it++) { char* szFilename = *it; if (Util::FileExists(szFilename)) { detail("Deleting file %s", Util::BaseFileName(szFilename)); remove(szFilename); } } // delete .out.tmp-files and _brokenlog.txt DirBrowser dir(pNZBInfo->GetDestDir()); while (const char* szFilename = dir.Next()) { int iLen = strlen(szFilename); if ((iLen > 8 && !strcmp(szFilename + iLen - 8, ".out.tmp")) || !strcmp(szFilename, "_brokenlog.txt")) { char szFullFilename[1024]; snprintf(szFullFilename, 1024, "%s%c%s", pNZBInfo->GetDestDir(), PATH_SEPARATOR, szFilename); szFullFilename[1024-1] = '\0'; detail("Deleting file %s", szFilename); remove(szFullFilename); } } // delete old directory (if empty) if (Util::DirEmpty(pNZBInfo->GetDestDir())) { rmdir(pNZBInfo->GetDestDir()); } } if (pNZBInfo->GetDeleteStatus() == NZBInfo::dsHealth) { NZBDownloaded(pDownloadQueue, pNZBInfo); } else { NZBCompleted(pDownloadQueue, pNZBInfo, true); } } void PrePostProcessor::NZBCompleted(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, bool bSaveQueue) { bool bNeedSave = false; if (g_pOptions->GetKeepHistory() > 0 && !pNZBInfo->GetAvoidHistory()) { //remove old item for the same NZB for (HistoryList::iterator it = pDownloadQueue->GetHistoryList()->begin(); it != pDownloadQueue->GetHistoryList()->end(); it++) { HistoryInfo* pHistoryInfo = *it; if (pHistoryInfo->GetNZBInfo() == pNZBInfo) { delete pHistoryInfo; pDownloadQueue->GetHistoryList()->erase(it); break; } } HistoryInfo* pHistoryInfo = new HistoryInfo(pNZBInfo); pHistoryInfo->SetTime(time(NULL)); pDownloadQueue->GetHistoryList()->push_front(pHistoryInfo); // park files int iParkedFiles = 0; int index = 0; for (FileQueue::iterator it = pDownloadQueue->GetFileQueue()->begin(); it != pDownloadQueue->GetFileQueue()->end(); ) { FileInfo* pFileInfo = *it; if (pFileInfo->GetNZBInfo() == pNZBInfo && !pFileInfo->GetDeleted()) { detail("Park file %s", pFileInfo->GetFilename()); g_pQueueCoordinator->DiscardDiskFile(pFileInfo); pDownloadQueue->GetFileQueue()->erase(it); pDownloadQueue->GetParkedFiles()->push_back(pFileInfo); it = pDownloadQueue->GetFileQueue()->begin() + index; iParkedFiles++; } else { it++; index++; } } pNZBInfo->SetParkedFileCount(iParkedFiles); info("Collection %s added to history", pNZBInfo->GetName()); bNeedSave = true; } pNZBInfo->SetAvoidHistory(false); if (g_pOptions->GetDupeCheck() && pNZBInfo->GetDupeMode() != dmForce && (pNZBInfo->GetDeleteStatus() == NZBInfo::dsNone || pNZBInfo->GetDeleteStatus() == NZBInfo::dsHealth)) { m_DupeCoordinator.NZBCompleted(pDownloadQueue, pNZBInfo); bNeedSave = true; } if (bSaveQueue && bNeedSave) { SaveQueue(pDownloadQueue); } } /** * Removes old entries from (recent) history */ void PrePostProcessor::CheckHistory() { DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue(); time_t tMinTime = time(NULL) - g_pOptions->GetKeepHistory() * 60*60*24; bool bChanged = false; int index = 0; // traversing in a reverse order to delete items in order they were added to history // (just to produce the log-messages in a more logical order) for (HistoryList::reverse_iterator it = pDownloadQueue->GetHistoryList()->rbegin(); it != pDownloadQueue->GetHistoryList()->rend(); ) { HistoryInfo* pHistoryInfo = *it; if (pHistoryInfo->GetKind() != HistoryInfo::hkDupInfo && pHistoryInfo->GetTime() < tMinTime) { if (g_pOptions->GetDupeCheck() && pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo) { // replace history element m_DupeCoordinator.HistoryTransformToDup(pDownloadQueue, pHistoryInfo, index); index++; } else { char szNiceName[1024]; pHistoryInfo->GetName(szNiceName, 1024); pDownloadQueue->GetHistoryList()->erase(pDownloadQueue->GetHistoryList()->end() - 1 - index); delete pHistoryInfo; if (pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo) { DeleteQueuedFile(pHistoryInfo->GetNZBInfo()->GetQueuedFilename()); } info("Collection %s removed from history", szNiceName); } it = pDownloadQueue->GetHistoryList()->rbegin() + index; bChanged = true; } else { it++; index++; } } if (bChanged) { SaveQueue(pDownloadQueue); } g_pQueueCoordinator->UnlockQueue(); } void PrePostProcessor::DeleteQueuedFile(const char* szQueuedFile) { if (!g_pOptions->GetNzbCleanupDisk()) { return; } // szQueuedFile may contain one filename or several filenames separated // with "|"-character (for merged groups) char* szFilename = strdup(szQueuedFile); char* szEnd = szFilename - 1; while (szEnd) { char* szName1 = szEnd + 1; szEnd = strchr(szName1, '|'); if (szEnd) *szEnd = '\0'; if (Util::FileExists(szName1)) { info("Deleting file %s", szName1); remove(szName1); } } free(szFilename); } /** * Find ID of any file in the nzb-file */ int PrePostProcessor::FindGroupID(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo) { for (FileQueue::iterator it = pDownloadQueue->GetFileQueue()->begin(); it != pDownloadQueue->GetFileQueue()->end(); it++) { FileInfo* pFileInfo = *it; if (pFileInfo->GetNZBInfo() == pNZBInfo) { return pFileInfo->GetID(); break; } } return 0; } void PrePostProcessor::CheckDiskSpace() { long long lFreeSpace = Util::FreeDiskSize(g_pOptions->GetDestDir()); if (lFreeSpace > -1 && lFreeSpace / 1024 / 1024 < g_pOptions->GetDiskSpace()) { warn("Low disk space on %s. Pausing download", g_pOptions->GetDestDir()); g_pOptions->SetPauseDownload(true); } if (!Util::EmptyStr(g_pOptions->GetInterDir())) { lFreeSpace = Util::FreeDiskSize(g_pOptions->GetInterDir()); if (lFreeSpace > -1 && lFreeSpace / 1024 / 1024 < g_pOptions->GetDiskSpace()) { warn("Low disk space on %s. Pausing download", g_pOptions->GetInterDir()); g_pOptions->SetPauseDownload(true); } } } void PrePostProcessor::CheckPostQueue() { DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue(); if (!pDownloadQueue->GetPostQueue()->empty()) { PostInfo* pPostInfo = pDownloadQueue->GetPostQueue()->front(); if (!pPostInfo->GetWorking()) { #ifndef DISABLE_PARCHECK if (pPostInfo->GetRequestParCheck() && pPostInfo->GetNZBInfo()->GetParStatus() <= NZBInfo::psSkipped && g_pOptions->GetParCheck() != Options::pcManual) { pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::psNone); pPostInfo->SetRequestParCheck(false); pPostInfo->SetStage(PostInfo::ptQueued); pPostInfo->GetNZBInfo()->GetScriptStatuses()->Clear(); DeletePostThread(pPostInfo); } else if (pPostInfo->GetRequestParCheck() && pPostInfo->GetNZBInfo()->GetParStatus() <= NZBInfo::psSkipped && g_pOptions->GetParCheck() == Options::pcManual) { pPostInfo->SetRequestParCheck(false); pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::psManual); DeletePostThread(pPostInfo); FileInfo* pFileInfo = GetQueueGroup(pDownloadQueue, pPostInfo->GetNZBInfo()); if (pFileInfo) { info("Downloading all remaining files for manual par-check for %s", pPostInfo->GetNZBInfo()->GetName()); g_pQueueCoordinator->GetQueueEditor()->LockedEditEntry(pDownloadQueue, pFileInfo->GetID(), false, QueueEditor::eaGroupResume, 0, NULL); pPostInfo->SetStage(PostInfo::ptFinished); pPostInfo->GetNZBInfo()->SetPostProcess(false); } else { info("There are no par-files remain for download for %s", pPostInfo->GetNZBInfo()->GetName()); pPostInfo->SetStage(PostInfo::ptQueued); } } #endif if (pPostInfo->GetDeleted()) { pPostInfo->SetStage(PostInfo::ptFinished); } if (pPostInfo->GetStage() == PostInfo::ptQueued && !g_pOptions->GetPausePostProcess()) { DeletePostThread(pPostInfo); StartJob(pDownloadQueue, pPostInfo); } else if (pPostInfo->GetStage() == PostInfo::ptFinished) { UpdatePauseState(false, NULL); JobCompleted(pDownloadQueue, pPostInfo); } else if (!g_pOptions->GetPausePostProcess()) { error("Internal error: invalid state in post-processor"); } } } g_pQueueCoordinator->UnlockQueue(); } void PrePostProcessor::SaveQueue(DownloadQueue* pDownloadQueue) { if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState->SaveDownloadQueue(pDownloadQueue); } } /** * Reset the state of items after reloading from disk and * delete items which could not be resumed. */ void PrePostProcessor::SanitisePostQueue(PostQueue* pPostQueue) { for (PostQueue::iterator it = pPostQueue->begin(); it != pPostQueue->end(); it++) { PostInfo* pPostInfo = *it; if (pPostInfo->GetStage() == PostInfo::ptExecutingScript || !Util::DirectoryExists(pPostInfo->GetNZBInfo()->GetDestDir())) { pPostInfo->SetStage(PostInfo::ptFinished); } else { pPostInfo->SetStage(PostInfo::ptQueued); } } } void PrePostProcessor::DeletePostThread(PostInfo* pPostInfo) { delete pPostInfo->GetPostThread(); pPostInfo->SetPostThread(NULL); } void PrePostProcessor::StartJob(DownloadQueue* pDownloadQueue, PostInfo* pPostInfo) { #ifndef DISABLE_PARCHECK if (pPostInfo->GetNZBInfo()->GetRenameStatus() == NZBInfo::rsNone) { UpdatePauseState(g_pOptions->GetParPauseQueue(), "par-rename"); m_ParCoordinator.StartParRenameJob(pPostInfo); return; } else if (pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psNone) { if (m_ParCoordinator.FindMainPars(pPostInfo->GetNZBInfo()->GetDestDir(), NULL)) { UpdatePauseState(g_pOptions->GetParPauseQueue(), "par-check"); m_ParCoordinator.StartParCheckJob(pPostInfo); } else { info("Nothing to par-check for %s", pPostInfo->GetInfoName()); pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::psSkipped); pPostInfo->SetWorking(false); pPostInfo->SetStage(PostInfo::ptQueued); } return; } else if (pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psSkipped && pPostInfo->GetNZBInfo()->CalcHealth() < pPostInfo->GetNZBInfo()->CalcCriticalHealth() && m_ParCoordinator.FindMainPars(pPostInfo->GetNZBInfo()->GetDestDir(), NULL)) { warn("Skipping par-check for %s due to health %.1f%% below critical %.1f%%", pPostInfo->GetInfoName(), pPostInfo->GetNZBInfo()->CalcHealth() / 10.0, pPostInfo->GetNZBInfo()->CalcCriticalHealth() / 10.0); pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::psFailure); return; } else if (pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psSkipped && pPostInfo->GetNZBInfo()->GetFailedSize() - pPostInfo->GetNZBInfo()->GetParFailedSize() > 0 && m_ParCoordinator.FindMainPars(pPostInfo->GetNZBInfo()->GetDestDir(), NULL)) { info("Collection %s with health %.1f%% needs par-check", pPostInfo->GetInfoName(), pPostInfo->GetNZBInfo()->CalcHealth() / 10.0); pPostInfo->SetRequestParCheck(true); return; } #endif NZBParameter* pUnpackParameter = pPostInfo->GetNZBInfo()->GetParameters()->Find("*Unpack:", false); bool bUnpackParam = !(pUnpackParameter && !strcasecmp(pUnpackParameter->GetValue(), "no")); bool bUnpack = bUnpackParam && (pPostInfo->GetNZBInfo()->GetUnpackStatus() == NZBInfo::usNone); bool bParFailed = pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psFailure || pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psRepairPossible || pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psManual; bool bCleanup = !bUnpack && pPostInfo->GetNZBInfo()->GetCleanupStatus() == NZBInfo::csNone && (pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psSuccess || (pPostInfo->GetNZBInfo()->GetUnpackStatus() == NZBInfo::usSuccess && pPostInfo->GetNZBInfo()->GetParStatus() != NZBInfo::psFailure)) && strlen(g_pOptions->GetExtCleanupDisk()) > 0; bool bMoveInter = !bUnpack && pPostInfo->GetNZBInfo()->GetMoveStatus() == NZBInfo::msNone && pPostInfo->GetNZBInfo()->GetUnpackStatus() != NZBInfo::usFailure && pPostInfo->GetNZBInfo()->GetUnpackStatus() != NZBInfo::usSpace && pPostInfo->GetNZBInfo()->GetUnpackStatus() != NZBInfo::usPassword && pPostInfo->GetNZBInfo()->GetParStatus() != NZBInfo::psFailure && pPostInfo->GetNZBInfo()->GetParStatus() != NZBInfo::psManual && strlen(g_pOptions->GetInterDir()) > 0 && !strncmp(pPostInfo->GetNZBInfo()->GetDestDir(), g_pOptions->GetInterDir(), strlen(g_pOptions->GetInterDir())); // TODO: check if download has pp-scripts defined bool bPostScript = true; if (bUnpack && bParFailed) { warn("Skipping unpack for %s due to %s", pPostInfo->GetInfoName(), pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psManual ? "required par-repair" : "par-failure"); pPostInfo->GetNZBInfo()->SetUnpackStatus(NZBInfo::usSkipped); bUnpack = false; } if (!bUnpack && !bMoveInter && !bPostScript) { pPostInfo->SetStage(PostInfo::ptFinished); return; } pPostInfo->SetProgressLabel(bUnpack ? "Unpacking" : bMoveInter ? "Moving" : "Executing post-process-script"); pPostInfo->SetWorking(true); pPostInfo->SetStage(bUnpack ? PostInfo::ptUnpacking : bMoveInter ? PostInfo::ptMoving : PostInfo::ptExecutingScript); pPostInfo->SetFileProgress(0); pPostInfo->SetStageProgress(0); SaveQueue(pDownloadQueue); if (!pPostInfo->GetStartTime()) { pPostInfo->SetStartTime(time(NULL)); } pPostInfo->SetStageTime(time(NULL)); if (bUnpack) { UpdatePauseState(g_pOptions->GetUnpackPauseQueue(), "unpack"); UnpackController::StartJob(pPostInfo); } else if (bCleanup) { UpdatePauseState(g_pOptions->GetUnpackPauseQueue() || g_pOptions->GetScriptPauseQueue(), "cleanup"); CleanupController::StartJob(pPostInfo); } else if (bMoveInter) { UpdatePauseState(g_pOptions->GetUnpackPauseQueue() || g_pOptions->GetScriptPauseQueue(), "move"); MoveController::StartJob(pPostInfo); } else { UpdatePauseState(g_pOptions->GetScriptPauseQueue(), "post-process-script"); PostScriptController::StartJob(pPostInfo); } } void PrePostProcessor::JobCompleted(DownloadQueue* pDownloadQueue, PostInfo* pPostInfo) { pPostInfo->SetWorking(false); pPostInfo->SetProgressLabel(""); pPostInfo->SetStage(PostInfo::ptFinished); DeletePostThread(pPostInfo); if (IsNZBFileCompleted(pDownloadQueue, pPostInfo->GetNZBInfo(), true, false)) { // Cleaning up queue if par-check was successful or unpack was successful or // script was successful (if unpack was not performed) bool bCanCleanupQueue = pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psSuccess || pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psRepairPossible || pPostInfo->GetNZBInfo()->GetUnpackStatus() == NZBInfo::usSuccess || (pPostInfo->GetNZBInfo()->GetUnpackStatus() == NZBInfo::usNone && pPostInfo->GetNZBInfo()->GetScriptStatuses()->CalcTotalStatus() == ScriptStatus::srSuccess); if (g_pOptions->GetParCleanupQueue() && bCanCleanupQueue) { FileInfo* pFileInfo = GetQueueGroup(pDownloadQueue, pPostInfo->GetNZBInfo()); if (pFileInfo) { info("Cleaning up download queue for %s", pPostInfo->GetNZBInfo()->GetName()); pFileInfo->GetNZBInfo()->ClearCompletedFiles(); pFileInfo->GetNZBInfo()->SetParCleanup(true); g_pQueueCoordinator->GetQueueEditor()->LockedEditEntry(pDownloadQueue, pFileInfo->GetID(), false, QueueEditor::eaGroupDelete, 0, NULL); } } NZBCompleted(pDownloadQueue, pPostInfo->GetNZBInfo(), false); } for (PostQueue::iterator it = pDownloadQueue->GetPostQueue()->begin(); it != pDownloadQueue->GetPostQueue()->end(); it++) { if (pPostInfo == *it) { pDownloadQueue->GetPostQueue()->erase(it); break; } } delete pPostInfo; SaveQueue(pDownloadQueue); m_bHasMoreJobs = !pDownloadQueue->GetPostQueue()->empty(); } bool PrePostProcessor::IsNZBFileCompleted(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, bool bIgnorePausedPars, bool bAllowOnlyOneDeleted) { bool bNZBFileCompleted = true; int iDeleted = 0; for (FileQueue::iterator it = pDownloadQueue->GetFileQueue()->begin(); it != pDownloadQueue->GetFileQueue()->end(); it++) { FileInfo* pFileInfo = *it; if (pFileInfo->GetNZBInfo() == pNZBInfo) { if (pFileInfo->GetDeleted()) { iDeleted++; } if (((!pFileInfo->GetPaused() || !bIgnorePausedPars || !(m_ParCoordinator.ParseParFilename(pFileInfo->GetFilename(), NULL, NULL))) && !pFileInfo->GetDeleted()) || (bAllowOnlyOneDeleted && iDeleted > 1)) { bNZBFileCompleted = false; break; } } } return bNZBFileCompleted; } /** * Returns the first FileInfo belonging to given NZBInfo. */ FileInfo* PrePostProcessor::GetQueueGroup(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo) { for (FileQueue::iterator it = pDownloadQueue->GetFileQueue()->begin(); it != pDownloadQueue->GetFileQueue()->end(); it++) { FileInfo* pFileInfo = *it; if (pFileInfo->GetNZBInfo() == pNZBInfo) { return pFileInfo; } } return NULL; } void PrePostProcessor::ApplySchedulerState() { if (g_pScheduler->GetPauseDownloadChanged()) { info("Scheduler: %s download queue", g_pScheduler->GetPauseDownload() ? "pausing" : "unpausing"); m_bSchedulerPauseChanged = true; m_bSchedulerPause = g_pScheduler->GetPauseDownload(); if (!m_bPostPause) { g_pOptions->SetPauseDownload(m_bSchedulerPause); } } } void PrePostProcessor::UpdatePauseState(bool bNeedPause, const char* szReason) { if (bNeedPause) { if (PauseDownload()) { info("Pausing queue before %s", szReason); } } else if (m_bPostPause) { if (UnpauseDownload()) { info("Unpausing queue after %s", m_szPauseReason); } } m_szPauseReason = szReason; } bool PrePostProcessor::PauseDownload() { debug("PrePostProcessor::PauseDownload()"); if (m_bPostPause && g_pOptions->GetPauseDownload()) { return false; } m_bPostPause = !g_pOptions->GetPauseDownload(); m_bSchedulerPauseChanged = false; g_pOptions->SetPauseDownload(true); return m_bPostPause; } bool PrePostProcessor::UnpauseDownload() { debug("PrePostProcessor::UnpauseDownload()"); bool bPause = true; if (m_bPostPause) { m_bPostPause = false; bPause = m_bSchedulerPauseChanged && m_bSchedulerPause; g_pOptions->SetPauseDownload(bPause); } return !bPause; } void PrePostProcessor::CheckScheduledResume() { time_t tResumeTime = g_pOptions->GetResumeTime(); time_t tCurrentTime = time(NULL); if (tResumeTime > 0 && tCurrentTime >= tResumeTime) { info("Autoresume"); g_pOptions->SetResumeTime(0); g_pOptions->SetPauseDownload2(false); g_pOptions->SetPausePostProcess(false); g_pOptions->SetPauseScan(false); } } bool PrePostProcessor::QueueEditList(IDList* pIDList, EEditAction eAction, int iOffset, const char* szText) { debug("Edit-command for post-processor received"); switch (eAction) { case eaPostMoveOffset: case eaPostMoveTop: case eaPostMoveBottom: return PostQueueMove(pIDList, eAction, iOffset); case eaPostDelete: return PostQueueDelete(pIDList); case eaHistoryDelete: case eaHistoryFinalDelete: case eaHistoryReturn: case eaHistoryProcess: case eaHistoryRedownload: case eaHistorySetParameter: case eaHistorySetDupeKey: case eaHistorySetDupeScore: case eaHistorySetDupeMode: case eaHistorySetDupeBackup: case eaHistoryMarkBad: case eaHistoryMarkGood: return HistoryEdit(pIDList, eAction, iOffset, szText); default: return false; } } bool PrePostProcessor::PostQueueDelete(IDList* pIDList) { bool bOK = false; DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue(); for (IDList::iterator itID = pIDList->begin(); itID != pIDList->end(); itID++) { int iID = *itID; for (PostQueue::iterator itPost = pDownloadQueue->GetPostQueue()->begin(); itPost != pDownloadQueue->GetPostQueue()->end(); itPost++) { PostInfo* pPostInfo = *itPost; if (pPostInfo->GetID() == iID) { if (pPostInfo->GetWorking()) { info("Deleting active post-job %s", pPostInfo->GetInfoName()); pPostInfo->SetDeleted(true); #ifndef DISABLE_PARCHECK if (PostInfo::ptLoadingPars <= pPostInfo->GetStage() && pPostInfo->GetStage() <= PostInfo::ptRenaming) { if (m_ParCoordinator.Cancel()) { bOK = true; } } else #endif if (pPostInfo->GetPostThread()) { debug("Terminating %s for %s", (pPostInfo->GetStage() == PostInfo::ptUnpacking ? "unpack" : "post-process-script"), pPostInfo->GetInfoName()); pPostInfo->GetPostThread()->Stop(); bOK = true; } else { error("Internal error in PrePostProcessor::QueueDelete"); } } else { info("Deleting queued post-job %s", pPostInfo->GetInfoName()); JobCompleted(pDownloadQueue, pPostInfo); bOK = true; } break; } } } g_pQueueCoordinator->UnlockQueue(); return bOK; } bool PrePostProcessor::PostQueueMove(IDList* pIDList, EEditAction eAction, int iOffset) { if (pIDList->size() != 1) { //NOTE: Only one post-job can be moved at once return false; } DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue(); bool bOK = false; int iID = pIDList->front(); unsigned int iIndex = 0; PostInfo* pPostInfo = NULL; for (PostQueue::iterator it = pDownloadQueue->GetPostQueue()->begin(); it != pDownloadQueue->GetPostQueue()->end(); it++) { PostInfo* pPostInfo1 = *it; if (pPostInfo1->GetID() == iID) { pPostInfo = pPostInfo1; break; } iIndex++; } if (pPostInfo) { // NOTE: only items which are not currently being processed can be moved unsigned int iNewIndex = 0; switch (eAction) { case eaPostMoveTop: iNewIndex = 1; break; case eaPostMoveBottom: iNewIndex = pDownloadQueue->GetPostQueue()->size() - 1; break; case eaPostMoveOffset: iNewIndex = iIndex + iOffset; break; default: ; // suppress compiler warning } if (iNewIndex < 1) { iNewIndex = 1; } else if (iNewIndex > pDownloadQueue->GetPostQueue()->size() - 1) { iNewIndex = pDownloadQueue->GetPostQueue()->size() - 1; } if (0 < iNewIndex && iNewIndex < pDownloadQueue->GetPostQueue()->size() && iNewIndex != iIndex) { pDownloadQueue->GetPostQueue()->erase(pDownloadQueue->GetPostQueue()->begin() + iIndex); pDownloadQueue->GetPostQueue()->insert(pDownloadQueue->GetPostQueue()->begin() + iNewIndex, pPostInfo); SaveQueue(pDownloadQueue); bOK = true; } } g_pQueueCoordinator->UnlockQueue(); return bOK; } bool PrePostProcessor::HistoryEdit(IDList* pIDList, EEditAction eAction, int iOffset, const char* szText) { bool bOK = false; DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue(); for (IDList::iterator itID = pIDList->begin(); itID != pIDList->end(); itID++) { int iID = *itID; for (HistoryList::iterator itHistory = pDownloadQueue->GetHistoryList()->begin(); itHistory != pDownloadQueue->GetHistoryList()->end(); itHistory++) { HistoryInfo* pHistoryInfo = *itHistory; if (pHistoryInfo->GetID() == iID) { switch (eAction) { case eaHistoryDelete: case eaHistoryFinalDelete: HistoryDelete(pDownloadQueue, itHistory, pHistoryInfo, eAction == eaHistoryFinalDelete); break; case eaHistoryReturn: case eaHistoryProcess: HistoryReturn(pDownloadQueue, itHistory, pHistoryInfo, eAction == eaHistoryProcess); break; case eaHistoryRedownload: HistoryRedownload(pDownloadQueue, itHistory, pHistoryInfo, false); break; case eaHistorySetParameter: HistorySetParameter(pHistoryInfo, szText); break; case eaHistorySetDupeKey: case eaHistorySetDupeScore: case eaHistorySetDupeMode: case eaHistorySetDupeBackup: HistorySetDupeParam(pHistoryInfo, eAction, szText); break; case eaHistoryMarkBad: case eaHistoryMarkGood: m_DupeCoordinator.HistoryMark(pDownloadQueue, pHistoryInfo, eAction == eaHistoryMarkGood); break; default: // nothing, just to avoid compiler warning break; } bOK = true; break; } } } if (bOK) { SaveQueue(pDownloadQueue); } g_pQueueCoordinator->UnlockQueue(); return bOK; } void PrePostProcessor::HistoryDelete(DownloadQueue* pDownloadQueue, HistoryList::iterator itHistory, HistoryInfo* pHistoryInfo, bool bFinal) { char szNiceName[1024]; pHistoryInfo->GetName(szNiceName, 1024); info("Deleting %s from history", szNiceName); if (pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo) { NZBInfo* pNZBInfo = pHistoryInfo->GetNZBInfo(); // delete parked files int index = 0; for (FileQueue::iterator it = pDownloadQueue->GetParkedFiles()->begin(); it != pDownloadQueue->GetParkedFiles()->end(); ) { FileInfo* pFileInfo = *it; if (pFileInfo->GetNZBInfo() == pNZBInfo) { pDownloadQueue->GetParkedFiles()->erase(it); if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState->DiscardFile(pFileInfo); } delete pFileInfo; it = pDownloadQueue->GetParkedFiles()->begin() + index; } else { it++; index++; } } DeleteQueuedFile(pNZBInfo->GetQueuedFilename()); } if (pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo && g_pOptions->GetDeleteCleanupDisk() && (pHistoryInfo->GetNZBInfo()->GetDeleteStatus() != NZBInfo::dsNone || pHistoryInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psFailure || pHistoryInfo->GetNZBInfo()->GetUnpackStatus() == NZBInfo::usFailure || pHistoryInfo->GetNZBInfo()->GetUnpackStatus() == NZBInfo::usPassword) && Util::DirectoryExists(pHistoryInfo->GetNZBInfo()->GetDestDir())) { info("Deleting %s", pHistoryInfo->GetNZBInfo()->GetDestDir()); Util::DeleteDirectoryWithContent(pHistoryInfo->GetNZBInfo()->GetDestDir()); } if (bFinal || !g_pOptions->GetDupeCheck() || pHistoryInfo->GetKind() == HistoryInfo::hkUrlInfo) { pDownloadQueue->GetHistoryList()->erase(itHistory); delete pHistoryInfo; } else { if (pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo) { // replace history element int rindex = pDownloadQueue->GetHistoryList()->size() - 1 - (itHistory - pDownloadQueue->GetHistoryList()->begin()); m_DupeCoordinator.HistoryTransformToDup(pDownloadQueue, pHistoryInfo, rindex); } } } void PrePostProcessor::HistoryReturn(DownloadQueue* pDownloadQueue, HistoryList::iterator itHistory, HistoryInfo* pHistoryInfo, bool bReprocess) { char szNiceName[1024]; pHistoryInfo->GetName(szNiceName, 1024); debug("Returning %s from history back to download queue", szNiceName); bool bUnparked = false; if (bReprocess && pHistoryInfo->GetKind() != HistoryInfo::hkNZBInfo) { error("Could not restart postprocessing for %s: history item has wrong type", szNiceName); return; } if (pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo) { NZBInfo* pNZBInfo = pHistoryInfo->GetNZBInfo(); // unpark files int index = 0; for (FileQueue::reverse_iterator it = pDownloadQueue->GetParkedFiles()->rbegin(); it != pDownloadQueue->GetParkedFiles()->rend(); ) { FileInfo* pFileInfo = *it; if (pFileInfo->GetNZBInfo() == pNZBInfo) { detail("Unpark file %s", pFileInfo->GetFilename()); pDownloadQueue->GetParkedFiles()->erase(pDownloadQueue->GetParkedFiles()->end() - 1 - index); pDownloadQueue->GetFileQueue()->push_front(pFileInfo); bUnparked = true; it = pDownloadQueue->GetParkedFiles()->rbegin() + index; } else { it++; index++; } } // reset postprocessing status variables pNZBInfo->SetPostProcess(false); pNZBInfo->SetParCleanup(false); if (!pNZBInfo->GetUnpackCleanedUpDisk()) { pNZBInfo->SetUnpackStatus(NZBInfo::usNone); pNZBInfo->SetCleanupStatus(NZBInfo::csNone); if (m_ParCoordinator.FindMainPars(pNZBInfo->GetDestDir(), NULL)) { pNZBInfo->SetParStatus(NZBInfo::psNone); pNZBInfo->SetRenameStatus(NZBInfo::rsNone); } } pNZBInfo->SetDeleteStatus(NZBInfo::dsNone); pNZBInfo->SetDeletePaused(false); pNZBInfo->SetMarkStatus(NZBInfo::ksNone); pNZBInfo->GetScriptStatuses()->Clear(); pNZBInfo->SetParkedFileCount(0); } if (pHistoryInfo->GetKind() == HistoryInfo::hkUrlInfo) { UrlInfo* pUrlInfo = pHistoryInfo->GetUrlInfo(); pHistoryInfo->DiscardUrlInfo(); pUrlInfo->SetStatus(UrlInfo::aiUndefined); pDownloadQueue->GetUrlQueue()->push_back(pUrlInfo); bUnparked = true; } if (bUnparked || bReprocess) { pDownloadQueue->GetHistoryList()->erase(itHistory); // the object "pHistoryInfo" is released few lines later, after the call to "NZBDownloaded" info("%s returned from history back to download queue", szNiceName); } else { warn("Could not return %s back from history to download queue: history item does not have any files left for download", szNiceName); } if (bReprocess) { // start postprocessing debug("Restarting postprocessing for %s", szNiceName); NZBDownloaded(pDownloadQueue, pHistoryInfo->GetNZBInfo()); } if (bUnparked || bReprocess) { delete pHistoryInfo; } } void PrePostProcessor::HistoryRedownload(DownloadQueue* pDownloadQueue, HistoryList::iterator itHistory, HistoryInfo* pHistoryInfo, bool bRestorePauseState) { NZBInfo* pNZBInfo = pHistoryInfo->GetNZBInfo(); bool bPaused = bRestorePauseState && pNZBInfo->GetDeletePaused(); int iGroupdID = -1; if (!Util::FileExists(pNZBInfo->GetQueuedFilename())) { error("Could not return collection %s from history back to queue: could not find source nzb-file %s", pNZBInfo->GetName(), pNZBInfo->GetQueuedFilename()); return; } NZBFile* pNZBFile = NZBFile::Create(pNZBInfo->GetQueuedFilename(), ""); if (pNZBFile == NULL) { error("Could not return collection %s from history back to queue: could not parse nzb-file", pNZBInfo->GetName()); return; } info("Returning collection %s from history back to queue", pNZBInfo->GetName()); for (NZBFile::FileInfos::iterator it = pNZBFile->GetFileInfos()->begin(); it != pNZBFile->GetFileInfos()->end(); it++) { FileInfo* pFileInfo = *it; pFileInfo->SetNZBInfo(pNZBInfo); pFileInfo->SetPaused(bPaused); iGroupdID = pFileInfo->GetID(); } if (Util::DirectoryExists(pNZBInfo->GetDestDir())) { detail("Deleting %s", pNZBInfo->GetDestDir()); Util::DeleteDirectoryWithContent(pNZBInfo->GetDestDir()); } pNZBInfo->BuildDestDirName(); if (Util::DirectoryExists(pNZBInfo->GetDestDir())) { detail("Deleting %s", pNZBInfo->GetDestDir()); Util::DeleteDirectoryWithContent(pNZBInfo->GetDestDir()); } // reset status fields (which are not reset by "HistoryReturn") pNZBInfo->SetMoveStatus(NZBInfo::msNone); pNZBInfo->SetUnpackCleanedUpDisk(false); pNZBInfo->SetParStatus(NZBInfo::psNone); pNZBInfo->SetRenameStatus(NZBInfo::rsNone); pNZBInfo->ClearCompletedFiles(); pNZBInfo->GetServerStats()->Clear(); // take file stats from newly read nzb-file pNZBInfo->SetFileCount(pNZBFile->GetNZBInfo()->GetFileCount()); pNZBInfo->SetFullContentHash(pNZBFile->GetNZBInfo()->GetFullContentHash()); pNZBInfo->SetFilteredContentHash(pNZBFile->GetNZBInfo()->GetFilteredContentHash()); pNZBInfo->SetSize(pNZBFile->GetNZBInfo()->GetSize()); pNZBInfo->SetSuccessSize(pNZBFile->GetNZBInfo()->GetSuccessSize()); pNZBInfo->SetCurrentSuccessSize(pNZBFile->GetNZBInfo()->GetCurrentSuccessSize()); pNZBInfo->SetFailedSize(pNZBFile->GetNZBInfo()->GetFailedSize()); pNZBInfo->SetCurrentFailedSize(pNZBFile->GetNZBInfo()->GetCurrentFailedSize()); pNZBInfo->SetParSize(pNZBFile->GetNZBInfo()->GetParSize()); pNZBInfo->SetParSuccessSize(pNZBFile->GetNZBInfo()->GetParSuccessSize()); pNZBInfo->SetParCurrentSuccessSize(pNZBFile->GetNZBInfo()->GetParCurrentSuccessSize()); pNZBInfo->SetParFailedSize(pNZBFile->GetNZBInfo()->GetParFailedSize()); pNZBInfo->SetParCurrentFailedSize(pNZBFile->GetNZBInfo()->GetParCurrentFailedSize()); pNZBInfo->SetSuccessArticles(pNZBFile->GetNZBInfo()->GetSuccessArticles()); pNZBInfo->SetFailedArticles(pNZBFile->GetNZBInfo()->GetFailedArticles()); g_pQueueCoordinator->AddFileInfosToFileQueue(pNZBFile, pDownloadQueue->GetParkedFiles(), false); delete pNZBFile; HistoryReturn(pDownloadQueue, itHistory, pHistoryInfo, false); if (!bPaused && g_pOptions->GetParCheck() != Options::pcForce) { g_pQueueCoordinator->GetQueueEditor()->LockedEditEntry(pDownloadQueue, iGroupdID, false, QueueEditor::eaGroupPauseExtraPars, 0, NULL); } } void PrePostProcessor::HistorySetParameter(HistoryInfo* pHistoryInfo, const char* szText) { char szNiceName[1024]; pHistoryInfo->GetName(szNiceName, 1024); debug("Setting post-process-parameter '%s' for '%s'", szText, szNiceName); if (pHistoryInfo->GetKind() != HistoryInfo::hkNZBInfo) { error("Could not set post-process-parameter for %s: history item has wrong type", szNiceName); return; } char* szStr = strdup(szText); char* szValue = strchr(szStr, '='); if (szValue) { *szValue = '\0'; szValue++; pHistoryInfo->GetNZBInfo()->GetParameters()->SetParameter(szStr, szValue); } else { error("Could not set post-process-parameter for %s: invalid argument: %s", pHistoryInfo->GetNZBInfo()->GetName(), szText); } free(szStr); } void PrePostProcessor::HistorySetDupeParam(HistoryInfo* pHistoryInfo, EEditAction eAction, const char* szText) { char szNiceName[1024]; pHistoryInfo->GetName(szNiceName, 1024); debug("Setting dupe-parameter '%i'='%s' for '%s'", (int)eAction, szText, szNiceName); if (!(pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo || pHistoryInfo->GetKind() == HistoryInfo::hkDupInfo)) { error("Could not set duplicate parameter for %s: history item has wrong type", szNiceName); return; } EDupeMode eMode = dmScore; if (eAction == eaHistorySetDupeMode) { if (!strcasecmp(szText, "SCORE")) { eMode = dmScore; } else if (!strcasecmp(szText, "ALL")) { eMode = dmAll; } else if (!strcasecmp(szText, "FORCE")) { eMode = dmForce; } else { error("Could not set duplicate mode for %s: incorrect mode (%s)", szNiceName, szText); return; } } if (pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo) { switch (eAction) { case eaHistorySetDupeKey: pHistoryInfo->GetNZBInfo()->SetDupeKey(szText); break; case eaHistorySetDupeScore: pHistoryInfo->GetNZBInfo()->SetDupeScore(atoi(szText)); break; case eaHistorySetDupeMode: pHistoryInfo->GetNZBInfo()->SetDupeMode(eMode); break; case eaHistorySetDupeBackup: if (pHistoryInfo->GetNZBInfo()->GetDeleteStatus() != NZBInfo::dsDupe && pHistoryInfo->GetNZBInfo()->GetDeleteStatus() != NZBInfo::dsManual) { error("Could not set duplicate parameter for %s: history item has wrong delete status", szNiceName); return; } pHistoryInfo->GetNZBInfo()->SetDeleteStatus(!strcasecmp(szText, "YES") || !strcasecmp(szText, "TRUE") || !strcasecmp(szText, "1") ? NZBInfo::dsDupe : NZBInfo::dsManual); break; default: // suppress compiler warning break; } } else if (pHistoryInfo->GetKind() == HistoryInfo::hkDupInfo) { switch (eAction) { case eaHistorySetDupeKey: pHistoryInfo->GetDupInfo()->SetDupeKey(szText); break; case eaHistorySetDupeScore: pHistoryInfo->GetDupInfo()->SetDupeScore(atoi(szText)); break; case eaHistorySetDupeMode: pHistoryInfo->GetDupInfo()->SetDupeMode(eMode); break; case eaHistorySetDupeBackup: error("Could not set duplicate parameter for %s: history item has wrong type", szNiceName); return; default: // suppress compiler warning break; } } } nzbget-12.0+dfsg/PrePostProcessor.h000066400000000000000000000123721226450633000173170ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 924 $ * $Date: 2013-12-21 22:39:49 +0100 (Sat, 21 Dec 2013) $ * */ #ifndef PREPOSTPROCESSOR_H #define PREPOSTPROCESSOR_H #include #include "Thread.h" #include "Observer.h" #include "DownloadInfo.h" #include "ParCoordinator.h" #include "DupeCoordinator.h" class PrePostProcessor : public Thread { public: // NOTE: changes to this enum must be synced with "eRemoteEditAction" in unit "MessageBase.h" enum EEditAction { eaPostMoveOffset = 51, // move post to m_iOffset relative to the current position in post-queue eaPostMoveTop, eaPostMoveBottom, eaPostDelete, eaHistoryDelete, eaHistoryFinalDelete, eaHistoryReturn, eaHistoryProcess, eaHistoryRedownload, eaHistorySetParameter, eaHistorySetDupeKey, eaHistorySetDupeScore, eaHistorySetDupeMode, eaHistorySetDupeBackup, eaHistoryMarkBad, eaHistoryMarkGood }; private: class QueueCoordinatorObserver: public Observer { public: PrePostProcessor* m_pOwner; virtual void Update(Subject* Caller, void* Aspect) { m_pOwner->QueueCoordinatorUpdate(Caller, Aspect); } }; class PostParCoordinator: public ParCoordinator { private: PrePostProcessor* m_pOwner; protected: virtual bool PauseDownload() { return m_pOwner->PauseDownload(); } virtual bool UnpauseDownload() { return m_pOwner->UnpauseDownload(); } friend class PrePostProcessor; }; class PostDupeCoordinator: public DupeCoordinator { private: PrePostProcessor* m_pOwner; protected: virtual void HistoryRedownload(DownloadQueue* pDownloadQueue, HistoryInfo* pHistoryInfo); virtual void DeleteQueuedFile(const char* szQueuedFile) { m_pOwner->DeleteQueuedFile(szQueuedFile); } friend class PrePostProcessor; }; private: PostParCoordinator m_ParCoordinator; PostDupeCoordinator m_DupeCoordinator; QueueCoordinatorObserver m_QueueCoordinatorObserver; bool m_bHasMoreJobs; bool m_bSchedulerPauseChanged; bool m_bSchedulerPause; bool m_bPostPause; const char* m_szPauseReason; bool IsNZBFileCompleted(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, bool bIgnorePausedPars, bool bAllowOnlyOneDeleted); void CheckPostQueue(); void JobCompleted(DownloadQueue* pDownloadQueue, PostInfo* pPostInfo); void StartJob(DownloadQueue* pDownloadQueue, PostInfo* pPostInfo); void SaveQueue(DownloadQueue* pDownloadQueue); void SanitisePostQueue(PostQueue* pPostQueue); void CheckDiskSpace(); void ApplySchedulerState(); void CheckScheduledResume(); void UpdatePauseState(bool bNeedPause, const char* szReason); bool PauseDownload(); bool UnpauseDownload(); void NZBFound(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo); void NZBAdded(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo); void NZBDownloaded(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo); void NZBDeleted(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo); void NZBCompleted(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, bool bSaveQueue); void DeleteQueuedFile(const char* szQueuedFile); int FindGroupID(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo); bool PostQueueMove(IDList* pIDList, EEditAction eAction, int iOffset); bool PostQueueDelete(IDList* pIDList); bool HistoryEdit(IDList* pIDList, EEditAction eAction, int iOffset, const char* szText); void HistoryDelete(DownloadQueue* pDownloadQueue, HistoryList::iterator itHistory, HistoryInfo* pHistoryInfo, bool bFinal); void HistoryReturn(DownloadQueue* pDownloadQueue, HistoryList::iterator itHistory, HistoryInfo* pHistoryInfo, bool bReprocess); void HistoryRedownload(DownloadQueue* pDownloadQueue, HistoryList::iterator itHistory, HistoryInfo* pHistoryInfo, bool bRestorePauseState); void HistorySetParameter(HistoryInfo* pHistoryInfo, const char* szText); void HistorySetDupeParam(HistoryInfo* pHistoryInfo, EEditAction eAction, const char* szText); void HistoryTransformToDup(DownloadQueue* pDownloadQueue, HistoryInfo* pHistoryInfo, int rindex); void CheckHistory(); void Cleanup(); FileInfo* GetQueueGroup(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo); void DeletePostThread(PostInfo* pPostInfo); public: PrePostProcessor(); virtual ~PrePostProcessor(); virtual void Run(); virtual void Stop(); void QueueCoordinatorUpdate(Subject* Caller, void* Aspect); bool HasMoreJobs() { return m_bHasMoreJobs; } bool QueueEditList(IDList* pIDList, EEditAction eAction, int iOffset, const char* szText); }; #endif nzbget-12.0+dfsg/QueueCoordinator.cpp000066400000000000000000001123651226450633000176510ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2005 Bo Cordes Petersen * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 911 $ * $Date: 2013-11-24 20:29:52 +0100 (Sun, 24 Nov 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #include #ifndef WIN32 #include #include #endif #include #include "nzbget.h" #include "QueueCoordinator.h" #include "Options.h" #include "ServerPool.h" #include "ArticleDownloader.h" #include "DiskState.h" #include "Log.h" #include "Util.h" #include "Decoder.h" extern Options* g_pOptions; extern ServerPool* g_pServerPool; extern DiskState* g_pDiskState; QueueCoordinator::QueueCoordinator() { debug("Creating QueueCoordinator"); m_bHasMoreJobs = true; ResetSpeedStat(); m_iAllBytes = 0; m_tStartServer = 0; m_tStartDownload = 0; m_tPausedFrom = 0; m_bStandBy = true; m_iServerConfigGeneration = 0; YDecoder::Init(); } QueueCoordinator::~QueueCoordinator() { debug("Destroying QueueCoordinator"); // Cleanup debug("Deleting DownloadQueue"); for (FileQueue::iterator it = m_DownloadQueue.GetFileQueue()->begin(); it != m_DownloadQueue.GetFileQueue()->end(); it++) { delete *it; } m_DownloadQueue.GetFileQueue()->clear(); debug("Deleting ArticleDownloaders"); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { delete *it; } m_ActiveDownloads.clear(); YDecoder::Final(); debug("QueueCoordinator destroyed"); } void QueueCoordinator::Run() { debug("Entering QueueCoordinator-loop"); m_mutexDownloadQueue.Lock(); if (g_pOptions->GetServerMode()) { g_pDiskState->LoadStats(g_pServerPool->GetServers()); // currently there are no any stats but we need to save current server list into diskstate g_pDiskState->SaveStats(g_pServerPool->GetServers()); } if (g_pOptions->GetServerMode() && g_pOptions->GetSaveQueue() && g_pDiskState->DownloadQueueExists()) { if (g_pOptions->GetReloadQueue()) { g_pDiskState->LoadDownloadQueue(&m_DownloadQueue); } else { g_pDiskState->DiscardDownloadQueue(); } } g_pDiskState->CleanupTempDir(&m_DownloadQueue); m_mutexDownloadQueue.Unlock(); AdjustDownloadsLimit(); m_tStartServer = time(NULL); m_tLastCheck = m_tStartServer; bool bWasStandBy = true; bool bArticeDownloadsRunning = false; int iResetCounter = 0; while (!IsStopped()) { bool bDownloadsChecked = false; if (!(g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2())) { NNTPConnection* pConnection = g_pServerPool->GetConnection(0, NULL, NULL); if (pConnection) { // start download for next article FileInfo* pFileInfo; ArticleInfo* pArticleInfo; bool bFreeConnection = false; m_mutexDownloadQueue.Lock(); bool bHasMoreArticles = GetNextArticle(pFileInfo, pArticleInfo); bArticeDownloadsRunning = !m_ActiveDownloads.empty(); bDownloadsChecked = true; m_bHasMoreJobs = bHasMoreArticles || bArticeDownloadsRunning; if (bHasMoreArticles && !IsStopped() && (int)m_ActiveDownloads.size() < m_iDownloadsLimit) { StartArticleDownload(pFileInfo, pArticleInfo, pConnection); bArticeDownloadsRunning = true; } else { bFreeConnection = true; } m_mutexDownloadQueue.Unlock(); if (bFreeConnection) { g_pServerPool->FreeConnection(pConnection, false); } } } if (!bDownloadsChecked) { m_mutexDownloadQueue.Lock(); bArticeDownloadsRunning = !m_ActiveDownloads.empty(); m_mutexDownloadQueue.Unlock(); } bool bStandBy = !bArticeDownloadsRunning; if (bStandBy ^ bWasStandBy) { EnterLeaveStandBy(bStandBy); bWasStandBy = bStandBy; } // sleep longer in StandBy int iSleepInterval = bStandBy ? 100 : 5; usleep(iSleepInterval * 1000); if (!bStandBy) { 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; AdjustStartTime(); AdjustDownloadsLimit(); } } // waiting for downloads debug("QueueCoordinator: waiting for Downloads to complete"); bool completed = false; while (!completed) { m_mutexDownloadQueue.Lock(); completed = m_ActiveDownloads.size() == 0; m_mutexDownloadQueue.Unlock(); usleep(100 * 1000); ResetHangingDownloads(); } debug("QueueCoordinator: Downloads are completed"); debug("Exiting QueueCoordinator-loop"); } /* * Compute maximum number of allowed download threads **/ void QueueCoordinator::AdjustDownloadsLimit() { if (m_iServerConfigGeneration == g_pServerPool->GetGeneration()) { return; } // two extra threads for completing files (when connections are not needed) int iDownloadsLimit = 2; // allow one thread per 0-level (main) and 1-level (backup) server connection for (Servers::iterator it = g_pServerPool->GetServers()->begin(); it != g_pServerPool->GetServers()->end(); it++) { NewsServer* pNewsServer = *it; if ((pNewsServer->GetNormLevel() == 0 || pNewsServer->GetNormLevel() == 1) && pNewsServer->GetActive()) { iDownloadsLimit += pNewsServer->GetMaxConnections(); } } m_iDownloadsLimit = iDownloadsLimit; } void QueueCoordinator::AddNZBFileToQueue(NZBFile* pNZBFile, bool bAddFirst) { debug("Adding NZBFile to queue"); m_mutexDownloadQueue.Lock(); Aspect foundAspect = { eaNZBFileFound, &m_DownloadQueue, pNZBFile->GetNZBInfo(), NULL }; Notify(&foundAspect); if (pNZBFile->GetNZBInfo()->GetDeleteStatus() != NZBInfo::dsNone) { bool bAllPaused = !pNZBFile->GetFileInfos()->empty(); for (NZBFile::FileInfos::iterator it = pNZBFile->GetFileInfos()->begin(); it != pNZBFile->GetFileInfos()->end(); it++) { FileInfo* pFileInfo = *it; bAllPaused &= pFileInfo->GetPaused(); if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState->DiscardFile(pFileInfo); } } pNZBFile->GetNZBInfo()->SetDeletePaused(bAllPaused); } if (pNZBFile->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsManual) { m_mutexDownloadQueue.Unlock(); // UNLOCK return; } m_DownloadQueue.GetNZBInfoList()->Add(pNZBFile->GetNZBInfo()); if (pNZBFile->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsNone) { AddFileInfosToFileQueue(pNZBFile, m_DownloadQueue.GetFileQueue(), bAddFirst); } char szNZBName[1024]; strncpy(szNZBName, pNZBFile->GetNZBInfo()->GetName(), sizeof(szNZBName)-1); Aspect aspect = { eaNZBFileAdded, &m_DownloadQueue, pNZBFile->GetNZBInfo(), NULL }; Notify(&aspect); if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState->SaveDownloadQueue(&m_DownloadQueue); } m_mutexDownloadQueue.Unlock(); if (pNZBFile->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsNone) { info("Collection %s added to queue", szNZBName); } } void QueueCoordinator::AddFileInfosToFileQueue(NZBFile* pNZBFile, FileQueue* pFileQueue, bool bAddFirst) { debug("Adding NZBFile to queue"); FileQueue tmpFileQueue; FileQueue DupeList; int index1 = 0; for (NZBFile::FileInfos::iterator it = pNZBFile->GetFileInfos()->begin(); it != pNZBFile->GetFileInfos()->end(); it++) { index1++; FileInfo* pFileInfo = *it; if (g_pOptions->GetDupeCheck() && !pNZBFile->GetNZBInfo()->GetDupeMode()) { bool dupe = false; int index2 = 0; for (NZBFile::FileInfos::iterator it2 = pNZBFile->GetFileInfos()->begin(); it2 != pNZBFile->GetFileInfos()->end(); it2++) { index2++; FileInfo* pFileInfo2 = *it2; if (pFileInfo != pFileInfo2 && !strcmp(pFileInfo->GetFilename(), pFileInfo2->GetFilename()) && (pFileInfo->GetSize() < pFileInfo2->GetSize() || (pFileInfo->GetSize() == pFileInfo2->GetSize() && index2 < index1))) { warn("File \"%s\" appears twice in collection, adding only the biggest file", pFileInfo->GetFilename()); dupe = true; break; } } if (dupe) { DupeList.push_back(pFileInfo); StatFileInfo(pFileInfo, false); continue; } } if (bAddFirst) { tmpFileQueue.push_front(pFileInfo); } else { tmpFileQueue.push_back(pFileInfo); } } for (FileQueue::iterator it = tmpFileQueue.begin(); it != tmpFileQueue.end(); it++) { if (bAddFirst) { pFileQueue->push_front(*it); } else { pFileQueue->push_back(*it); } } for (FileQueue::iterator it = DupeList.begin(); it != DupeList.end(); it++) { FileInfo* pFileInfo = *it; if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState->DiscardFile(pFileInfo); } delete pFileInfo; } pNZBFile->DetachFileInfos(); } /* * NOTE: see note to "AddSpeedReading" */ int QueueCoordinator::CalcCurrentDownloadSpeed() { if (m_bStandBy) { return 0; } int iTimeDiff = (int)time(NULL) - m_iSpeedStartTime * SPEEDMETER_SLOTSIZE; if (iTimeDiff == 0) { return 0; } return m_iSpeedTotalBytes / iTimeDiff; } void QueueCoordinator::AddSpeedReading(int iBytes) { time_t tCurTime = time(NULL); int iNowSlot = (int)tCurTime / SPEEDMETER_SLOTSIZE; if (g_pOptions->GetAccurateRate()) { #ifdef HAVE_SPINLOCK m_spinlockSpeed.Lock(); #else m_mutexSpeed.Lock(); #endif } while (iNowSlot > m_iSpeedTime[m_iSpeedBytesIndex]) { //record bytes in next slot m_iSpeedBytesIndex++; if (m_iSpeedBytesIndex >= SPEEDMETER_SLOTS) { m_iSpeedBytesIndex = 0; } //Adjust counters with outgoing information. m_iSpeedTotalBytes -= m_iSpeedBytes[m_iSpeedBytesIndex]; //Note we should really use the start time of the next slot //but its easier to just use the outgoing slot time. This //will result in a small error. m_iSpeedStartTime = m_iSpeedTime[m_iSpeedBytesIndex]; //Now reset. m_iSpeedBytes[m_iSpeedBytesIndex] = 0; m_iSpeedTime[m_iSpeedBytesIndex] = iNowSlot; } // Once per second recalculate summary field "m_iSpeedTotalBytes" to recover from possible synchronisation errors if (tCurTime > m_tSpeedCorrection) { int iSpeedTotalBytes = 0; for (int i = 0; i < SPEEDMETER_SLOTS; i++) { iSpeedTotalBytes += m_iSpeedBytes[i]; } m_iSpeedTotalBytes = iSpeedTotalBytes; m_tSpeedCorrection = tCurTime; } if (m_iSpeedTotalBytes == 0) { m_iSpeedStartTime = iNowSlot; } m_iSpeedBytes[m_iSpeedBytesIndex] += iBytes; m_iSpeedTotalBytes += iBytes; m_iAllBytes += iBytes; if (g_pOptions->GetAccurateRate()) { #ifdef HAVE_SPINLOCK m_spinlockSpeed.Unlock(); #else m_mutexSpeed.Unlock(); #endif } } void QueueCoordinator::ResetSpeedStat() { time_t tCurTime = time(NULL); m_iSpeedStartTime = (int)tCurTime / SPEEDMETER_SLOTSIZE; for (int i = 0; i < SPEEDMETER_SLOTS; i++) { m_iSpeedBytes[i] = 0; m_iSpeedTime[i] = m_iSpeedStartTime; } m_iSpeedBytesIndex = 0; m_iSpeedTotalBytes = 0; m_tSpeedCorrection = tCurTime; } long long QueueCoordinator::CalcRemainingSize() { long long lRemainingSize = 0; m_mutexDownloadQueue.Lock(); for (FileQueue::iterator it = m_DownloadQueue.GetFileQueue()->begin(); it != m_DownloadQueue.GetFileQueue()->end(); it++) { FileInfo* pFileInfo = *it; if (!pFileInfo->GetPaused() && !pFileInfo->GetDeleted()) { lRemainingSize += pFileInfo->GetRemainingSize(); } } m_mutexDownloadQueue.Unlock(); return lRemainingSize; } /* * NOTE: DownloadQueue must be locked prior to call of this function * Returns True if Entry was deleted from Queue or False if it was scheduled for Deletion. * NOTE: "False" does not mean unsuccess; the entry is (or will be) deleted in any case. */ bool QueueCoordinator::DeleteQueueEntry(FileInfo* pFileInfo) { pFileInfo->SetDeleted(true); bool hasDownloads = false; for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { ArticleDownloader* pArticleDownloader = *it; if (pArticleDownloader->GetFileInfo() == pFileInfo) { hasDownloads = true; pArticleDownloader->Stop(); } } if (!hasDownloads) { StatFileInfo(pFileInfo, false); Aspect aspect = { eaFileDeleted, &m_DownloadQueue, pFileInfo->GetNZBInfo(), pFileInfo }; Notify(&aspect); DeleteFileInfo(pFileInfo, false); } return hasDownloads; } void QueueCoordinator::Stop() { Thread::Stop(); debug("Stopping ArticleDownloads"); m_mutexDownloadQueue.Lock(); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { (*it)->Stop(); } m_mutexDownloadQueue.Unlock(); debug("ArticleDownloads are notified"); } /* * Returns next article for download. */ bool QueueCoordinator::GetNextArticle(FileInfo* &pFileInfo, ArticleInfo* &pArticleInfo) { // find an unpaused file with the highest priority, then take the next article from the file. // if the file doesn't have any articles left for download, we store that fact and search again, // ignoring all files which were previously marked as not having any articles. // special case: if the file has ExtraPriority-flag set, it has the highest priority and the // Paused-flag is ignored. //debug("QueueCoordinator::GetNextArticle()"); bool bOK = false; // pCheckedFiles stores bool* pCheckedFiles = NULL; while (!bOK) { //debug("QueueCoordinator::GetNextArticle() - in loop"); pFileInfo = NULL; int iNum = 0; int iFileNum = 0; for (FileQueue::iterator it = m_DownloadQueue.GetFileQueue()->begin(); it != m_DownloadQueue.GetFileQueue()->end(); it++) { FileInfo* pFileInfo1 = *it; if ((!pCheckedFiles || !pCheckedFiles[iNum]) && !pFileInfo1->GetPaused() && !pFileInfo1->GetDeleted() && (!pFileInfo || (pFileInfo1->GetExtraPriority() == pFileInfo->GetExtraPriority() && pFileInfo1->GetPriority() > pFileInfo->GetPriority()) || (pFileInfo1->GetExtraPriority() > pFileInfo->GetExtraPriority()))) { pFileInfo = pFileInfo1; iFileNum = iNum; } iNum++; } if (!pFileInfo) { // there are no more files for download break; } if (pFileInfo->GetArticles()->empty() && g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState->LoadArticles(pFileInfo); } // check if the file has any articles left for download for (FileInfo::Articles::iterator at = pFileInfo->GetArticles()->begin(); at != pFileInfo->GetArticles()->end(); at++) { pArticleInfo = *at; if (pArticleInfo->GetStatus() == 0) { bOK = true; break; } } if (!bOK) { // the file doesn't have any articles left for download, we mark the file as such if (!pCheckedFiles) { int iArrSize = sizeof(bool) * m_DownloadQueue.GetFileQueue()->size(); pCheckedFiles = (bool*)malloc(iArrSize); memset(pCheckedFiles, false, iArrSize); } pCheckedFiles[iFileNum] = true; } } free(pCheckedFiles); return bOK; } void QueueCoordinator::StartArticleDownload(FileInfo* pFileInfo, ArticleInfo* pArticleInfo, NNTPConnection* pConnection) { debug("Starting new ArticleDownloader"); ArticleDownloader* pArticleDownloader = new ArticleDownloader(); pArticleDownloader->SetAutoDestroy(true); pArticleDownloader->Attach(this); pArticleDownloader->SetFileInfo(pFileInfo); pArticleDownloader->SetArticleInfo(pArticleInfo); pArticleDownloader->SetConnection(pConnection); char szInfoName[1024]; snprintf(szInfoName, 1024, "%s%c%s [%i/%i]", pFileInfo->GetNZBInfo()->GetName(), (int)PATH_SEPARATOR, pFileInfo->GetFilename(), pArticleInfo->GetPartNumber(), (int)pFileInfo->GetArticles()->size()); szInfoName[1024-1] = '\0'; pArticleDownloader->SetInfoName(szInfoName); pArticleInfo->SetStatus(ArticleInfo::aiRunning); pFileInfo->SetActiveDownloads(pFileInfo->GetActiveDownloads() + 1); m_ActiveDownloads.push_back(pArticleDownloader); pArticleDownloader->Start(); } DownloadQueue* QueueCoordinator::LockQueue() { m_mutexDownloadQueue.Lock(); return &m_DownloadQueue; } void QueueCoordinator::UnlockQueue() { m_mutexDownloadQueue.Unlock(); } void QueueCoordinator::Update(Subject* Caller, void* Aspect) { debug("Notification from ArticleDownloader received"); ArticleDownloader* pArticleDownloader = (ArticleDownloader*) Caller; if ((pArticleDownloader->GetStatus() == ArticleDownloader::adFinished) || (pArticleDownloader->GetStatus() == ArticleDownloader::adFailed) || (pArticleDownloader->GetStatus() == ArticleDownloader::adRetry)) { ArticleCompleted(pArticleDownloader); } } void QueueCoordinator::ArticleCompleted(ArticleDownloader* pArticleDownloader) { debug("Article downloaded"); FileInfo* pFileInfo = pArticleDownloader->GetFileInfo(); ArticleInfo* pArticleInfo = pArticleDownloader->GetArticleInfo(); bool bPaused = false; bool fileCompleted = false; m_mutexDownloadQueue.Lock(); if (pArticleDownloader->GetStatus() == ArticleDownloader::adFinished) { pArticleInfo->SetStatus(ArticleInfo::aiFinished); pFileInfo->SetSuccessSize(pFileInfo->GetSuccessSize() + pArticleInfo->GetSize()); pFileInfo->GetNZBInfo()->SetCurrentSuccessSize(pFileInfo->GetNZBInfo()->GetCurrentSuccessSize() + pArticleInfo->GetSize()); pFileInfo->GetNZBInfo()->SetParCurrentSuccessSize(pFileInfo->GetNZBInfo()->GetParCurrentSuccessSize() + (pFileInfo->GetParFile() ? pArticleInfo->GetSize() : 0)); pFileInfo->SetSuccessArticles(pFileInfo->GetSuccessArticles() + 1); } else if (pArticleDownloader->GetStatus() == ArticleDownloader::adFailed) { pArticleInfo->SetStatus(ArticleInfo::aiFailed); pFileInfo->SetFailedSize(pFileInfo->GetFailedSize() + pArticleInfo->GetSize()); pFileInfo->GetNZBInfo()->SetCurrentFailedSize(pFileInfo->GetNZBInfo()->GetCurrentFailedSize() + pArticleInfo->GetSize()); pFileInfo->GetNZBInfo()->SetParCurrentFailedSize(pFileInfo->GetNZBInfo()->GetParCurrentFailedSize() + (pFileInfo->GetParFile() ? pArticleInfo->GetSize() : 0)); pFileInfo->SetFailedArticles(pFileInfo->GetFailedArticles() + 1); } else if (pArticleDownloader->GetStatus() == ArticleDownloader::adRetry) { pArticleInfo->SetStatus(ArticleInfo::aiUndefined); bPaused = true; } if (!bPaused) { pFileInfo->SetRemainingSize(pFileInfo->GetRemainingSize() - pArticleInfo->GetSize()); pFileInfo->SetCompleted(pFileInfo->GetCompleted() + 1); fileCompleted = (int)pFileInfo->GetArticles()->size() == pFileInfo->GetCompleted(); pFileInfo->GetNZBInfo()->GetServerStats()->Add(pArticleDownloader->GetServerStats()); } if (!pFileInfo->GetFilenameConfirmed() && pArticleDownloader->GetStatus() == ArticleDownloader::adFinished && pArticleDownloader->GetArticleFilename()) { pFileInfo->SetFilename(pArticleDownloader->GetArticleFilename()); pFileInfo->SetFilenameConfirmed(true); if (g_pOptions->GetDupeCheck() && pFileInfo->GetNZBInfo()->GetDupeMode() != dmForce && !pFileInfo->GetNZBInfo()->GetManyDupeFiles() && Util::FileExists(pFileInfo->GetNZBInfo()->GetDestDir(), pFileInfo->GetFilename())) { warn("File \"%s\" seems to be duplicate, cancelling download and deleting file from queue", pFileInfo->GetFilename()); fileCompleted = false; pFileInfo->SetAutoDeleted(true); DeleteQueueEntry(pFileInfo); } } bool deleteFileObj = false; if (fileCompleted && !IsStopped() && !pFileInfo->GetDeleted()) { // all jobs done m_mutexDownloadQueue.Unlock(); pArticleDownloader->CompleteFileParts(); m_mutexDownloadQueue.Lock(); deleteFileObj = true; } CheckHealth(pFileInfo); bool hasOtherDownloaders = false; for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { ArticleDownloader* pDownloader = *it; if (pDownloader != pArticleDownloader && pDownloader->GetFileInfo() == pFileInfo) { hasOtherDownloaders = true; break; } } deleteFileObj |= pFileInfo->GetDeleted() && !hasOtherDownloaders; // remove downloader from downloader list m_ActiveDownloads.erase(std::find(m_ActiveDownloads.begin(), m_ActiveDownloads.end(), pArticleDownloader)); pFileInfo->SetActiveDownloads(pFileInfo->GetActiveDownloads() - 1); if (deleteFileObj) { bool fileDeleted = pFileInfo->GetDeleted(); // delete File from Queue pFileInfo->SetDeleted(true); StatFileInfo(pFileInfo, fileCompleted); Aspect aspect = { fileCompleted && !fileDeleted ? eaFileCompleted : eaFileDeleted, &m_DownloadQueue, pFileInfo->GetNZBInfo(), pFileInfo }; Notify(&aspect); DeleteFileInfo(pFileInfo, fileCompleted); if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState->SaveDownloadQueue(&m_DownloadQueue); } } m_mutexDownloadQueue.Unlock(); } void QueueCoordinator::StatFileInfo(FileInfo* pFileInfo, bool bCompleted) { NZBInfo* pNZBInfo = pFileInfo->GetNZBInfo(); if (bCompleted || pNZBInfo->GetDeleting()) { pNZBInfo->SetSuccessSize(pNZBInfo->GetSuccessSize() + pFileInfo->GetSuccessSize()); pNZBInfo->SetFailedSize(pNZBInfo->GetFailedSize() + pFileInfo->GetFailedSize()); pNZBInfo->SetFailedArticles(pNZBInfo->GetFailedArticles() + pFileInfo->GetFailedArticles() + pFileInfo->GetMissedArticles()); pNZBInfo->SetSuccessArticles(pNZBInfo->GetSuccessArticles() + pFileInfo->GetSuccessArticles()); if (pFileInfo->GetParFile()) { pNZBInfo->SetParSuccessSize(pNZBInfo->GetParSuccessSize() + pFileInfo->GetSuccessSize()); pNZBInfo->SetParFailedSize(pNZBInfo->GetParFailedSize() + pFileInfo->GetFailedSize()); } } else if (!pNZBInfo->GetDeleting() && !pNZBInfo->GetParCleanup()) { // file deleted but not the whole nzb and not par-cleanup pNZBInfo->SetFileCount(pNZBInfo->GetFileCount() - 1); pNZBInfo->SetSize(pNZBInfo->GetSize() - pFileInfo->GetSize()); pNZBInfo->SetCurrentSuccessSize(pNZBInfo->GetCurrentSuccessSize() - pFileInfo->GetSuccessSize()); pNZBInfo->SetFailedSize(pNZBInfo->GetFailedSize() - pFileInfo->GetMissedSize()); pNZBInfo->SetCurrentFailedSize(pNZBInfo->GetCurrentFailedSize() - pFileInfo->GetFailedSize() - pFileInfo->GetMissedSize()); pNZBInfo->SetTotalArticles(pNZBInfo->GetTotalArticles() - pFileInfo->GetTotalArticles()); if (pFileInfo->GetParFile()) { pNZBInfo->SetParSize(pNZBInfo->GetParSize() - pFileInfo->GetSize()); pNZBInfo->SetParCurrentSuccessSize(pNZBInfo->GetParCurrentSuccessSize() - pFileInfo->GetSuccessSize()); pNZBInfo->SetParFailedSize(pNZBInfo->GetParFailedSize() - pFileInfo->GetMissedSize()); pNZBInfo->SetParCurrentFailedSize(pNZBInfo->GetParCurrentFailedSize() - pFileInfo->GetFailedSize() - pFileInfo->GetMissedSize()); } } } void QueueCoordinator::DeleteFileInfo(FileInfo* pFileInfo, bool bCompleted) { for (FileQueue::iterator it = m_DownloadQueue.GetFileQueue()->begin(); it != m_DownloadQueue.GetFileQueue()->end(); it++) { FileInfo* pa = *it; if (pa == pFileInfo) { m_DownloadQueue.GetFileQueue()->erase(it); break; } } if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState->DiscardFile(pFileInfo); } if (!bCompleted) { DiscardDiskFile(pFileInfo); } delete pFileInfo; } void QueueCoordinator::DiscardDiskFile(FileInfo* pFileInfo) { // 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() && pFileInfo->GetOutputFilename()) { remove(pFileInfo->GetOutputFilename()); } } void QueueCoordinator::CheckHealth(FileInfo* pFileInfo) { if (g_pOptions->GetHealthCheck() == Options::hcNone || pFileInfo->GetNZBInfo()->GetHealthPaused() || pFileInfo->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsHealth || pFileInfo->GetNZBInfo()->CalcHealth() >= pFileInfo->GetNZBInfo()->CalcCriticalHealth()) { return; } if (g_pOptions->GetHealthCheck() == Options::hcPause) { warn("Pausing %s due to health %.1f%% below critical %.1f%%", pFileInfo->GetNZBInfo()->GetName(), pFileInfo->GetNZBInfo()->CalcHealth() / 10.0, pFileInfo->GetNZBInfo()->CalcCriticalHealth() / 10.0); pFileInfo->GetNZBInfo()->SetHealthPaused(true); m_QueueEditor.LockedEditEntry(&m_DownloadQueue, pFileInfo->GetID(), false, QueueEditor::eaGroupPause, 0, NULL); } else if (g_pOptions->GetHealthCheck() == Options::hcDelete) { warn("Cancelling download and deleting %s due to health %.1f%% below critical %.1f%%", pFileInfo->GetNZBInfo()->GetName(), pFileInfo->GetNZBInfo()->CalcHealth() / 10.0, pFileInfo->GetNZBInfo()->CalcCriticalHealth() / 10.0); pFileInfo->GetNZBInfo()->SetDeleteStatus(NZBInfo::dsHealth); m_QueueEditor.LockedEditEntry(&m_DownloadQueue, pFileInfo->GetID(), false, QueueEditor::eaGroupDelete, 0, NULL); } } void QueueCoordinator::LogDebugInfo() { debug("--------------------------------------------"); debug("Dumping debug debug to log"); debug("--------------------------------------------"); debug(" SpeedMeter"); debug(" ----------"); float fSpeed = (float)(CalcCurrentDownloadSpeed() / 1024.0); int iTimeDiff = (int)time(NULL) - m_iSpeedStartTime * SPEEDMETER_SLOTSIZE; debug(" Speed: %f", fSpeed); debug(" SpeedStartTime: %i", m_iSpeedStartTime); debug(" SpeedTotalBytes: %i", m_iSpeedTotalBytes); debug(" SpeedBytesIndex: %i", m_iSpeedBytesIndex); debug(" AllBytes: %i", m_iAllBytes); debug(" Time: %i", (int)time(NULL)); debug(" TimeDiff: %i", iTimeDiff); for (int i=0; i < SPEEDMETER_SLOTS; i++) { debug(" Bytes[%i]: %i, Time[%i]: %i", i, m_iSpeedBytes[i], i, m_iSpeedTime[i]); } debug(" QueueCoordinator"); debug(" ----------------"); m_mutexDownloadQueue.Lock(); debug(" Active Downloads: %i", m_ActiveDownloads.size()); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { ArticleDownloader* pArticleDownloader = *it; pArticleDownloader->LogDebugInfo(); } m_mutexDownloadQueue.Unlock(); debug(""); g_pServerPool->LogDebugInfo(); } void QueueCoordinator::ResetHangingDownloads() { if (g_pOptions->GetTerminateTimeout() == 0 && g_pOptions->GetConnectionTimeout() == 0) { return; } m_mutexDownloadQueue.Lock(); time_t tm = ::time(NULL); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end();) { ArticleDownloader* pArticleDownloader = *it; if (tm - pArticleDownloader->GetLastUpdateTime() > g_pOptions->GetConnectionTimeout() + 1 && pArticleDownloader->GetStatus() == ArticleDownloader::adRunning) { error("Cancelling hanging download %s", pArticleDownloader->GetInfoName()); pArticleDownloader->Stop(); } if (tm - pArticleDownloader->GetLastUpdateTime() > g_pOptions->GetTerminateTimeout() && pArticleDownloader->GetStatus() == ArticleDownloader::adRunning) { ArticleInfo* pArticleInfo = pArticleDownloader->GetArticleInfo(); debug("Terminating hanging download %s", pArticleDownloader->GetInfoName()); if (pArticleDownloader->Terminate()) { error("Terminated hanging download %s", pArticleDownloader->GetInfoName()); pArticleInfo->SetStatus(ArticleInfo::aiUndefined); } else { error("Could not terminate hanging download %s", Util::BaseFileName(pArticleInfo->GetResultFilename())); } m_ActiveDownloads.erase(it); pArticleDownloader->GetFileInfo()->SetActiveDownloads(pArticleDownloader->GetFileInfo()->GetActiveDownloads() - 1); // it's not safe to destroy pArticleDownloader, because the state of object is unknown delete pArticleDownloader; it = m_ActiveDownloads.begin(); continue; } it++; } m_mutexDownloadQueue.Unlock(); } void QueueCoordinator::EnterLeaveStandBy(bool bEnter) { m_mutexStat.Lock(); m_bStandBy = bEnter; if (bEnter) { m_tPausedFrom = time(NULL); } else { if (m_tStartDownload == 0) { m_tStartDownload = time(NULL); } else { m_tStartDownload += time(NULL) - m_tPausedFrom; } m_tPausedFrom = 0; ResetSpeedStat(); } m_mutexStat.Unlock(); } void QueueCoordinator::CalcStat(int* iUpTimeSec, int* iDnTimeSec, long long* iAllBytes, bool* bStandBy) { m_mutexStat.Lock(); if (m_tStartServer > 0) { *iUpTimeSec = (int)(time(NULL) - m_tStartServer); } else { *iUpTimeSec = 0; } *bStandBy = m_bStandBy; if (m_bStandBy) { *iDnTimeSec = (int)(m_tPausedFrom - m_tStartDownload); } else { *iDnTimeSec = (int)(time(NULL) - m_tStartDownload); } *iAllBytes = m_iAllBytes; m_mutexStat.Unlock(); } /* * Detects large step changes of system time and adjust statistics. */ void QueueCoordinator::AdjustStartTime() { time_t m_tCurTime = time(NULL); time_t tDiff = m_tCurTime - m_tLastCheck; if (tDiff > 60 || tDiff < 0) { m_tStartServer += tDiff + 1; // "1" because the method is called once per second if (m_tStartDownload != 0 && !m_bStandBy) { m_tStartDownload += tDiff + 1; } } m_tLastCheck = m_tCurTime; } bool QueueCoordinator::SetQueueEntryNZBCategory(NZBInfo* pNZBInfo, const char* szCategory) { if (pNZBInfo->GetPostProcess()) { error("Could not change category for %s. File in post-process-stage", pNZBInfo->GetName()); return false; } char szOldDestDir[1024]; strncpy(szOldDestDir, pNZBInfo->GetDestDir(), 1024); szOldDestDir[1024-1] = '\0'; pNZBInfo->SetCategory(szCategory); pNZBInfo->BuildDestDirName(); bool bDirUnchanged = !strcmp(pNZBInfo->GetDestDir(), szOldDestDir); bool bOK = bDirUnchanged || ArticleDownloader::MoveCompletedFiles(pNZBInfo, szOldDestDir); return bOK; } bool QueueCoordinator::SetQueueEntryNZBName(NZBInfo* pNZBInfo, const char* szName) { if (pNZBInfo->GetPostProcess()) { error("Could not rename %s. File in post-process-stage", pNZBInfo->GetName()); return false; } if (strlen(szName) == 0) { error("Could not rename %s. The new name cannot be empty", pNZBInfo->GetName()); return false; } char szOldDestDir[1024]; strncpy(szOldDestDir, pNZBInfo->GetDestDir(), 1024); szOldDestDir[1024-1] = '\0'; char szNZBNicename[1024]; NZBInfo::MakeNiceNZBName(szName, szNZBNicename, sizeof(szNZBNicename), false); pNZBInfo->SetName(szNZBNicename); pNZBInfo->BuildDestDirName(); bool bDirUnchanged = !strcmp(pNZBInfo->GetDestDir(), szOldDestDir); bool bOK = bDirUnchanged || ArticleDownloader::MoveCompletedFiles(pNZBInfo, szOldDestDir); return bOK; } /* * NOTE: DownloadQueue must be locked prior to call of this function */ bool QueueCoordinator::MergeQueueEntries(NZBInfo* pDestNZBInfo, NZBInfo* pSrcNZBInfo) { if (pDestNZBInfo->GetPostProcess() || pSrcNZBInfo->GetPostProcess()) { error("Could not merge %s and %s. File in post-process-stage", pDestNZBInfo->GetName(), pSrcNZBInfo->GetName()); return false; } // set new dest directory, new category and move downloaded files to new dest directory pSrcNZBInfo->SetFilename(pSrcNZBInfo->GetFilename()); SetQueueEntryNZBCategory(pSrcNZBInfo, pDestNZBInfo->GetCategory()); // reattach file items to new NZBInfo-object for (FileQueue::iterator it = m_DownloadQueue.GetFileQueue()->begin(); it != m_DownloadQueue.GetFileQueue()->end(); it++) { FileInfo* pFileInfo = *it; if (pFileInfo->GetNZBInfo() == pSrcNZBInfo) { pFileInfo->SetNZBInfo(pDestNZBInfo); } } pDestNZBInfo->SetFileCount(pDestNZBInfo->GetFileCount() + pSrcNZBInfo->GetFileCount()); pDestNZBInfo->SetFullContentHash(0); pDestNZBInfo->SetFilteredContentHash(0); pDestNZBInfo->SetSize(pDestNZBInfo->GetSize() + pSrcNZBInfo->GetSize()); pDestNZBInfo->SetSuccessSize(pDestNZBInfo->GetSuccessSize() + pSrcNZBInfo->GetSuccessSize()); pDestNZBInfo->SetCurrentSuccessSize(pDestNZBInfo->GetCurrentSuccessSize() + pSrcNZBInfo->GetCurrentSuccessSize()); pDestNZBInfo->SetFailedSize(pDestNZBInfo->GetFailedSize() + pSrcNZBInfo->GetFailedSize()); pDestNZBInfo->SetCurrentFailedSize(pDestNZBInfo->GetCurrentFailedSize() + pSrcNZBInfo->GetCurrentFailedSize()); pDestNZBInfo->SetParSize(pDestNZBInfo->GetParSize() + pSrcNZBInfo->GetParSize()); pDestNZBInfo->SetParSuccessSize(pDestNZBInfo->GetParSuccessSize() + pSrcNZBInfo->GetParSuccessSize()); pDestNZBInfo->SetParCurrentSuccessSize(pDestNZBInfo->GetParCurrentSuccessSize() + pSrcNZBInfo->GetParCurrentSuccessSize()); pDestNZBInfo->SetParFailedSize(pDestNZBInfo->GetParFailedSize() + pSrcNZBInfo->GetParFailedSize()); pDestNZBInfo->SetParCurrentFailedSize(pDestNZBInfo->GetParCurrentFailedSize() + pSrcNZBInfo->GetParCurrentFailedSize()); // reattach completed file items to new NZBInfo-object for (NZBInfo::Files::iterator it = pSrcNZBInfo->GetCompletedFiles()->begin(); it != pSrcNZBInfo->GetCompletedFiles()->end(); it++) { char* szFileName = *it; pDestNZBInfo->GetCompletedFiles()->push_back(szFileName); } pSrcNZBInfo->GetCompletedFiles()->clear(); // concatenate QueuedFilenames using character '|' as separator int iLen = strlen(pDestNZBInfo->GetQueuedFilename()) + strlen(pSrcNZBInfo->GetQueuedFilename()) + 1; char* szQueuedFilename = (char*)malloc(iLen); snprintf(szQueuedFilename, iLen, "%s|%s", pDestNZBInfo->GetQueuedFilename(), pSrcNZBInfo->GetQueuedFilename()); szQueuedFilename[iLen - 1] = '\0'; pDestNZBInfo->SetQueuedFilename(szQueuedFilename); free(szQueuedFilename); return true; } /* * Creates new nzb-item out of existing files from other nzb-items. * If any of file-items is being downloaded the command fail. * For each file-item an event "eaFileDeleted" is fired. * * NOTE: DownloadQueue must be locked prior to call of this function */ bool QueueCoordinator::SplitQueueEntries(FileQueue* pFileList, const char* szName, NZBInfo** pNewNZBInfo) { if (pFileList->empty()) { return false; } NZBInfo* pSrcNZBInfo = NULL; for (FileQueue::iterator it = pFileList->begin(); it != pFileList->end(); it++) { FileInfo* pFileInfo = *it; if (pFileInfo->GetActiveDownloads() > 0 || pFileInfo->GetCompleted() > 0) { error("Could not split %s. File is already (partially) downloaded", pFileInfo->GetFilename()); return false; } if (pFileInfo->GetNZBInfo()->GetPostProcess()) { error("Could not split %s. File in post-process-stage", pFileInfo->GetFilename()); return false; } if (!pSrcNZBInfo) { pSrcNZBInfo = pFileInfo->GetNZBInfo(); } } NZBInfo* pNZBInfo = new NZBInfo(); pNZBInfo->Retain(); m_DownloadQueue.GetNZBInfoList()->Add(pNZBInfo); pNZBInfo->SetFilename(pSrcNZBInfo->GetFilename()); pNZBInfo->SetName(szName); pNZBInfo->SetCategory(pSrcNZBInfo->GetCategory()); pNZBInfo->SetFullContentHash(0); pNZBInfo->SetFilteredContentHash(0); pNZBInfo->BuildDestDirName(); pNZBInfo->SetQueuedFilename(pSrcNZBInfo->GetQueuedFilename()); pNZBInfo->GetParameters()->CopyFrom(pSrcNZBInfo->GetParameters()); pSrcNZBInfo->SetFullContentHash(0); pSrcNZBInfo->SetFilteredContentHash(0); for (FileQueue::iterator it = pFileList->begin(); it != pFileList->end(); it++) { FileInfo* pFileInfo = *it; Aspect aspect = { eaFileDeleted, &m_DownloadQueue, pFileInfo->GetNZBInfo(), pFileInfo }; Notify(&aspect); pFileInfo->SetNZBInfo(pNZBInfo); pSrcNZBInfo->SetFileCount(pSrcNZBInfo->GetFileCount() - 1); pSrcNZBInfo->SetSize(pSrcNZBInfo->GetSize() - pFileInfo->GetSize()); pSrcNZBInfo->SetSuccessSize(pSrcNZBInfo->GetSuccessSize() - pFileInfo->GetSuccessSize()); pSrcNZBInfo->SetCurrentSuccessSize(pSrcNZBInfo->GetCurrentSuccessSize() - pFileInfo->GetSuccessSize()); pSrcNZBInfo->SetFailedSize(pSrcNZBInfo->GetFailedSize() - pFileInfo->GetFailedSize() - pFileInfo->GetMissedSize()); pSrcNZBInfo->SetCurrentFailedSize(pSrcNZBInfo->GetCurrentFailedSize() - pFileInfo->GetFailedSize() - pFileInfo->GetMissedSize()); pNZBInfo->SetFileCount(pNZBInfo->GetFileCount() + 1); pNZBInfo->SetSize(pNZBInfo->GetSize() + pFileInfo->GetSize()); pNZBInfo->SetSuccessSize(pNZBInfo->GetSuccessSize() + pFileInfo->GetSuccessSize()); pNZBInfo->SetCurrentSuccessSize(pNZBInfo->GetCurrentSuccessSize() + pFileInfo->GetSuccessSize()); pNZBInfo->SetFailedSize(pNZBInfo->GetFailedSize() + pFileInfo->GetFailedSize() + pFileInfo->GetMissedSize()); pNZBInfo->SetCurrentFailedSize(pNZBInfo->GetCurrentFailedSize() + pFileInfo->GetFailedSize() + pFileInfo->GetMissedSize()); if (pFileInfo->GetParFile()) { pSrcNZBInfo->SetParSize(pSrcNZBInfo->GetParSize() - pFileInfo->GetSize()); pSrcNZBInfo->SetParSuccessSize(pSrcNZBInfo->GetParSuccessSize() - pFileInfo->GetSuccessSize()); pSrcNZBInfo->SetParCurrentSuccessSize(pSrcNZBInfo->GetParCurrentSuccessSize() - pFileInfo->GetSuccessSize()); pSrcNZBInfo->SetParFailedSize(pSrcNZBInfo->GetParFailedSize() - pFileInfo->GetFailedSize() - pFileInfo->GetMissedSize()); pSrcNZBInfo->SetParCurrentFailedSize(pSrcNZBInfo->GetParCurrentFailedSize() - pFileInfo->GetFailedSize() - pFileInfo->GetMissedSize()); pNZBInfo->SetParSize(pNZBInfo->GetParSize() + pFileInfo->GetSize()); pNZBInfo->SetParSuccessSize(pNZBInfo->GetParSuccessSize() + pFileInfo->GetSuccessSize()); pNZBInfo->SetParCurrentSuccessSize(pNZBInfo->GetParCurrentSuccessSize() + pFileInfo->GetSuccessSize()); pNZBInfo->SetParFailedSize(pNZBInfo->GetParFailedSize() + pFileInfo->GetFailedSize() + pFileInfo->GetMissedSize()); pNZBInfo->SetParCurrentFailedSize(pNZBInfo->GetParCurrentFailedSize() + pFileInfo->GetFailedSize() + pFileInfo->GetMissedSize()); } } pNZBInfo->Release(); *pNewNZBInfo = pNZBInfo; return true; } nzbget-12.0+dfsg/QueueCoordinator.h000066400000000000000000000106451226450633000173140ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 832 $ * $Date: 2013-09-20 22:45:07 +0200 (Fri, 20 Sep 2013) $ * */ #ifndef QUEUECOORDINATOR_H #define QUEUECOORDINATOR_H #include #include #include #include "Thread.h" #include "NZBFile.h" #include "ArticleDownloader.h" #include "DownloadInfo.h" #include "Observer.h" #include "QueueEditor.h" #include "NNTPConnection.h" class QueueCoordinator : public Thread, public Observer, public Subject, public DownloadSpeedMeter, public DownloadQueueHolder { public: typedef std::list ActiveDownloads; enum EAspectAction { eaNZBFileFound, eaNZBFileAdded, eaFileCompleted, eaFileDeleted }; struct Aspect { EAspectAction eAction; DownloadQueue* pDownloadQueue; NZBInfo* pNZBInfo; FileInfo* pFileInfo; }; private: DownloadQueue m_DownloadQueue; ActiveDownloads m_ActiveDownloads; QueueEditor m_QueueEditor; Mutex m_mutexDownloadQueue; bool m_bHasMoreJobs; int m_iDownloadsLimit; int m_iServerConfigGeneration; // statistics static const int SPEEDMETER_SLOTS = 30; static const int SPEEDMETER_SLOTSIZE = 1; //Split elapsed time into this number of secs. int m_iSpeedBytes[SPEEDMETER_SLOTS]; int m_iSpeedTotalBytes; int m_iSpeedTime[SPEEDMETER_SLOTS]; int m_iSpeedStartTime; time_t m_tSpeedCorrection; #ifdef HAVE_SPINLOCK SpinLock m_spinlockSpeed; #else Mutex m_mutexSpeed; #endif int m_iSpeedBytesIndex; long long m_iAllBytes; time_t m_tStartServer; time_t m_tLastCheck; time_t m_tStartDownload; time_t m_tPausedFrom; bool m_bStandBy; Mutex m_mutexStat; bool GetNextArticle(FileInfo* &pFileInfo, ArticleInfo* &pArticleInfo); void StartArticleDownload(FileInfo* pFileInfo, ArticleInfo* pArticleInfo, NNTPConnection* pConnection); void ArticleCompleted(ArticleDownloader* pArticleDownloader); void DeleteFileInfo(FileInfo* pFileInfo, bool bCompleted); void StatFileInfo(FileInfo* pFileInfo, bool bCompleted); void CheckHealth(FileInfo* pFileInfo); void ResetHangingDownloads(); void ResetSpeedStat(); void EnterLeaveStandBy(bool bEnter); void AdjustStartTime(); void AdjustDownloadsLimit(); public: QueueCoordinator(); virtual ~QueueCoordinator(); virtual void Run(); virtual void Stop(); void Update(Subject* Caller, void* Aspect); // statistics long long CalcRemainingSize(); virtual int 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* pNZBFile, bool bAddFirst); void AddFileInfosToFileQueue(NZBFile* pNZBFile, FileQueue* pFileQueue, bool bAddFirst); bool HasMoreJobs() { return m_bHasMoreJobs; } bool GetStandBy() { return m_bStandBy; } bool DeleteQueueEntry(FileInfo* pFileInfo); bool SetQueueEntryNZBCategory(NZBInfo* pNZBInfo, const char* szCategory); bool SetQueueEntryNZBName(NZBInfo* pNZBInfo, const char* szName); bool MergeQueueEntries(NZBInfo* pDestNZBInfo, NZBInfo* pSrcNZBInfo); bool SplitQueueEntries(FileQueue* pFileList, const char* szName, NZBInfo** pNewNZBInfo); void DiscardDiskFile(FileInfo* pFileInfo); QueueEditor* GetQueueEditor() { return &m_QueueEditor; } void LogDebugInfo(); }; #endif nzbget-12.0+dfsg/QueueEditor.cpp000066400000000000000000000642361226450633000166170ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 916 $ * $Date: 2013-12-10 21:25:07 +0100 (Tue, 10 Dec 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #include #include #include #ifndef WIN32 #include #include #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 (FileQueue::iterator it = pDownloadQueue->GetFileQueue()->begin(); it != pDownloadQueue->GetFileQueue()->end(); it++) { FileInfo* pFileInfo = *it; if (pFileInfo->GetID() == iID) { return pFileInfo; } } return NULL; } int QueueEditor::FindFileInfoEntry(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo) { int iEntry = 0; for (FileQueue::iterator it = pDownloadQueue->GetFileQueue()->begin(); it != pDownloadQueue->GetFileQueue()->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 * returns true if successful, false if operation is not possible */ void QueueEditor::DeleteEntry(FileInfo* pFileInfo) { if (pFileInfo->GetNZBInfo()->GetDeleting()) { detail("Deleting file %s from download queue", pFileInfo->GetFilename()); } else { info("Deleting file %s from download queue", pFileInfo->GetFilename()); } g_pQueueCoordinator->DeleteQueueEntry(pFileInfo); } /* * Moves entry 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->GetFileQueue()->size() - 1) { iNewEntry = (int)pDownloadQueue->GetFileQueue()->size() - 1; } if (iNewEntry >= 0 && (unsigned int)iNewEntry <= pDownloadQueue->GetFileQueue()->size() - 1) { FileInfo* fi = pDownloadQueue->GetFileQueue()->at(iEntry); pDownloadQueue->GetFileQueue()->erase(pDownloadQueue->GetFileQueue()->begin() + iEntry); pDownloadQueue->GetFileQueue()->insert(pDownloadQueue->GetFileQueue()->begin() + iNewEntry, fi); } } } /* * Set priority for entry * returns true if successful, false if operation is not possible */ void QueueEditor::SetPriorityEntry(FileInfo* pFileInfo, const char* szPriority) { debug("Setting priority %s for file %s", szPriority, pFileInfo->GetFilename()); int iPriority = atoi(szPriority); pFileInfo->SetPriority(iPriority); } bool QueueEditor::EditEntry(int ID, bool bSmartOrder, EEditAction eAction, int iOffset, const char* szText) { IDList cIDList; cIDList.clear(); cIDList.push_back(ID); return EditList(&cIDList, NULL, mmID, bSmartOrder, eAction, iOffset, szText); } bool QueueEditor::LockedEditEntry(DownloadQueue* pDownloadQueue, int ID, bool bSmartOrder, EEditAction eAction, int iOffset, const char* szText) { IDList cIDList; cIDList.clear(); cIDList.push_back(ID); return InternEditList(pDownloadQueue, &cIDList, bSmartOrder, eAction, iOffset, szText); } bool QueueEditor::EditList(IDList* pIDList, NameList* pNameList, EMatchMode eMatchMode, bool bSmartOrder, EEditAction eAction, int iOffset, const char* szText) { DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue(); bool bOK = true; if (pNameList) { pIDList = new IDList(); bOK = BuildIDListFromNameList(pDownloadQueue, pIDList, pNameList, eMatchMode, eAction); } bOK = bOK && (InternEditList(pDownloadQueue, pIDList, bSmartOrder, eAction, iOffset, szText) || eMatchMode == mmRegEx); if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState->SaveDownloadQueue(pDownloadQueue); } g_pQueueCoordinator->UnlockQueue(); if (pNameList) { delete pIDList; } return bOK; } bool QueueEditor::LockedEditList(DownloadQueue* pDownloadQueue, IDList* pIDList, bool bSmartOrder, EEditAction eAction, int iOffset, const char* szText) { return InternEditList(pDownloadQueue, pIDList, bSmartOrder, eAction, iOffset, szText); } bool QueueEditor::InternEditList(DownloadQueue* pDownloadQueue, IDList* pIDList, bool bSmartOrder, EEditAction eAction, int iOffset, const char* szText) { if (eAction == eaGroupMoveOffset) { AlignAffectedGroups(pDownloadQueue, pIDList, bSmartOrder, iOffset); } ItemList cItemList; PrepareList(pDownloadQueue, &cItemList, pIDList, bSmartOrder, eAction, iOffset); switch (eAction) { case eaFilePauseAllPars: case eaFilePauseExtraPars: PauseParsInGroups(&cItemList, eAction == eaFilePauseExtraPars); break; case eaGroupMerge: return MergeGroups(pDownloadQueue, &cItemList); case eaFileSplit: return SplitGroup(pDownloadQueue, &cItemList, szText); case eaFileReorder: ReorderFiles(pDownloadQueue, &cItemList); break; default: 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 eaFileSetPriority: SetPriorityEntry(pItem->m_pFileInfo, szText); break; case eaGroupSetCategory: SetNZBCategory(pItem->m_pFileInfo->GetNZBInfo(), szText); break; case eaGroupSetName: SetNZBName(pItem->m_pFileInfo->GetNZBInfo(), szText); break; case eaGroupSetDupeKey: case eaGroupSetDupeScore: case eaGroupSetDupeMode: SetNZBDupeParam(pItem->m_pFileInfo->GetNZBInfo(), eAction, szText); break; case eaGroupSetParameter: SetNZBParameter(pItem->m_pFileInfo->GetNZBInfo(), szText); break; case eaGroupPause: case eaGroupResume: case eaGroupDelete: case eaGroupDupeDelete: case eaGroupFinalDelete: case eaGroupMoveTop: case eaGroupMoveBottom: case eaGroupMoveOffset: case eaGroupPauseAllPars: case eaGroupPauseExtraPars: case eaGroupSetPriority: EditGroup(pDownloadQueue, pItem->m_pFileInfo, eAction, iOffset, szText); break; case eaFilePauseAllPars: case eaFilePauseExtraPars: case eaGroupMerge: case eaFileReorder: case eaFileSplit: // remove compiler warning "enumeration not handled in switch" 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->GetFileQueue()->size(); iStep = 1; } else { iStart = pDownloadQueue->GetFileQueue()->size() - 1; iEnd = -1; iStep = -1; } for (int iIndex = iStart; iIndex != iEnd; iIndex += iStep) { FileInfo* pFileInfo = pDownloadQueue->GetFileQueue()->at(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->GetFileQueue()->size()) - 1) { iWorkOffset = int(pDownloadQueue->GetFileQueue()->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 (FileQueue::iterator it = pDownloadQueue->GetFileQueue()->begin(); it != pDownloadQueue->GetFileQueue()->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::BuildIDListFromNameList(DownloadQueue* pDownloadQueue, IDList* pIDList, NameList* pNameList, EMatchMode eMatchMode, EEditAction eAction) { #ifndef HAVE_REGEX_H if (eMatchMode == mmRegEx) { return false; } #endif std::set uniqueIDs; for (NameList::iterator it = pNameList->begin(); it != pNameList->end(); it++) { const char* szName = *it; RegEx *pRegEx = NULL; if (eMatchMode == mmRegEx) { pRegEx = new RegEx(szName); if (!pRegEx->IsValid()) { delete pRegEx; return false; } } bool bFound = false; for (FileQueue::iterator it2 = pDownloadQueue->GetFileQueue()->begin(); it2 != pDownloadQueue->GetFileQueue()->end(); it2++) { FileInfo* pFileInfo = *it2; if (eAction < eaGroupMoveOffset) { // file action char szFilename[MAX_PATH]; snprintf(szFilename, sizeof(szFilename) - 1, "%s/%s", pFileInfo->GetNZBInfo()->GetName(), Util::BaseFileName(pFileInfo->GetFilename())); if (((!pRegEx && !strcmp(szFilename, szName)) || (pRegEx && pRegEx->Match(szFilename))) && (uniqueIDs.find(pFileInfo->GetID()) == uniqueIDs.end())) { uniqueIDs.insert(pFileInfo->GetID()); pIDList->push_back(pFileInfo->GetID()); bFound = true; } } else { // group action const char *szFilename = pFileInfo->GetNZBInfo()->GetName(); if (((!pRegEx && !strcmp(szFilename, szName)) || (pRegEx && pRegEx->Match(szFilename))) && (uniqueIDs.find(pFileInfo->GetNZBInfo()->GetID()) == uniqueIDs.end())) { uniqueIDs.insert(pFileInfo->GetNZBInfo()->GetID()); pIDList->push_back(pFileInfo->GetID()); bFound = true; } } } delete pRegEx; if (!bFound && (eMatchMode == mmName)) { return false; } } return true; } bool QueueEditor::EditGroup(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo, EEditAction eAction, int iOffset, const char* szText) { IDList cIDList; cIDList.clear(); bool bAllPaused = true; // collecting files belonging to group for (FileQueue::iterator it = pDownloadQueue->GetFileQueue()->begin(); it != pDownloadQueue->GetFileQueue()->end(); it++) { FileInfo* pFileInfo2 = *it; if (pFileInfo2->GetNZBInfo() == pFileInfo->GetNZBInfo()) { cIDList.push_back(pFileInfo2->GetID()); bAllPaused &= pFileInfo2->GetPaused(); } } 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 (pGroupInfo->GetNZBInfo() == pFileInfo->GetNZBInfo()) { 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; } else if (eAction == eaGroupDelete || eAction == eaGroupDupeDelete || eAction == eaGroupFinalDelete) { pFileInfo->GetNZBInfo()->SetDeleting(true); pFileInfo->GetNZBInfo()->SetAvoidHistory(eAction == eaGroupFinalDelete); pFileInfo->GetNZBInfo()->SetDeletePaused(bAllPaused); if (eAction == eaGroupDupeDelete) { pFileInfo->GetNZBInfo()->SetDeleteStatus(NZBInfo::dsDupe); } pFileInfo->GetNZBInfo()->SetCleanupDisk(CanCleanupDisk(pDownloadQueue, pFileInfo->GetNZBInfo())); } EEditAction GroupToFileMap[] = { (EEditAction)0, eaFileMoveOffset, eaFileMoveTop, eaFileMoveBottom, eaFilePause, eaFileResume, eaFileDelete, eaFilePauseAllPars, eaFilePauseExtraPars, eaFileSetPriority, eaFileReorder, eaFileSplit, eaFileMoveOffset, eaFileMoveTop, eaFileMoveBottom, eaFilePause, eaFileResume, eaFileDelete, eaFileDelete, eaFileDelete, eaFilePauseAllPars, eaFilePauseExtraPars, eaFileSetPriority, (EEditAction)0, (EEditAction)0, (EEditAction)0 }; return InternEditList(pDownloadQueue, &cIDList, true, GroupToFileMap[eAction], iOffset, szText); } void QueueEditor::BuildGroupList(DownloadQueue* pDownloadQueue, FileList* pGroupList) { pGroupList->clear(); for (FileQueue::iterator it = pDownloadQueue->GetFileQueue()->begin(); it != pDownloadQueue->GetFileQueue()->end(); it++) { FileInfo* pFileInfo = *it; FileInfo* pGroupInfo = NULL; for (FileList::iterator itg = pGroupList->begin(); itg != pGroupList->end(); itg++) { FileInfo* pGroupInfo1 = *itg; if (pGroupInfo1->GetNZBInfo() == pFileInfo->GetNZBInfo()) { 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 (pItem->m_pFileInfo->GetNZBInfo() == pFileInfo->GetNZBInfo()) { 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 (int i = iNum + 1; i <= (int)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->GetNZBInfo()); } } void QueueEditor::AlignGroup(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo) { FileInfo* pLastFileInfo = NULL; unsigned int iLastNum = 0; unsigned int iNum = 0; while (iNum < pDownloadQueue->GetFileQueue()->size()) { FileInfo* pFileInfo = pDownloadQueue->GetFileQueue()->at(iNum); if (pFileInfo->GetNZBInfo() == pNZBInfo) { if (pLastFileInfo && iNum - iLastNum > 1) { pDownloadQueue->GetFileQueue()->erase(pDownloadQueue->GetFileQueue()->begin() + iNum); pDownloadQueue->GetFileQueue()->insert(pDownloadQueue->GetFileQueue()->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 || (pFirstFileInfo->GetNZBInfo() == pItem->m_pFileInfo->GetNZBInfo())) { 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); } } } } } void QueueEditor::SetNZBCategory(NZBInfo* pNZBInfo, const char* szCategory) { debug("QueueEditor: setting category '%s' for '%s'", szCategory, Util::BaseFileName(pNZBInfo->GetFilename())); g_pQueueCoordinator->SetQueueEntryNZBCategory(pNZBInfo, szCategory); } void QueueEditor::SetNZBName(NZBInfo* pNZBInfo, const char* szName) { debug("QueueEditor: renaming '%s' to '%s'", Util::BaseFileName(pNZBInfo->GetFilename()), szName); g_pQueueCoordinator->SetQueueEntryNZBName(pNZBInfo, szName); } /** * Check if deletion of already downloaded files is possible (when nzb is deleted from queue). * The deletion is most always possible, except the case if all remaining files in queue * (belonging to this nzb-file) are PARS. */ bool QueueEditor::CanCleanupDisk(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo) { for (FileQueue::iterator it = pDownloadQueue->GetFileQueue()->begin(); it != pDownloadQueue->GetFileQueue()->end(); it++) { FileInfo* pFileInfo = *it; if (pFileInfo->GetNZBInfo() == pNZBInfo) { 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")) { // non-par file found return true; } } } return false; } bool QueueEditor::MergeGroups(DownloadQueue* pDownloadQueue, ItemList* pItemList) { if (pItemList->size() == 0) { return false; } bool bOK = true; EditItem* pDestItem = pItemList->front(); for (ItemList::iterator it = pItemList->begin() + 1; it != pItemList->end(); it++) { EditItem* pItem = *it; if (pItem->m_pFileInfo->GetNZBInfo() != pDestItem->m_pFileInfo->GetNZBInfo()) { debug("merge %s to %s", pItem->m_pFileInfo->GetNZBInfo()->GetFilename(), pDestItem->m_pFileInfo->GetNZBInfo()->GetFilename()); if (g_pQueueCoordinator->MergeQueueEntries(pDestItem->m_pFileInfo->GetNZBInfo(), pItem->m_pFileInfo->GetNZBInfo())) { bOK = false; } } delete pItem; } AlignGroup(pDownloadQueue, pDestItem->m_pFileInfo->GetNZBInfo()); delete pDestItem; return bOK; } bool QueueEditor::SplitGroup(DownloadQueue* pDownloadQueue, ItemList* pItemList, const char* szName) { if (pItemList->size() == 0) { return false; } FileQueue* pFileList = new FileQueue(); for (ItemList::iterator it = pItemList->begin(); it != pItemList->end(); it++) { EditItem* pItem = *it; pFileList->push_back(pItem->m_pFileInfo); delete pItem; } NZBInfo* pNewNZBInfo = NULL; bool bOK = g_pQueueCoordinator->SplitQueueEntries(pFileList, szName, &pNewNZBInfo); if (bOK) { AlignGroup(pDownloadQueue, pNewNZBInfo); } delete pFileList; return bOK; } void QueueEditor::ReorderFiles(DownloadQueue* pDownloadQueue, ItemList* pItemList) { if (pItemList->size() == 0) { return; } EditItem* pFirstItem = pItemList->front(); NZBInfo* pNZBInfo = pFirstItem->m_pFileInfo->GetNZBInfo(); unsigned int iInsertPos = 0; // find first file of the group for (FileQueue::iterator it = pDownloadQueue->GetFileQueue()->begin(); it != pDownloadQueue->GetFileQueue()->end(); it++) { FileInfo* pFileInfo = *it; if (pFileInfo->GetNZBInfo() == pNZBInfo) { break; } iInsertPos++; } // now can reorder for (ItemList::iterator it = pItemList->begin(); it != pItemList->end(); it++) { EditItem* pItem = *it; FileInfo* pFileInfo = pItem->m_pFileInfo; // move file item for (FileQueue::iterator it = pDownloadQueue->GetFileQueue()->begin(); it != pDownloadQueue->GetFileQueue()->end(); it++) { FileInfo* pFileInfo1 = *it; if (pFileInfo1 == pFileInfo) { pDownloadQueue->GetFileQueue()->erase(it); pDownloadQueue->GetFileQueue()->insert(pDownloadQueue->GetFileQueue()->begin() + iInsertPos, pFileInfo); iInsertPos++; break; } } delete pItem; } } void QueueEditor::SetNZBParameter(NZBInfo* pNZBInfo, const char* szParamString) { debug("QueueEditor: setting nzb parameter '%s' for '%s'", szParamString, Util::BaseFileName(pNZBInfo->GetFilename())); char* szStr = strdup(szParamString); char* szValue = strchr(szStr, '='); if (szValue) { *szValue = '\0'; szValue++; pNZBInfo->GetParameters()->SetParameter(szStr, szValue); } else { error("Could not set nzb parameter for %s: invalid argument: %s", pNZBInfo->GetName(), szParamString); } free(szStr); } void QueueEditor::SetNZBDupeParam(NZBInfo* pNZBInfo, EEditAction eAction, const char* szText) { debug("QueueEditor: setting dupe parameter %i='%s' for '%s'", (int)eAction, szText, pNZBInfo->GetName()); switch (eAction) { case eaGroupSetDupeKey: pNZBInfo->SetDupeKey(szText); break; case eaGroupSetDupeScore: pNZBInfo->SetDupeScore(atoi(szText)); break; case eaGroupSetDupeMode: { EDupeMode eMode = dmScore; if (!strcasecmp(szText, "SCORE")) { eMode = dmScore; } else if (!strcasecmp(szText, "ALL")) { eMode = dmAll; } else if (!strcasecmp(szText, "FORCE")) { eMode = dmForce; } else { error("Could not set duplicate mode for %s: incorrect mode (%s)", pNZBInfo->GetName(), szText); return; } pNZBInfo->SetDupeMode(eMode); break; } default: // suppress compiler warning break; } } nzbget-12.0+dfsg/QueueEditor.h000066400000000000000000000113451226450633000162550ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 924 $ * $Date: 2013-12-21 22:39:49 +0100 (Sat, 21 Dec 2013) $ * */ #ifndef QUEUEEDITOR_H #define QUEUEEDITOR_H #include #include "DownloadInfo.h" class QueueEditor { public: // NOTE: changes to this enum must be synced with "eRemoteEditAction" in unit "MessageBase.h" enum EEditAction { eaFileMoveOffset = 1, // move to m_iOffset relative to the current position in queue eaFileMoveTop, eaFileMoveBottom, eaFilePause, eaFileResume, eaFileDelete, eaFilePauseAllPars, eaFilePauseExtraPars, eaFileSetPriority, eaFileReorder, eaFileSplit, eaGroupMoveOffset, // move to m_iOffset relative to the current position in queue eaGroupMoveTop, eaGroupMoveBottom, eaGroupPause, eaGroupResume, eaGroupDelete, eaGroupDupeDelete, eaGroupFinalDelete, eaGroupPauseAllPars, eaGroupPauseExtraPars, eaGroupSetPriority, eaGroupSetCategory, eaGroupMerge, eaGroupSetParameter, eaGroupSetName, eaGroupSetDupeKey, eaGroupSetDupeScore, eaGroupSetDupeMode }; enum EMatchMode { mmID = 1, mmName, mmRegEx }; private: class EditItem { public: int m_iOffset; FileInfo* m_pFileInfo; EditItem(FileInfo* pFileInfo, int iOffset); }; typedef std::vector ItemList; typedef std::vector 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, const char* szText); void PrepareList(DownloadQueue* pDownloadQueue, ItemList* pItemList, IDList* pIDList, bool bSmartOrder, EEditAction eAction, int iOffset); bool BuildIDListFromNameList(DownloadQueue* pDownloadQueue, IDList* pIDList, NameList* pNameList, EMatchMode eMatchMode, EEditAction eAction); bool EditGroup(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo, EEditAction eAction, int iOffset, const char* szText); 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, NZBInfo* pNZBInfo); void PauseParsInGroups(ItemList* pItemList, bool bExtraParsOnly); void PausePars(FileList* pFileList, bool bExtraParsOnly); void SetNZBCategory(NZBInfo* pNZBInfo, const char* szCategory); void SetNZBName(NZBInfo* pNZBInfo, const char* szName); bool CanCleanupDisk(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo); bool MergeGroups(DownloadQueue* pDownloadQueue, ItemList* pItemList); bool SplitGroup(DownloadQueue* pDownloadQueue, ItemList* pItemList, const char* szName); void ReorderFiles(DownloadQueue* pDownloadQueue, ItemList* pItemList); void SetNZBParameter(NZBInfo* pNZBInfo, const char* szParamString); void SetNZBDupeParam(NZBInfo* pNZBInfo, EEditAction eAction, const char* szText); void PauseUnpauseEntry(FileInfo* pFileInfo, bool bPause); void DeleteEntry(FileInfo* pFileInfo); void MoveEntry(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo, int iOffset); void SetPriorityEntry(FileInfo* pFileInfo, const char* szPriority); public: QueueEditor(); ~QueueEditor(); bool EditEntry(int ID, bool bSmartOrder, EEditAction eAction, int iOffset, const char* szText); bool EditList(IDList* pIDList, NameList* pNameList, EMatchMode eMatchMode, bool bSmartOrder, EEditAction eAction, int iOffset, const char* szText); bool LockedEditEntry(DownloadQueue* pDownloadQueue, int ID, bool bSmartOrder, EEditAction eAction, int iOffset, const char* szText); bool LockedEditList(DownloadQueue* pDownloadQueue, IDList* pIDList, bool bSmartOrder, EEditAction eAction, int iOffset, const char* szText); }; #endif nzbget-12.0+dfsg/README000066400000000000000000000417631226450633000145400ustar00rootroot00000000000000===================================== NZBGet ReadMe ===================================== This is a short documentation. For more information please visit NZBGet home page at http://nzbget.sourceforge.net Contents -------- 1. About NZBGet 2. Supported OS 3. Prerequisites on POSIX 4. Installation on POSIX 5. Compiling on Windows 6. Configuration 7. Usage 8. Authors 9. Copyright 10. Contact ===================================== 1. About NZBGet ===================================== NZBGet is a binary newsgrabber, which downloads files from usenet based on information given in nzb-files. NZBGet can be used in standalone and in server/client modes. In standalone mode you pass a nzb-file as parameter in command-line, NZBGet downloads listed files and then exits. In server/client mode NZBGet runs as server in background. Then you use client to send requests to server. The sample requests are: download nzb-file, list files in queue, etc. There is also a built-in web-interface. The server has RPC-support and can be controlled from third party applications too. Standalone-tool, server and client are all contained in only one executable file "nzbget". The mode in which the program works depends on command-line parameters passed to the program. ===================================== 2. Supported OS ===================================== 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. It should run at least on: - Linux Debian 5.0 on x86; - Linux with uClibc on MIPSEL and ARM; - OpenBSD 5.0 on x86; - Mac OS X 10.7 Lion on x64; - Windows XP SP3 on x86 and Windows 7 on x64. Clients and servers running on different OS'es may communicate with each other. For example, you can use NZBGet as client on Windows to control your NZBGet-server running on Linux. The download-section of NZBGet web-site provides binary files for Windows. 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". ===================================== 3. Prerequisites on POSIX ===================================== NZBGet is developed on a linux-system, but it should run on other POSIX platforms (see the list of tested platforms above). NZBGet absolutely needs the following libraries: - libstdc++ (usually part of compiler) - libxml2 (http://www.xmlsoft.org) And the following libraries are optional: - 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 (enabled by default): - libpar2 (http://parchive.sourceforge.net) - libsigc++ (http://libsigc.sourceforge.net) - for encrypted connections (TLS/SSL): - GnuTLS (http://www.gnu.org/software/gnutls) or - OpenSSL (http://www.openssl.org) - for gzip support in web-server and web-client (enabled by default): - zlib (http://www.zlib.net) All these libraries are included in modern Linux distributions and should be available as installable packages. Please note that you also need the developer packages for these libraries too, they package names have often suffix "dev" or "devel". On other systems you may need to download the libraries at the given URLs and compile them (see hints below). ===================================== 4. Installation on POSIX ===================================== Installation from the source distribution archive (nzbget-VERSION.tar.gz): - untar the nzbget-source via tar -zxf nzbget-VERSION.tar.gz - change into nzbget-directory via cd nzbget-VERSION - configure it via ./configure (maybe you have to tell configure, where to find some libraries. ./configure --help is your friend! also see "Configure-options" later) - in a case you don't have root access or want to install the program in your home directory use the configure parameter --prefix, e. g.: ./configure --prefix ~/usr - compile it via make - to install system wide become root via: su - install it via: make install - install configuration files into /etc via: make install-conf (you can skip this step if you intend to store configuration files in a non-standard location) Configure-options ----------------- You may run configure with additional arguments: --disable-curses - to make without curses-support. Use this option if you can not use curses/ncurses. --disable-parcheck - to make without parcheck-support. Use this option if you can not use libpar2 or libsigc++. --with-tlslib=(GnuTLS, OpenSSL) - to select which TLS/SSL library should be used for encrypted server connections. --disable-tls - to make without TLS/SSL support. Use this option if you can not neither GnuTLS nor OpenSSL. --disable-gzip - to make without gzip support. Use this option if you can not use zlib. --enable-debug - to build in debug-mode, if you want to see and log debug-messages. Optional package: par-check --------------------------- NZBGet can check and repair downloaded files for you. For this purpose it uses library par2 (libpar2), which needs sigc++ on its part. The libpar2 and libsigc++ (version 2 or later) must be installed on your system. On most linux distributions these libraries are available as packages. If you do not have these packages you can compile them yourself. Following configure-parameters may be usefull: --with-libpar2-includes --with-libpar2-libraries --with-libsigc-includes --with-libsigc-libraries The library libsigc++ must be installed first, since libpar2 requires it. If you use nzbget on a very slow computer like NAS-device, it may be good to limit the time allowed for par-repair (option "ParTimeLimit" in nzbget configuration file). This feature requires a patched version of libpar2. To compile that version download the original source code of libpar2 (version 0.2) and apply patches "libpar2-0.2-bugfixes.patch" and "libpar2-0.2-cancel.patch", provided with nzbget: cd libpar2-0.2 cp ~/nzbget/libpar2-0.2-*.patch . patch < libpar2-0.2-bugfixes.patch patch < libpar2-0.2-cancel.patch ./configure make make install If you are not able to use libpar2 or libsigc++ or do not want them you can make nzbget without support for par-check using option "--disable-parcheck": ./configure --disable-parcheck Optional package: curses ------------------------- For curses-outputmode you need ncurses or curses on your system. If you do not have one of them you can download and compile ncurses yourself. Following configure-parameters may be usefull: --with-libcurses-includes --with-libcurses-libraries If you are not able to use curses or ncurses or do not want them you can make the program without support for curses using option "--disable-curses": ./configure --disable-curses Optional package: TLS ------------------------- To enable encrypted server connections (TLS/SSL) you need to build the program with TLS/SSL support. NZBGet can use two libraries: GnuTLS or OpenSSL. Configure-script checks which library is installed and use it. If both are avialable it gives the precedence to GnuTLS. You may override that with the option --with-tlslib=(GnuTLS, OpenSSL). For example to build whith OpenSSL: ./configure --with-tlslib=OpenSSL Following configure-parameters may be usefull: --with-libtls-includes --with-libtls-libraries --with-openssl-includes --with-openssl-libraries If none of these libraries is available you can make the program without TLS/SSL support using option "--disable-tls": ./configure --disable-tls ===================================== 5. Compiling on Windows ===================================== NZBGet is developed using MS Visual C++ 2005. The project file and solution are provided. If you use MS Visual C++ 2005 Express you need to download and install Platform SDK. To compile the program with par-check-support you also need the following libraries: - libsigc++ (http://libsigc.sourceforge.net) - libpar2 (http://parchive.sourceforge.net) Download these libaries, then use patch-files provided with NZBGet to create preconfigured project files and solutions for each library. Look at http://gnuwin32.sourceforge.net/packages/patch.htm for info on how to use patch-files, if you do not familiar with this technique. To compile the program with TLS/SSL support you also need the library: - GnuTLS (http://www.gnu.org/software/gnutls) Download a precompiled version of GnuTLS from http://josefsson.org/gnutls4win and create lib-file as described there in section "Using the GnuTLS DLL from your Visual Studio program". After libsigc++ and libpar2 are compiled in static libraries (.lib), the library for GnuTLS is created and include- and libraries-paths are configured in MS Visual C++ 2005 you should be able to compile NZBGet. ===================================== 6. Configuration ===================================== NZBGet needs a configuration file. An example configuration file is provided in "nzbget.conf", which is installed into "/share/nzbget" (where depends on system configuration and configure options - typically "/usr/local", "/usr" or "/opt"). The installer adjusts the file according to your system paths. If you have performed the installation step "make install-conf" this file is already copied to "/etc" and NZBGet finds it automatically. If you install the program manually from a binary archive you have to copy the file from "/share/nzbget" to one of the locations listed below. Open the file in a text editor and modify it accodring to your needs. You need to set at least the option "MAINDIR" and one news server in configuration file. The file has comments on how to use each option. The program looks for configuration file in following standard locations (in this order): On POSIX systems: ~/.nzbget /etc/nzbget.conf /usr/etc/nzbget.conf /usr/local/etc/nzbget.conf /opt/etc/nzbget.conf On Windows: \nzbget.conf If you put the configuration file in other place, you can use command- line switch "-c " to point the program to correct location. In special cases you can run program without configuration file using switch "-n". You need to use switch "-o" to pass required configuration options via command-line. ===================================== 7. Usage ===================================== NZBGet can be used in either standalone mode which downloads a single file or as a server which is able to queue up numerous download requests. TIP for Windows users: NZBGet is controlled via various command line parameters. For easier using there is a simple shell script included in "nzbget-shell.bat". Start this script from Windows Explorer and you will be running a command shell with PATH adjusted to find NZBGet executable. Then you can type all commands without full path to nzbget.exe. Standalone mode: ---------------- nzbget Server mode: ------------ First start the nzbget-server: - in console mode: nzbget -s - or in daemon mode (POSIX only): nzbget -D - or as a service (Windowx only, firstly install the service with command "nzbget -install"): net start NZBGet To stop server use: nzbget -Q TIP for POSIX users: with included script "nzbgetd" you can use standard commands to control daemon: nzbgetd start nzbgetd stop etc. When NZBGet is started in console server mode it displays a message that it is ready to receive download requests. In daemon mode it doesn't print any messages to console since it runs in background. When the server is running it is possible to queue up downloads. This can be done either in terminal with "nzbget -A " or by uploading a nzb-file into server's monitor-directory (/nzb by default). 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) The numbers in square braces are ID's of files in queue. They can be used in edit-command. For example to move file with ID 2 to the top of queue: nzbget -E T 2 or to pause files with IDs from 10 to 20: nzbget -E P 10-20 or to delete files from queue: nzbget -E D 3 10-15 20-21 16 The edit-command has also a group-mode which affects all files from the same nzb-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: nzbget -o reloadqueue=no -o dupecheck=no -o parcheck=yes -s or: nzbget -o createlog=no -C Running client & server on seperate machines: --------------------------------------------- Since nzbget communicates via TCP/IP it's possible to have a server running on one computer and adding downloads via a client on another computer. Do this by setting the "ControlIP" option in the nzbget.conf file to point to the IP of the server (default is localhost which means client and server runs on same computer) Security warning ---------------- NZBGet communicates via unsecured socket connections. This makes it vulnerable. Although server checks the password passed by client, this password is still transmitted in unsecured way. For this reason it is highly recommended to configure your Firewall to not expose the port used by NZBGet to WAN. If you need to control server from WAN it is better to connect to server's terminal via SSH (POSIX) or remote desktop (Windows) and then run nzbget-client-commands in this terminal. Post processing scripts ----------------------- After the download of nzb-file is completed nzbget can call post-processing scripts, defined in configuration file. Example post-processing scripts are provided in directory "ppscripts". To use the scripts copy them into your local directory and set options , and . For information on writing your own post-processing scripts please visit NZBGet web site. Web-interface ------------- NZBGet has a built-in web-server providing the access to the program functions via web-interface. To activate web-interface set the option "WebDir" to the path with web-interface files. If you install using "make install-conf" as described above the option is set automatically. If you install using binary files you should check if the option is set correctly. To access web-interface from your web-browser use the server address and port defined in NZBGet configuration file in options "ControlIP" and "ControlPort". For example: http://localhost:6789/ For login credentials type username "nzbget" (predefined and not changeable) and the password from the option "ControlPassword" (default is tegbzn6789). In a case your browser forget credentials, to prevent typing them each time, there is a workaround - use URL in the form: http://localhost:6789/nzbget:password/ Please note, that in this case the password is saved in a bookmark or in browser history in plain text and is easy to find by persons having access to your computer. ===================================== 8. Authors ===================================== NZBGet is developed and maintained by Andrey Prygunkov (hugbug@users.sourceforge.net). The original project was initially created by Sven Henkel (sidddy@users.sourceforge.net) in 2004 and later developed by Bo Cordes Petersen (placebodk@users.sourceforge.net) until 2005. In 2007 the abandoned project was overtaken by Andrey Prygunkov. Since then the program has been completely rewritten. ===================================== 9. Copyright ===================================== This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The complete content of license is provided in file COPYING. Additional exemption: compiling, linking, and/or using OpenSSL is allowed. Binary distribution for Windows contains code from the following libraries: - libpar2 (http://parchive.sourceforge.net) - libsigc++ (http://libsigc.sourceforge.net) - GnuTLS (http://www.gnu.org/software/gnutls) libpar2 is distributed under GPL; libsigc++ and GnuTLS - under LGPL. ===================================== 10. Contact ===================================== If you encounter any problem, feel free to use the forum nzbget.sourceforge.net/forum or contact me at hugbug@users.sourceforge.net nzbget-12.0+dfsg/RemoteClient.cpp000066400000000000000000001153431226450633000167520ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2005 Bo Cordes Petersen * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 911 $ * $Date: 2013-11-24 20:29:52 +0100 (Sun, 24 Nov 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #ifdef WIN32 #include #else #include #include #include #endif #include #include "nzbget.h" #include "RemoteClient.h" #include "DownloadInfo.h" #include "Options.h" #include "Log.h" #include "Util.h" extern Options* g_pOptions; RemoteClient::RemoteClient() { m_pConnection = NULL; m_bVerbose = true; /* printf("sizeof(SNZBRequestBase)=%i\n", sizeof(SNZBRequestBase)); printf("sizeof(SNZBDownloadRequest)=%i\n", sizeof(SNZBDownloadRequest)); printf("sizeof(SNZBListRequest)=%i\n", sizeof(SNZBListRequest)); printf("sizeof(SNZBListResponse)=%i\n", sizeof(SNZBListResponse)); printf("sizeof(SNZBListResponseFileEntry)=%i\n", sizeof(SNZBListResponseFileEntry)); printf("sizeof(SNZBLogRequest)=%i\n", sizeof(SNZBLogRequest)); printf("sizeof(SNZBLogResponse)=%i\n", sizeof(SNZBLogResponse)); printf("sizeof(SNZBLogResponseEntry)=%i\n", sizeof(SNZBLogResponseEntry)); printf("sizeof(SNZBPauseUnpauseRequest)=%i\n", sizeof(SNZBPauseUnpauseRequest)); printf("sizeof(SNZBSetDownloadRateRequest)=%i\n", sizeof(SNZBSetDownloadRateRequest)); printf("sizeof(SNZBEditQueueRequest)=%i\n", sizeof(SNZBEditQueueRequest)); printf("sizeof(SNZBDumpDebugRequest)=%i\n", sizeof(SNZBDumpDebugRequest)); */ } RemoteClient::~RemoteClient() { delete m_pConnection; } void RemoteClient::printf(const char * msg,...) { if (m_bVerbose) { va_list ap; va_start(ap, msg); ::vprintf(msg, ap); va_end(ap); } } void RemoteClient::perror(const char * msg) { if (m_bVerbose) { ::perror(msg); } } bool RemoteClient::InitConnection() { // Create a connection to the server const char *szControlIP = g_pOptions->GetControlIP(); if (!strcmp(szControlIP, "0.0.0.0")) { szControlIP = "127.0.0.1"; } m_pConnection = new Connection(szControlIP, g_pOptions->GetControlPort(), false); bool OK = m_pConnection->Connect(); if (!OK) { printf("Unable to send request to nzbserver at %s (port %i)\n", szControlIP, g_pOptions->GetControlPort()); } return OK; } void RemoteClient::InitMessageBase(SNZBRequestBase* pMessageBase, int iRequest, int iSize) { pMessageBase->m_iSignature = htonl(NZBMESSAGE_SIGNATURE); pMessageBase->m_iType = htonl(iRequest); pMessageBase->m_iStructSize = htonl(iSize); strncpy(pMessageBase->m_szUsername, g_pOptions->GetControlUsername(), NZBREQUESTPASSWORDSIZE - 1); pMessageBase->m_szUsername[NZBREQUESTPASSWORDSIZE - 1] = '\0'; strncpy(pMessageBase->m_szPassword, g_pOptions->GetControlPassword(), NZBREQUESTPASSWORDSIZE - 1); pMessageBase->m_szPassword[NZBREQUESTPASSWORDSIZE - 1] = '\0'; } bool RemoteClient::ReceiveBoolResponse() { printf("Request sent\n"); // all bool-responses have the same format of structure, we use SNZBDownloadResponse here SNZBDownloadResponse BoolResponse; memset(&BoolResponse, 0, sizeof(BoolResponse)); bool bRead = m_pConnection->Recv((char*)&BoolResponse, sizeof(BoolResponse)); if (!bRead || (int)ntohl(BoolResponse.m_MessageBase.m_iSignature) != (int)NZBMESSAGE_SIGNATURE || ntohl(BoolResponse.m_MessageBase.m_iStructSize) != sizeof(BoolResponse)) { printf("No response or invalid response (timeout, not nzbget-server or wrong nzbget-server version)\n"); return false; } int iTextLen = ntohl(BoolResponse.m_iTrailingDataLength); char* buf = (char*)malloc(iTextLen); bRead = m_pConnection->Recv(buf, iTextLen); if (!bRead) { printf("No response or invalid response (timeout, not nzbget-server or wrong nzbget-server version)\n"); free(buf); return false; } printf("server returned: %s\n", buf); free(buf); return ntohl(BoolResponse.m_bSuccess); } /* * Sends a message to the running nzbget process. */ bool RemoteClient::RequestServerDownload(const char* szFilename, const char* szCategory, bool bAddFirst, bool bAddPaused, int iPriority) { // Read the file into the buffer char* szBuffer = NULL; int iLength = 0; if (!Util::LoadFileIntoBuffer(szFilename, &szBuffer, &iLength)) { printf("Could not load file %s\n", szFilename); return false; } bool OK = InitConnection(); if (OK) { SNZBDownloadRequest DownloadRequest; InitMessageBase(&DownloadRequest.m_MessageBase, eRemoteRequestDownload, sizeof(DownloadRequest)); DownloadRequest.m_bAddFirst = htonl(bAddFirst); DownloadRequest.m_bAddPaused = htonl(bAddPaused); DownloadRequest.m_iPriority = htonl(iPriority); DownloadRequest.m_iTrailingDataLength = htonl(iLength - 1); strncpy(DownloadRequest.m_szFilename, szFilename, NZBREQUESTFILENAMESIZE - 1); DownloadRequest.m_szFilename[NZBREQUESTFILENAMESIZE-1] = '\0'; DownloadRequest.m_szCategory[0] = '\0'; if (szCategory) { strncpy(DownloadRequest.m_szCategory, szCategory, NZBREQUESTFILENAMESIZE - 1); } DownloadRequest.m_szCategory[NZBREQUESTFILENAMESIZE-1] = '\0'; if (!m_pConnection->Send((char*)(&DownloadRequest), sizeof(DownloadRequest))) { perror("m_pConnection->Send"); OK = false; } else { m_pConnection->Send(szBuffer, iLength); OK = ReceiveBoolResponse(); m_pConnection->Disconnect(); } } // Cleanup free(szBuffer); return OK; } void RemoteClient::BuildFileList(SNZBListResponse* pListResponse, const char* pTrailingData, DownloadQueue* pDownloadQueue) { if (ntohl(pListResponse->m_iTrailingDataLength) > 0) { const char* pBufPtr = pTrailingData; // read nzb entries for (unsigned int i = 0; i < ntohl(pListResponse->m_iNrTrailingNZBEntries); i++) { SNZBListResponseNZBEntry* pListAnswer = (SNZBListResponseNZBEntry*) pBufPtr; const char* szFileName = pBufPtr + sizeof(SNZBListResponseNZBEntry); const char* szName = pBufPtr + sizeof(SNZBListResponseNZBEntry) + ntohl(pListAnswer->m_iFilenameLen); const char* szDestDir = pBufPtr + sizeof(SNZBListResponseNZBEntry) + ntohl(pListAnswer->m_iFilenameLen) + ntohl(pListAnswer->m_iNameLen); const char* szCategory = pBufPtr + sizeof(SNZBListResponseNZBEntry) + ntohl(pListAnswer->m_iFilenameLen) + ntohl(pListAnswer->m_iNameLen) + ntohl(pListAnswer->m_iDestDirLen); const char* m_szQueuedFilename = pBufPtr + sizeof(SNZBListResponseNZBEntry) + ntohl(pListAnswer->m_iFilenameLen) + ntohl(pListAnswer->m_iNameLen) + ntohl(pListAnswer->m_iDestDirLen) + ntohl(pListAnswer->m_iCategoryLen); MatchedNZBInfo* pNZBInfo = new MatchedNZBInfo(); pNZBInfo->SetSize(Util::JoinInt64(ntohl(pListAnswer->m_iSizeHi), ntohl(pListAnswer->m_iSizeLo))); pNZBInfo->SetFilename(szFileName); pNZBInfo->SetName(szName); pNZBInfo->SetDestDir(szDestDir); pNZBInfo->SetCategory(szCategory); pNZBInfo->SetQueuedFilename(m_szQueuedFilename); pNZBInfo->m_bMatch = ntohl(pListAnswer->m_bMatch); pNZBInfo->Retain(); pDownloadQueue->GetNZBInfoList()->Add(pNZBInfo); pBufPtr += sizeof(SNZBListResponseNZBEntry) + ntohl(pListAnswer->m_iFilenameLen) + ntohl(pListAnswer->m_iNameLen) + ntohl(pListAnswer->m_iDestDirLen) + ntohl(pListAnswer->m_iCategoryLen) + ntohl(pListAnswer->m_iQueuedFilenameLen); } //read ppp entries for (unsigned int i = 0; i < ntohl(pListResponse->m_iNrTrailingPPPEntries); i++) { SNZBListResponsePPPEntry* pListAnswer = (SNZBListResponsePPPEntry*) pBufPtr; const char* szName = pBufPtr + sizeof(SNZBListResponsePPPEntry); const char* szValue = pBufPtr + sizeof(SNZBListResponsePPPEntry) + ntohl(pListAnswer->m_iNameLen); NZBInfo* pNZBInfo = pDownloadQueue->GetNZBInfoList()->at(ntohl(pListAnswer->m_iNZBIndex) - 1); pNZBInfo->GetParameters()->SetParameter(szName, szValue); pBufPtr += sizeof(SNZBListResponsePPPEntry) + ntohl(pListAnswer->m_iNameLen) + ntohl(pListAnswer->m_iValueLen); } //read file entries for (unsigned int i = 0; i < ntohl(pListResponse->m_iNrTrailingFileEntries); i++) { SNZBListResponseFileEntry* pListAnswer = (SNZBListResponseFileEntry*) pBufPtr; const char* szSubject = pBufPtr + sizeof(SNZBListResponseFileEntry); const char* szFileName = pBufPtr + sizeof(SNZBListResponseFileEntry) + ntohl(pListAnswer->m_iSubjectLen); MatchedFileInfo* pFileInfo = new MatchedFileInfo(); pFileInfo->SetID(ntohl(pListAnswer->m_iID)); pFileInfo->SetSize(Util::JoinInt64(ntohl(pListAnswer->m_iFileSizeHi), ntohl(pListAnswer->m_iFileSizeLo))); pFileInfo->SetRemainingSize(Util::JoinInt64(ntohl(pListAnswer->m_iRemainingSizeHi), ntohl(pListAnswer->m_iRemainingSizeLo))); pFileInfo->SetPaused(ntohl(pListAnswer->m_bPaused)); pFileInfo->SetSubject(szSubject); pFileInfo->SetFilename(szFileName); pFileInfo->SetFilenameConfirmed(ntohl(pListAnswer->m_bFilenameConfirmed)); pFileInfo->SetActiveDownloads(ntohl(pListAnswer->m_iActiveDownloads)); pFileInfo->SetPriority(ntohl(pListAnswer->m_iPriority)); pFileInfo->m_bMatch = ntohl(pListAnswer->m_bMatch); NZBInfo* pNZBInfo = pDownloadQueue->GetNZBInfoList()->at(ntohl(pListAnswer->m_iNZBIndex) - 1); pFileInfo->SetNZBInfo(pNZBInfo); pDownloadQueue->GetFileQueue()->push_back(pFileInfo); pBufPtr += sizeof(SNZBListResponseFileEntry) + ntohl(pListAnswer->m_iSubjectLen) + ntohl(pListAnswer->m_iFilenameLen); } } pDownloadQueue->GetNZBInfoList()->ReleaseAll(); } bool RemoteClient::RequestServerList(bool bFiles, bool bGroups, const char* szPattern) { if (!InitConnection()) return false; SNZBListRequest ListRequest; InitMessageBase(&ListRequest.m_MessageBase, eRemoteRequestList, sizeof(ListRequest)); ListRequest.m_bFileList = htonl(true); ListRequest.m_bServerState = htonl(true); ListRequest.m_iMatchMode = htonl(szPattern ? eRemoteMatchModeRegEx : eRemoteMatchModeID); ListRequest.m_bMatchGroup = htonl(bGroups); if (szPattern) { strncpy(ListRequest.m_szPattern, szPattern, NZBREQUESTFILENAMESIZE - 1); ListRequest.m_szPattern[NZBREQUESTFILENAMESIZE-1] = '\0'; } if (!m_pConnection->Send((char*)(&ListRequest), sizeof(ListRequest))) { perror("m_pConnection->Send"); return false; } printf("Request sent\n"); // Now listen for the returned list SNZBListResponse ListResponse; bool bRead = m_pConnection->Recv((char*) &ListResponse, sizeof(ListResponse)); if (!bRead || (int)ntohl(ListResponse.m_MessageBase.m_iSignature) != (int)NZBMESSAGE_SIGNATURE || ntohl(ListResponse.m_MessageBase.m_iStructSize) != sizeof(ListResponse)) { printf("No response or invalid response (timeout, not nzbget-server or wrong nzbget-server version)\n"); return false; } char* pBuf = NULL; if (ntohl(ListResponse.m_iTrailingDataLength) > 0) { pBuf = (char*)malloc(ntohl(ListResponse.m_iTrailingDataLength)); if (!m_pConnection->Recv(pBuf, ntohl(ListResponse.m_iTrailingDataLength))) { free(pBuf); return false; } } m_pConnection->Disconnect(); if (szPattern && !ListResponse.m_bRegExValid) { printf("Error in regular expression\n"); free(pBuf); return false; } if (bFiles) { if (ntohl(ListResponse.m_iTrailingDataLength) == 0) { printf("Server has no files queued for download\n"); } else { printf("Queue List\n"); printf("-----------------------------------\n"); DownloadQueue cRemoteQueue; BuildFileList(&ListResponse, pBuf, &cRemoteQueue); long long lRemaining = 0; long long lPaused = 0; int iMatches = 0; for (FileQueue::iterator it = cRemoteQueue.GetFileQueue()->begin(); it != cRemoteQueue.GetFileQueue()->end(); it++) { FileInfo* pFileInfo = *it; char szPriority[100]; szPriority[0] = '\0'; if (pFileInfo->GetPriority() != 0) { sprintf(szPriority, "[%+i] ", pFileInfo->GetPriority()); } char szCompleted[100]; szCompleted[0] = '\0'; if (pFileInfo->GetRemainingSize() < pFileInfo->GetSize()) { sprintf(szCompleted, ", %i%s", (int)(100 - Util::Int64ToFloat(pFileInfo->GetRemainingSize()) * 100.0 / Util::Int64ToFloat(pFileInfo->GetSize())), "%"); } char szThreads[100]; szThreads[0] = '\0'; if (pFileInfo->GetActiveDownloads() > 0) { sprintf(szThreads, ", %i thread%s", pFileInfo->GetActiveDownloads(), (pFileInfo->GetActiveDownloads() > 1 ? "s" : "")); } char szStatus[100]; if (pFileInfo->GetPaused()) { sprintf(szStatus, " (paused)"); lPaused += pFileInfo->GetRemainingSize(); } else { szStatus[0] = '\0'; lRemaining += pFileInfo->GetRemainingSize(); } if (!szPattern || ((MatchedFileInfo*)pFileInfo)->m_bMatch) { printf("[%i] %s%s/%s (%.2f MB%s%s)%s\n", pFileInfo->GetID(), szPriority, pFileInfo->GetNZBInfo()->GetName(), pFileInfo->GetFilename(), (float)(Util::Int64ToFloat(pFileInfo->GetSize()) / 1024.0 / 1024.0), szCompleted, szThreads, szStatus); iMatches++; } delete pFileInfo; } if (iMatches == 0) { printf("No matches founds\n"); } printf("-----------------------------------\n"); printf("Files: %i\n", cRemoteQueue.GetFileQueue()->size()); if (szPattern) { printf("Matches: %i\n", iMatches); } if (lPaused > 0) { printf("Remaining size: %.2f MB (+%.2f MB paused)\n", (float)(Util::Int64ToFloat(lRemaining) / 1024.0 / 1024.0), (float)(Util::Int64ToFloat(lPaused) / 1024.0 / 1024.0)); } else { printf("Remaining size: %.2f MB\n", (float)(Util::Int64ToFloat(lRemaining) / 1024.0 / 1024.0)); } } } if (bGroups) { if (ntohl(ListResponse.m_iTrailingDataLength) == 0) { printf("Server has no files queued for download\n"); } else { printf("Queue List\n"); printf("-----------------------------------\n"); DownloadQueue cRemoteQueue; BuildFileList(&ListResponse, pBuf, &cRemoteQueue); GroupQueue cGroupQueue; cRemoteQueue.BuildGroups(&cGroupQueue); long long lRemaining = 0; long long lPaused = 0; int iMatches = 0; for (GroupQueue::iterator it = cGroupQueue.begin(); it != cGroupQueue.end(); it++) { GroupInfo* pGroupInfo = *it; long long lUnpausedRemainingSize = pGroupInfo->GetRemainingSize() - pGroupInfo->GetPausedSize(); lRemaining += lUnpausedRemainingSize; char szRemaining[20]; Util::FormatFileSize(szRemaining, sizeof(szRemaining), lUnpausedRemainingSize); char szPriority[100]; szPriority[0] = '\0'; if (pGroupInfo->GetMinPriority() != 0 || pGroupInfo->GetMaxPriority() != 0) { if (pGroupInfo->GetMinPriority() == pGroupInfo->GetMaxPriority()) { sprintf(szPriority, "[%+i] ", pGroupInfo->GetMinPriority()); } else { sprintf(szPriority, "[%+i..%+i] ", pGroupInfo->GetMinPriority(), pGroupInfo->GetMaxPriority()); } } char szPaused[20]; szPaused[0] = '\0'; if (pGroupInfo->GetPausedSize() > 0) { char szPausedSize[20]; Util::FormatFileSize(szPausedSize, sizeof(szPausedSize), pGroupInfo->GetPausedSize()); sprintf(szPaused, " + %s paused", szPausedSize); lPaused += pGroupInfo->GetPausedSize(); } char szCategory[1024]; szCategory[0] = '\0'; if (pGroupInfo->GetNZBInfo()->GetCategory() && strlen(pGroupInfo->GetNZBInfo()->GetCategory()) > 0) { sprintf(szCategory, " (%s)", pGroupInfo->GetNZBInfo()->GetCategory()); } char szThreads[100]; szThreads[0] = '\0'; if (pGroupInfo->GetActiveDownloads() > 0) { sprintf(szThreads, ", %i thread%s", pGroupInfo->GetActiveDownloads(), (pGroupInfo->GetActiveDownloads() > 1 ? "s" : "")); } char szParameters[1024]; szParameters[0] = '\0'; for (NZBParameterList::iterator it = pGroupInfo->GetNZBInfo()->GetParameters()->begin(); it != pGroupInfo->GetNZBInfo()->GetParameters()->end(); it++) { if (szParameters[0] == '\0') { strncat(szParameters, " (", sizeof(szParameters) - strlen(szParameters) - 1); } else { strncat(szParameters, ", ", sizeof(szParameters) - strlen(szParameters) - 1); } NZBParameter* pNZBParameter = *it; strncat(szParameters, pNZBParameter->GetName(), sizeof(szParameters) - strlen(szParameters) - 1); strncat(szParameters, "=", sizeof(szParameters) - strlen(szParameters) - 1); strncat(szParameters, pNZBParameter->GetValue(), sizeof(szParameters) - strlen(szParameters) - 1); } if (szParameters[0] != '\0') { strncat(szParameters, ")", sizeof(szParameters) - strlen(szParameters) - 1); } if (!szPattern || ((MatchedNZBInfo*)pGroupInfo->GetNZBInfo())->m_bMatch) { printf("[%i-%i] %s%s (%i file%s, %s%s%s)%s%s\n", pGroupInfo->GetFirstID(), pGroupInfo->GetLastID(), szPriority, pGroupInfo->GetNZBInfo()->GetName(), pGroupInfo->GetRemainingFileCount(), pGroupInfo->GetRemainingFileCount() > 1 ? "s" : "", szRemaining, szPaused, szThreads, szCategory, szParameters); iMatches++; } } for (FileQueue::iterator it = cRemoteQueue.GetFileQueue()->begin(); it != cRemoteQueue.GetFileQueue()->end(); it++) { delete *it; } if (iMatches == 0) { printf("No matches founds\n"); } printf("-----------------------------------\n"); printf("Groups: %i\n", cGroupQueue.size()); if (szPattern) { printf("Matches: %i\n", iMatches); } printf("Files: %i\n", cRemoteQueue.GetFileQueue()->size()); if (lPaused > 0) { printf("Remaining size: %.2f MB (+%.2f MB paused)\n", (float)(Util::Int64ToFloat(lRemaining) / 1024.0 / 1024.0), (float)(Util::Int64ToFloat(lPaused) / 1024.0 / 1024.0)); } else { printf("Remaining size: %.2f MB\n", (float)(Util::Int64ToFloat(lRemaining) / 1024.0 / 1024.0)); } } } free(pBuf); long long lRemaining = Util::JoinInt64(ntohl(ListResponse.m_iRemainingSizeHi), ntohl(ListResponse.m_iRemainingSizeLo)); if (!bFiles && !bGroups) { printf("Remaining size: %.2f MB\n", (float)(Util::Int64ToFloat(lRemaining) / 1024.0 / 1024.0)); } if (ntohl(ListResponse.m_iDownloadRate) > 0 && !ntohl(ListResponse.m_bDownloadPaused) && !ntohl(ListResponse.m_bDownload2Paused) && !ntohl(ListResponse.m_bDownloadStandBy)) { long long remain_sec = (long long)(lRemaining / ntohl(ListResponse.m_iDownloadRate)); int h = (int)(remain_sec / 3600); int m = (int)((remain_sec % 3600) / 60); int s = (int)(remain_sec % 60); printf("Remaining time: %.2d:%.2d:%.2d\n", h, m, s); } printf("Current download rate: %.1f KB/s\n", (float)(ntohl(ListResponse.m_iDownloadRate) / 1024.0)); long long iAllBytes = Util::JoinInt64(ntohl(ListResponse.m_iDownloadedBytesHi), ntohl(ListResponse.m_iDownloadedBytesLo)); float fAverageSpeed = Util::Int64ToFloat(ntohl(ListResponse.m_iDownloadTimeSec) > 0 ? iAllBytes / ntohl(ListResponse.m_iDownloadTimeSec) : 0); printf("Session download rate: %.1f KB/s\n", (float)(fAverageSpeed / 1024.0)); if (ntohl(ListResponse.m_iDownloadLimit) > 0) { printf("Speed limit: %.1f KB/s\n", (float)(ntohl(ListResponse.m_iDownloadLimit) / 1024.0)); } int sec = ntohl(ListResponse.m_iUpTimeSec); int h = sec / 3600; int m = (sec % 3600) / 60; int s = sec % 60; printf("Up time: %.2d:%.2d:%.2d\n", h, m, s); sec = ntohl(ListResponse.m_iDownloadTimeSec); h = sec / 3600; m = (sec % 3600) / 60; s = sec % 60; printf("Download time: %.2d:%.2d:%.2d\n", h, m, s); printf("Downloaded: %.2f MB\n", (float)(Util::Int64ToFloat(iAllBytes) / 1024.0 / 1024.0)); printf("Threads running: %i\n", ntohl(ListResponse.m_iThreadCount)); if (ntohl(ListResponse.m_iPostJobCount) > 0) { printf("Post-jobs: %i\n", (int)ntohl(ListResponse.m_iPostJobCount)); } if (ntohl(ListResponse.m_bScanPaused)) { printf("Scan state: Paused\n"); } char szServerState[50]; if (ntohl(ListResponse.m_bDownloadPaused) || ntohl(ListResponse.m_bDownload2Paused)) { snprintf(szServerState, sizeof(szServerState), "%s%s", ntohl(ListResponse.m_bDownloadStandBy) ? "Paused" : "Pausing", ntohl(ListResponse.m_bDownloadPaused) && ntohl(ListResponse.m_bDownload2Paused) ? " (+2)" : ntohl(ListResponse.m_bDownload2Paused) ? " (2)" : ""); } else { snprintf(szServerState, sizeof(szServerState), "%s", ntohl(ListResponse.m_bDownloadStandBy) ? "" : "Downloading"); } if (ntohl(ListResponse.m_iPostJobCount) > 0 || ntohl(ListResponse.m_bPostPaused)) { strncat(szServerState, strlen(szServerState) > 0 ? ", Post-Processing" : "Post-Processing", sizeof(szServerState) - strlen(szServerState) - 1); if (ntohl(ListResponse.m_bPostPaused)) { strncat(szServerState, " paused", sizeof(szServerState) - strlen(szServerState) - 1); } } if (strlen(szServerState) == 0) { strncpy(szServerState, "Stand-By", sizeof(szServerState)); } printf("Server state: %s\n", szServerState); return true; } bool RemoteClient::RequestServerLog(int iLines) { if (!InitConnection()) return false; SNZBLogRequest LogRequest; InitMessageBase(&LogRequest.m_MessageBase, eRemoteRequestLog, sizeof(LogRequest)); LogRequest.m_iLines = htonl(iLines); LogRequest.m_iIDFrom = 0; if (!m_pConnection->Send((char*)(&LogRequest), sizeof(LogRequest))) { perror("m_pConnection->Send"); return false; } printf("Request sent\n"); // Now listen for the returned log SNZBLogResponse LogResponse; bool bRead = m_pConnection->Recv((char*) &LogResponse, sizeof(LogResponse)); if (!bRead || (int)ntohl(LogResponse.m_MessageBase.m_iSignature) != (int)NZBMESSAGE_SIGNATURE || ntohl(LogResponse.m_MessageBase.m_iStructSize) != sizeof(LogResponse)) { printf("No response or invalid response (timeout, not nzbget-server or wrong nzbget-server version)\n"); return false; } char* pBuf = NULL; if (ntohl(LogResponse.m_iTrailingDataLength) > 0) { pBuf = (char*)malloc(ntohl(LogResponse.m_iTrailingDataLength)); if (!m_pConnection->Recv(pBuf, ntohl(LogResponse.m_iTrailingDataLength))) { free(pBuf); return false; } } m_pConnection->Disconnect(); if (LogResponse.m_iTrailingDataLength == 0) { printf("Log is empty\n"); } else { printf("Log (last %i entries)\n", ntohl(LogResponse.m_iNrTrailingEntries)); printf("-----------------------------------\n"); char* pBufPtr = (char*)pBuf; for (unsigned int i = 0; i < ntohl(LogResponse.m_iNrTrailingEntries); i++) { SNZBLogResponseEntry* pLogAnswer = (SNZBLogResponseEntry*) pBufPtr; char* szText = pBufPtr + sizeof(SNZBLogResponseEntry); switch (ntohl(pLogAnswer->m_iKind)) { case Message::mkDebug: printf("[DEBUG] %s\n", szText); break; case Message::mkError: printf("[ERROR] %s\n", szText); break; case Message::mkWarning: printf("[WARNING] %s\n", szText); break; case Message::mkInfo: printf("[INFO] %s\n", szText); break; case Message::mkDetail: printf("[DETAIL] %s\n", szText); break; } pBufPtr += sizeof(SNZBLogResponseEntry) + ntohl(pLogAnswer->m_iTextLen); } printf("-----------------------------------\n"); free(pBuf); } return true; } bool RemoteClient::RequestServerPauseUnpause(bool bPause, eRemotePauseUnpauseAction iAction) { if (!InitConnection()) return false; SNZBPauseUnpauseRequest PauseUnpauseRequest; InitMessageBase(&PauseUnpauseRequest.m_MessageBase, eRemoteRequestPauseUnpause, sizeof(PauseUnpauseRequest)); PauseUnpauseRequest.m_bPause = htonl(bPause); PauseUnpauseRequest.m_iAction = htonl(iAction); if (!m_pConnection->Send((char*)(&PauseUnpauseRequest), sizeof(PauseUnpauseRequest))) { perror("m_pConnection->Send"); m_pConnection->Disconnect(); return false; } bool OK = ReceiveBoolResponse(); m_pConnection->Disconnect(); return OK; } bool RemoteClient::RequestServerSetDownloadRate(int iRate) { if (!InitConnection()) return false; SNZBSetDownloadRateRequest SetDownloadRateRequest; InitMessageBase(&SetDownloadRateRequest.m_MessageBase, eRemoteRequestSetDownloadRate, sizeof(SetDownloadRateRequest)); SetDownloadRateRequest.m_iDownloadRate = htonl(iRate); if (!m_pConnection->Send((char*)(&SetDownloadRateRequest), sizeof(SetDownloadRateRequest))) { perror("m_pConnection->Send"); m_pConnection->Disconnect(); return false; } bool OK = ReceiveBoolResponse(); m_pConnection->Disconnect(); return OK; } bool RemoteClient::RequestServerDumpDebug() { if (!InitConnection()) return false; SNZBDumpDebugRequest DumpDebugInfo; InitMessageBase(&DumpDebugInfo.m_MessageBase, eRemoteRequestDumpDebug, sizeof(DumpDebugInfo)); if (!m_pConnection->Send((char*)(&DumpDebugInfo), sizeof(DumpDebugInfo))) { perror("m_pConnection->Send"); m_pConnection->Disconnect(); return false; } bool OK = ReceiveBoolResponse(); m_pConnection->Disconnect(); return OK; } bool RemoteClient::RequestServerEditQueue(eRemoteEditAction iAction, int iOffset, const char* szText, int* pIDList, int iIDCount, NameList* pNameList, eRemoteMatchMode iMatchMode, bool bSmartOrder) { if ((iIDCount <= 0 || pIDList == NULL) && (pNameList == NULL || pNameList->size() == 0)) { printf("File(s) not specified\n"); return false; } if (!InitConnection()) return false; int iIDLength = sizeof(int32_t) * iIDCount; int iNameCount = 0; int iNameLength = 0; if (pNameList && pNameList->size() > 0) { for (NameList::iterator it = pNameList->begin(); it != pNameList->end(); it++) { const char *szName = *it; iNameLength += strlen(szName) + 1; iNameCount++; } // align size to 4-bytes, needed by ARM-processor (and may be others) iNameLength += iNameLength % 4 > 0 ? 4 - iNameLength % 4 : 0; } int iTextLen = szText ? strlen(szText) + 1 : 0; // align size to 4-bytes, needed by ARM-processor (and may be others) iTextLen += iTextLen % 4 > 0 ? 4 - iTextLen % 4 : 0; int iLength = iTextLen + iIDLength + iNameLength; SNZBEditQueueRequest EditQueueRequest; InitMessageBase(&EditQueueRequest.m_MessageBase, eRemoteRequestEditQueue, sizeof(EditQueueRequest)); EditQueueRequest.m_iAction = htonl(iAction); EditQueueRequest.m_iMatchMode = htonl(iMatchMode); EditQueueRequest.m_iOffset = htonl((int)iOffset); EditQueueRequest.m_bSmartOrder = htonl(bSmartOrder); EditQueueRequest.m_iTextLen = htonl(iTextLen); EditQueueRequest.m_iNrTrailingIDEntries = htonl(iIDCount); EditQueueRequest.m_iNrTrailingNameEntries = htonl(iNameCount); EditQueueRequest.m_iTrailingNameEntriesLen = htonl(iNameLength); EditQueueRequest.m_iTrailingDataLength = htonl(iLength); char* pTrailingData = (char*)malloc(iLength); if (iTextLen > 0) { strcpy(pTrailingData, szText); } int32_t* pIDs = (int32_t*)(pTrailingData + iTextLen); for (int i = 0; i < iIDCount; i++) { pIDs[i] = htonl(pIDList[i]); } if (iNameCount > 0) { char *pNames = pTrailingData + iTextLen + iIDLength; for (NameList::iterator it = pNameList->begin(); it != pNameList->end(); it++) { const char *szName = *it; int iLen = strlen(szName); strncpy(pNames, szName, iLen + 1); pNames += iLen + 1; } } bool OK = false; if (!m_pConnection->Send((char*)(&EditQueueRequest), sizeof(EditQueueRequest))) { perror("m_pConnection->Send"); } else { m_pConnection->Send(pTrailingData, iLength); OK = ReceiveBoolResponse(); m_pConnection->Disconnect(); } free(pTrailingData); m_pConnection->Disconnect(); return OK; } bool RemoteClient::RequestServerShutdown() { if (!InitConnection()) return false; SNZBShutdownRequest ShutdownRequest; InitMessageBase(&ShutdownRequest.m_MessageBase, eRemoteRequestShutdown, sizeof(ShutdownRequest)); bool OK = m_pConnection->Send((char*)(&ShutdownRequest), sizeof(ShutdownRequest)); if (OK) { OK = ReceiveBoolResponse(); } else { perror("m_pConnection->Send"); } m_pConnection->Disconnect(); return OK; } bool RemoteClient::RequestServerReload() { if (!InitConnection()) return false; SNZBReloadRequest ReloadRequest; InitMessageBase(&ReloadRequest.m_MessageBase, eRemoteRequestReload, sizeof(ReloadRequest)); bool OK = m_pConnection->Send((char*)(&ReloadRequest), sizeof(ReloadRequest)); if (OK) { OK = ReceiveBoolResponse(); } else { perror("m_pConnection->Send"); } m_pConnection->Disconnect(); return OK; } bool RemoteClient::RequestServerVersion() { if (!InitConnection()) return false; SNZBVersionRequest VersionRequest; InitMessageBase(&VersionRequest.m_MessageBase, eRemoteRequestVersion, sizeof(VersionRequest)); bool OK = m_pConnection->Send((char*)(&VersionRequest), sizeof(VersionRequest)); if (OK) { OK = ReceiveBoolResponse(); } else { perror("m_pConnection->Send"); } m_pConnection->Disconnect(); return OK; } bool RemoteClient::RequestPostQueue() { if (!InitConnection()) return false; SNZBPostQueueRequest PostQueueRequest; InitMessageBase(&PostQueueRequest.m_MessageBase, eRemoteRequestPostQueue, sizeof(PostQueueRequest)); if (!m_pConnection->Send((char*)(&PostQueueRequest), sizeof(PostQueueRequest))) { perror("m_pConnection->Send"); return false; } printf("Request sent\n"); // Now listen for the returned list SNZBPostQueueResponse PostQueueResponse; bool bRead = m_pConnection->Recv((char*) &PostQueueResponse, sizeof(PostQueueResponse)); if (!bRead || (int)ntohl(PostQueueResponse.m_MessageBase.m_iSignature) != (int)NZBMESSAGE_SIGNATURE || ntohl(PostQueueResponse.m_MessageBase.m_iStructSize) != sizeof(PostQueueResponse)) { printf("No response or invalid response (timeout, not nzbget-server or wrong nzbget-server version)\n"); return false; } char* pBuf = NULL; if (ntohl(PostQueueResponse.m_iTrailingDataLength) > 0) { pBuf = (char*)malloc(ntohl(PostQueueResponse.m_iTrailingDataLength)); if (!m_pConnection->Recv(pBuf, ntohl(PostQueueResponse.m_iTrailingDataLength))) { free(pBuf); return false; } } m_pConnection->Disconnect(); if (ntohl(PostQueueResponse.m_iTrailingDataLength) == 0) { printf("Server has no jobs queued for post-processing\n"); } else { printf("Post-Processing List\n"); printf("-----------------------------------\n"); char* pBufPtr = (char*)pBuf; for (unsigned int i = 0; i < ntohl(PostQueueResponse.m_iNrTrailingEntries); i++) { SNZBPostQueueResponseEntry* pPostQueueAnswer = (SNZBPostQueueResponseEntry*) pBufPtr; int iStageProgress = ntohl(pPostQueueAnswer->m_iStageProgress); char szCompleted[100]; szCompleted[0] = '\0'; if (iStageProgress > 0 && (int)ntohl(pPostQueueAnswer->m_iStage) != (int)PostInfo::ptExecutingScript) { sprintf(szCompleted, ", %i%s", (int)(iStageProgress / 10), "%"); } const char* szPostStageName[] = { "", ", Loading Pars", ", Verifying source files", ", Repairing", ", Verifying repaired files", ", Unpacking", ", Executing postprocess-script", "" }; char* szInfoName = pBufPtr + sizeof(SNZBPostQueueResponseEntry) + ntohl(pPostQueueAnswer->m_iNZBFilenameLen); printf("[%i] %s%s%s\n", ntohl(pPostQueueAnswer->m_iID), szInfoName, szPostStageName[ntohl(pPostQueueAnswer->m_iStage)], szCompleted); pBufPtr += sizeof(SNZBPostQueueResponseEntry) + ntohl(pPostQueueAnswer->m_iNZBFilenameLen) + ntohl(pPostQueueAnswer->m_iInfoNameLen) + ntohl(pPostQueueAnswer->m_iDestDirLen) + ntohl(pPostQueueAnswer->m_iProgressLabelLen); } free(pBuf); printf("-----------------------------------\n"); } return true; } bool RemoteClient::RequestWriteLog(int iKind, const char* szText) { if (!InitConnection()) return false; SNZBWriteLogRequest WriteLogRequest; InitMessageBase(&WriteLogRequest.m_MessageBase, eRemoteRequestWriteLog, sizeof(WriteLogRequest)); WriteLogRequest.m_iKind = htonl(iKind); int iLength = strlen(szText) + 1; WriteLogRequest.m_iTrailingDataLength = htonl(iLength); if (!m_pConnection->Send((char*)(&WriteLogRequest), sizeof(WriteLogRequest))) { perror("m_pConnection->Send"); return false; } m_pConnection->Send(szText, iLength); bool OK = ReceiveBoolResponse(); m_pConnection->Disconnect(); return OK; } bool RemoteClient::RequestScan(bool bSyncMode) { if (!InitConnection()) return false; SNZBScanRequest ScanRequest; InitMessageBase(&ScanRequest.m_MessageBase, eRemoteRequestScan, sizeof(ScanRequest)); ScanRequest.m_bSyncMode = htonl(bSyncMode); bool OK = m_pConnection->Send((char*)(&ScanRequest), sizeof(ScanRequest)); if (OK) { OK = ReceiveBoolResponse(); } else { perror("m_pConnection->Send"); } m_pConnection->Disconnect(); return OK; } bool RemoteClient::RequestHistory() { if (!InitConnection()) return false; SNZBHistoryRequest HistoryRequest; InitMessageBase(&HistoryRequest.m_MessageBase, eRemoteRequestHistory, sizeof(HistoryRequest)); if (!m_pConnection->Send((char*)(&HistoryRequest), sizeof(HistoryRequest))) { perror("m_pConnection->Send"); return false; } printf("Request sent\n"); // Now listen for the returned list SNZBHistoryResponse HistoryResponse; bool bRead = m_pConnection->Recv((char*) &HistoryResponse, sizeof(HistoryResponse)); if (!bRead || (int)ntohl(HistoryResponse.m_MessageBase.m_iSignature) != (int)NZBMESSAGE_SIGNATURE || ntohl(HistoryResponse.m_MessageBase.m_iStructSize) != sizeof(HistoryResponse)) { printf("No response or invalid response (timeout, not nzbget-server or wrong nzbget-server version)\n"); return false; } char* pBuf = NULL; if (ntohl(HistoryResponse.m_iTrailingDataLength) > 0) { pBuf = (char*)malloc(ntohl(HistoryResponse.m_iTrailingDataLength)); if (!m_pConnection->Recv(pBuf, ntohl(HistoryResponse.m_iTrailingDataLength))) { free(pBuf); return false; } } m_pConnection->Disconnect(); if (ntohl(HistoryResponse.m_iTrailingDataLength) == 0) { printf("Server has no files in history\n"); } else { printf("History (most recent first)\n"); printf("-----------------------------------\n"); char* pBufPtr = (char*)pBuf; for (unsigned int i = 0; i < ntohl(HistoryResponse.m_iNrTrailingEntries); i++) { SNZBHistoryResponseEntry* pListAnswer = (SNZBHistoryResponseEntry*) pBufPtr; HistoryInfo::EKind eKind = (HistoryInfo::EKind)ntohl(pListAnswer->m_iKind); const char* szNicename = pBufPtr + sizeof(SNZBHistoryResponseEntry); if (eKind == HistoryInfo::hkNZBInfo) { long long lSize = Util::JoinInt64(ntohl(pListAnswer->m_iSizeHi), ntohl(pListAnswer->m_iSizeLo)); char szSize[20]; Util::FormatFileSize(szSize, sizeof(szSize), lSize); const char* szParStatusText[] = { "", "", ", Par failed", ", Par successful", ", Repair possible", ", Repair needed" }; const char* szScriptStatusText[] = { "", ", Script status unknown", ", Script failed", ", Script successful" }; printf("[%i] %s (%i files, %s%s%s)\n", ntohl(pListAnswer->m_iID), szNicename, ntohl(pListAnswer->m_iFileCount), szSize, szParStatusText[ntohl(pListAnswer->m_iParStatus)], szScriptStatusText[ntohl(pListAnswer->m_iScriptStatus)]); } else if (eKind == HistoryInfo::hkUrlInfo) { const char* szUrlStatusText[] = { "", "", "Url download successful", "Url download failed", "" }; printf("[%i] %s (%s)\n", ntohl(pListAnswer->m_iID), szNicename, szUrlStatusText[ntohl(pListAnswer->m_iUrlStatus)]); } pBufPtr += sizeof(SNZBHistoryResponseEntry) + ntohl(pListAnswer->m_iNicenameLen); } printf("-----------------------------------\n"); printf("Items: %i\n", ntohl(HistoryResponse.m_iNrTrailingEntries)); } free(pBuf); return true; } bool RemoteClient::RequestServerDownloadUrl(const char* szURL, const char* szNZBFilename, const char* szCategory, bool bAddFirst, bool bAddPaused, int iPriority) { if (!InitConnection()) return false; SNZBDownloadUrlRequest DownloadUrlRequest; InitMessageBase(&DownloadUrlRequest.m_MessageBase, eRemoteRequestDownloadUrl, sizeof(DownloadUrlRequest)); DownloadUrlRequest.m_bAddFirst = htonl(bAddFirst); DownloadUrlRequest.m_bAddPaused = htonl(bAddPaused); DownloadUrlRequest.m_iPriority = htonl(iPriority); strncpy(DownloadUrlRequest.m_szURL, szURL, NZBREQUESTFILENAMESIZE - 1); DownloadUrlRequest.m_szURL[NZBREQUESTFILENAMESIZE-1] = '\0'; DownloadUrlRequest.m_szCategory[0] = '\0'; if (szCategory) { strncpy(DownloadUrlRequest.m_szCategory, szCategory, NZBREQUESTFILENAMESIZE - 1); } DownloadUrlRequest.m_szCategory[NZBREQUESTFILENAMESIZE-1] = '\0'; DownloadUrlRequest.m_szNZBFilename[0] = '\0'; if (szNZBFilename) { strncpy(DownloadUrlRequest.m_szNZBFilename, szNZBFilename, NZBREQUESTFILENAMESIZE - 1); } DownloadUrlRequest.m_szNZBFilename[NZBREQUESTFILENAMESIZE-1] = '\0'; bool OK = m_pConnection->Send((char*)(&DownloadUrlRequest), sizeof(DownloadUrlRequest)); if (OK) { OK = ReceiveBoolResponse(); } else { perror("m_pConnection->Send"); } m_pConnection->Disconnect(); return OK; } bool RemoteClient::RequestUrlQueue() { if (!InitConnection()) return false; SNZBUrlQueueRequest UrlQueueRequest; InitMessageBase(&UrlQueueRequest.m_MessageBase, eRemoteRequestUrlQueue, sizeof(UrlQueueRequest)); if (!m_pConnection->Send((char*)(&UrlQueueRequest), sizeof(UrlQueueRequest))) { perror("m_pConnection->Send"); return false; } printf("Request sent\n"); // Now listen for the returned list SNZBUrlQueueResponse UrlQueueResponse; bool bRead = m_pConnection->Recv((char*) &UrlQueueResponse, sizeof(UrlQueueResponse)); if (!bRead || (int)ntohl(UrlQueueResponse.m_MessageBase.m_iSignature) != (int)NZBMESSAGE_SIGNATURE || ntohl(UrlQueueResponse.m_MessageBase.m_iStructSize) != sizeof(UrlQueueResponse)) { printf("No response or invalid response (timeout, not nzbget-server or wrong nzbget-server version)\n"); return false; } char* pBuf = NULL; if (ntohl(UrlQueueResponse.m_iTrailingDataLength) > 0) { pBuf = (char*)malloc(ntohl(UrlQueueResponse.m_iTrailingDataLength)); if (!m_pConnection->Recv(pBuf, ntohl(UrlQueueResponse.m_iTrailingDataLength))) { free(pBuf); return false; } } m_pConnection->Disconnect(); if (ntohl(UrlQueueResponse.m_iTrailingDataLength) == 0) { printf("Server has no urls queued for download\n"); } else { printf("Url-Queue\n"); printf("-----------------------------------\n"); char* pBufPtr = (char*)pBuf; for (unsigned int i = 0; i < ntohl(UrlQueueResponse.m_iNrTrailingEntries); i++) { SNZBUrlQueueResponseEntry* pUrlQueueAnswer = (SNZBUrlQueueResponseEntry*) pBufPtr; const char* szURL = pBufPtr + sizeof(SNZBUrlQueueResponseEntry); const char* szTitle = pBufPtr + sizeof(SNZBUrlQueueResponseEntry) + ntohl(pUrlQueueAnswer->m_iURLLen); char szNiceName[1024]; UrlInfo::MakeNiceName(szURL, szTitle, szNiceName, 1024); printf("[%i] %s\n", ntohl(pUrlQueueAnswer->m_iID), szNiceName); pBufPtr += sizeof(SNZBUrlQueueResponseEntry) + ntohl(pUrlQueueAnswer->m_iURLLen) + ntohl(pUrlQueueAnswer->m_iNZBFilenameLen); } free(pBuf); printf("-----------------------------------\n"); } return true; } nzbget-12.0+dfsg/RemoteClient.h000066400000000000000000000054511226450633000164150ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2005 Bo Cordes Petersen * Copyright (C) 2007-2009 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 544 $ * $Date: 2013-01-18 22:36:17 +0100 (Fri, 18 Jan 2013) $ * */ #ifndef REMOTECLIENT_H #define REMOTECLIENT_H #include "Options.h" #include "MessageBase.h" #include "Connection.h" #include "DownloadInfo.h" class RemoteClient { private: class MatchedNZBInfo: public NZBInfo { public: bool m_bMatch; }; class MatchedFileInfo: public FileInfo { public: bool m_bMatch; }; Connection* m_pConnection; bool m_bVerbose; bool InitConnection(); void InitMessageBase(SNZBRequestBase* pMessageBase, int iRequest, int iSize); bool ReceiveBoolResponse(); void printf(const char* msg, ...); void perror(const char* msg); public: RemoteClient(); ~RemoteClient(); void SetVerbose(bool bVerbose) { m_bVerbose = bVerbose; }; bool RequestServerDownload(const char* szFilename, const char* szCategory, bool bAddFirst, bool bAddPaused, int iPriority); bool RequestServerList(bool bFiles, bool bGroups, const char* szPattern); bool RequestServerPauseUnpause(bool bPause, eRemotePauseUnpauseAction iAction); bool RequestServerSetDownloadRate(int iRate); bool RequestServerDumpDebug(); bool RequestServerEditQueue(eRemoteEditAction iAction, int iOffset, const char* szText, int* pIDList, int iIDCount, NameList* pNameList, eRemoteMatchMode iMatchMode, bool bSmartOrder); bool RequestServerLog(int iLines); bool RequestServerShutdown(); bool RequestServerReload(); bool RequestServerVersion(); bool RequestPostQueue(); bool RequestWriteLog(int iKind, const char* szText); bool RequestScan(bool bSyncMode); bool RequestHistory(); bool RequestServerDownloadUrl(const char* szURL, const char* szNZBFilename, const char* szCategory, bool bAddFirst, bool bAddPaused, int iPriority); bool RequestUrlQueue(); void BuildFileList(SNZBListResponse* pListResponse, const char* pTrailingData, DownloadQueue* pDownloadQueue); }; #endif nzbget-12.0+dfsg/RemoteServer.cpp000066400000000000000000000131741226450633000170010ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2005 Bo Cordes Petersen * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 911 $ * $Date: 2013-11-24 20:29:52 +0100 (Sun, 24 Nov 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #ifndef WIN32 #include #include #include #include #endif #include "nzbget.h" #include "RemoteServer.h" #include "BinRpc.h" #include "WebServer.h" #include "Log.h" #include "Options.h" #include "Util.h" extern Options* g_pOptions; //***************************************************************** // RemoteServer RemoteServer::RemoteServer(bool bTLS) { debug("Creating RemoteServer"); m_bTLS = bTLS; m_pConnection = NULL; } RemoteServer::~RemoteServer() { debug("Destroying RemoteServer"); delete m_pConnection; } void RemoteServer::Run() { debug("Entering RemoteServer-loop"); #ifndef DISABLE_TLS if (m_bTLS) { if (strlen(g_pOptions->GetSecureCert()) == 0 || !Util::FileExists(g_pOptions->GetSecureCert())) { error("Could not initialize TLS, secure certificate is not configured or the cert-file was not found. Check option "); return; } if (strlen(g_pOptions->GetSecureKey()) == 0 || !Util::FileExists(g_pOptions->GetSecureKey())) { error("Could not initialize TLS, secure key is not configured or the key-file was not found. Check option "); return; } } #endif while (!IsStopped()) { bool bBind = true; if (!m_pConnection) { m_pConnection = new Connection(g_pOptions->GetControlIP(), m_bTLS ? g_pOptions->GetSecurePort() : g_pOptions->GetControlPort(), m_bTLS); m_pConnection->SetTimeout(g_pOptions->GetConnectionTimeout()); m_pConnection->SetSuppressErrors(false); bBind = m_pConnection->Bind(); } // Accept connections and store the new Connection Connection* pAcceptedConnection = NULL; if (bBind) { pAcceptedConnection = m_pConnection->Accept(); } if (!bBind || pAcceptedConnection == NULL) { // Remote server could not bind or accept connection, waiting 1/2 sec and try again if (IsStopped()) { break; } usleep(500 * 1000); delete m_pConnection; m_pConnection = NULL; continue; } RequestProcessor* commandThread = new RequestProcessor(); commandThread->SetAutoDestroy(true); commandThread->SetConnection(pAcceptedConnection); #ifndef DISABLE_TLS commandThread->SetTLS(m_bTLS); #endif commandThread->Start(); } if (m_pConnection) { m_pConnection->Disconnect(); } debug("Exiting RemoteServer-loop"); } void RemoteServer::Stop() { Thread::Stop(); if (m_pConnection) { m_pConnection->SetSuppressErrors(true); m_pConnection->Cancel(); #ifdef WIN32 m_pConnection->Disconnect(); #endif } } //***************************************************************** // RequestProcessor RequestProcessor::~RequestProcessor() { m_pConnection->Disconnect(); delete m_pConnection; } void RequestProcessor::Run() { bool bOK = false; m_pConnection->SetSuppressErrors(true); #ifndef DISABLE_TLS if (m_bTLS && !m_pConnection->StartTLS(false, g_pOptions->GetSecureCert(), g_pOptions->GetSecureKey())) { debug("Could not establish secure connection to web-client: Start TLS failed"); return; } #endif // Read the first 4 bytes to determine request type int iSignature = 0; if (!m_pConnection->Recv((char*)&iSignature, 4)) { debug("Could not read request signature, request received on port %i from %s", m_bTLS ? g_pOptions->GetSecurePort() : g_pOptions->GetControlPort(), m_pConnection->GetRemoteAddr()); return; } if ((int)ntohl(iSignature) == (int)NZBMESSAGE_SIGNATURE) { // binary request received bOK = true; BinRpcProcessor processor; processor.SetConnection(m_pConnection); processor.Execute(); } else if (!strncmp((char*)&iSignature, "POST", 4) || !strncmp((char*)&iSignature, "GET ", 4) || !strncmp((char*)&iSignature, "OPTI", 4)) { // HTTP request received char szBuffer[1024]; if (m_pConnection->ReadLine(szBuffer, sizeof(szBuffer), NULL)) { WebProcessor::EHttpMethod eHttpMethod = WebProcessor::hmGet; char* szUrl = szBuffer; if (!strncmp((char*)&iSignature, "POST", 4)) { eHttpMethod = WebProcessor::hmPost; szUrl++; } if (!strncmp((char*)&iSignature, "OPTI", 4) && strlen(szUrl) > 4) { eHttpMethod = WebProcessor::hmOptions; szUrl += 4; } if (char* p = strchr(szUrl, ' ')) { *p = '\0'; } debug("url: %s", szUrl); WebProcessor processor; processor.SetConnection(m_pConnection); processor.SetUrl(szUrl); processor.SetHttpMethod(eHttpMethod); processor.Execute(); bOK = true; } } if (!bOK) { warn("Non-nzbget request received on port %i from %s", m_bTLS ? g_pOptions->GetSecurePort() : g_pOptions->GetControlPort(), m_pConnection->GetRemoteAddr()); } } nzbget-12.0+dfsg/RemoteServer.h000066400000000000000000000031031226450633000164350ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2005 Bo Cordes Petersen * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 542 $ * $Date: 2013-01-17 20:07:13 +0100 (Thu, 17 Jan 2013) $ * */ #ifndef REMOTESERVER_H #define REMOTESERVER_H #include "Thread.h" #include "Connection.h" class RemoteServer : public Thread { private: bool m_bTLS; Connection* m_pConnection; public: RemoteServer(bool bTLS); ~RemoteServer(); virtual void Run(); virtual void Stop(); }; class RequestProcessor : public Thread { private: bool m_bTLS; Connection* m_pConnection; public: ~RequestProcessor(); virtual void Run(); void SetTLS(bool bTLS) { m_bTLS = bTLS; } void SetConnection(Connection* pConnection) { m_pConnection = pConnection; } }; #endif nzbget-12.0+dfsg/Scanner.cpp000066400000000000000000000427221226450633000157510ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 911 $ * $Date: 2013-11-24 20:29:52 +0100 (Sun, 24 Nov 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #ifdef WIN32 #include #else #include #endif #include #include "nzbget.h" #include "Scanner.h" #include "Options.h" #include "Log.h" #include "QueueCoordinator.h" #include "ScriptController.h" #include "DiskState.h" #include "Util.h" extern QueueCoordinator* g_pQueueCoordinator; extern Options* g_pOptions; extern DiskState* g_pDiskState; Scanner::FileData::FileData(const char* szFilename) { m_szFilename = strdup(szFilename); m_iSize = 0; m_tLastChange = 0; } Scanner::FileData::~FileData() { free(m_szFilename); } Scanner::QueueData::QueueData(const char* szFilename, const char* szNZBName, const char* szCategory, int iPriority, const char* szDupeKey, int iDupeScore, EDupeMode eDupeMode, NZBParameterList* pParameters, bool bAddTop, bool bAddPaused, EAddStatus* pAddStatus) { m_szFilename = strdup(szFilename); m_szNZBName = strdup(szNZBName); m_szCategory = strdup(szCategory ? szCategory : ""); m_iPriority = iPriority; m_szDupeKey = strdup(szDupeKey ? szDupeKey : ""); m_iDupeScore = iDupeScore; m_eDupeMode = eDupeMode; m_bAddTop = bAddTop; m_bAddPaused = bAddPaused; m_pAddStatus = pAddStatus; if (pParameters) { m_Parameters.CopyFrom(pParameters); } } Scanner::QueueData::~QueueData() { free(m_szFilename); free(m_szNZBName); free(m_szCategory); free(m_szDupeKey); } void Scanner::QueueData::SetAddStatus(EAddStatus eAddStatus) { if (m_pAddStatus) { *m_pAddStatus = eAddStatus; } } Scanner::Scanner() { debug("Creating Scanner"); m_bRequestedNZBDirScan = false; m_bScanning = false; m_iNZBDirInterval = g_pOptions->GetNzbDirInterval() * 1000; m_iPass = 0; const char* szNZBScript = g_pOptions->GetNZBProcess(); m_bNZBScript = szNZBScript && strlen(szNZBScript) > 0; } Scanner::~Scanner() { debug("Destroying Scanner"); for (FileList::iterator it = m_FileList.begin(); it != m_FileList.end(); it++) { delete *it; } m_FileList.clear(); ClearQueueList(); } void Scanner::ClearQueueList() { for (QueueList::iterator it = m_QueueList.begin(); it != m_QueueList.end(); it++) { delete *it; } m_QueueList.clear(); } void Scanner::Check() { m_mutexScan.Lock(); if (m_bRequestedNZBDirScan || (!g_pOptions->GetPauseScan() && g_pOptions->GetNzbDirInterval() > 0 && m_iNZBDirInterval >= g_pOptions->GetNzbDirInterval() * 1000)) { // check nzbdir every g_pOptions->GetNzbDirInterval() seconds or if requested bool bCheckStat = !m_bRequestedNZBDirScan; m_bRequestedNZBDirScan = false; m_bScanning = true; CheckIncomingNZBs(g_pOptions->GetNzbDir(), "", bCheckStat); if (!bCheckStat && m_bNZBScript) { // if immediate scan requested, we need second scan to process files extracted by NzbProcess-script CheckIncomingNZBs(g_pOptions->GetNzbDir(), "", bCheckStat); } m_bScanning = false; m_iNZBDirInterval = 0; // if NzbDirFileAge is less than NzbDirInterval (that can happen if NzbDirInterval // is set for rare scans like once per hour) we make 4 scans: // - one additional scan is neccessary to check sizes of detected files; // - another scan is required to check files which were extracted by NzbProcess-script; // - third scan is needed to check sizes of extracted files. if (g_pOptions->GetNzbDirInterval() > 0 && g_pOptions->GetNzbDirFileAge() < g_pOptions->GetNzbDirInterval()) { int iMaxPass = m_bNZBScript ? 3 : 1; if (m_iPass < iMaxPass) { // scheduling another scan of incoming directory in NzbDirFileAge seconds. m_iNZBDirInterval = (g_pOptions->GetNzbDirInterval() - g_pOptions->GetNzbDirFileAge()) * 1000; m_iPass++; } else { m_iPass = 0; } } DropOldFiles(); ClearQueueList(); } m_iNZBDirInterval += 200; m_mutexScan.Unlock(); } /** * Check if there are files in directory for incoming nzb-files * and add them to download queue */ void Scanner::CheckIncomingNZBs(const char* szDirectory, const char* szCategory, bool bCheckStat) { DirBrowser dir(szDirectory); while (const char* filename = dir.Next()) { struct stat buffer; char fullfilename[1023 + 1]; // one char reserved for the trailing slash (if needed) snprintf(fullfilename, 1023, "%s%s", szDirectory, filename); fullfilename[1023 - 1] = '\0'; if (!stat(fullfilename, &buffer)) { // check subfolders if ((buffer.st_mode & S_IFDIR) != 0 && strcmp(filename, ".") && strcmp(filename, "..")) { fullfilename[strlen(fullfilename) + 1] = '\0'; fullfilename[strlen(fullfilename)] = PATH_SEPARATOR; const char* szUseCategory = filename; char szSubCategory[1024]; if (strlen(szCategory) > 0) { snprintf(szSubCategory, 1023, "%s%c%s", szCategory, PATH_SEPARATOR, filename); szSubCategory[1024 - 1] = '\0'; szUseCategory = szSubCategory; } CheckIncomingNZBs(fullfilename, szUseCategory, bCheckStat); } else if ((buffer.st_mode & S_IFDIR) == 0 && CanProcessFile(fullfilename, bCheckStat)) { ProcessIncomingFile(szDirectory, filename, fullfilename, szCategory); } } } } /** * Only files which were not changed during last g_pOptions->GetNzbDirFileAge() seconds * can be processed. That prevents the processing of files, which are currently being * copied into nzb-directory (eg. being downloaded in web-browser). */ bool Scanner::CanProcessFile(const char* szFullFilename, bool bCheckStat) { const char* szExtension = strrchr(szFullFilename, '.'); if (!szExtension || !strcasecmp(szExtension, ".queued") || !strcasecmp(szExtension, ".error") || !strcasecmp(szExtension, ".processed")) { return false; } if (!bCheckStat) { return true; } long long lSize = Util::FileSize(szFullFilename); time_t tCurrent = time(NULL); bool bCanProcess = false; bool bInList = false; for (FileList::iterator it = m_FileList.begin(); it != m_FileList.end(); it++) { FileData* pFileData = *it; if (!strcmp(pFileData->GetFilename(), szFullFilename)) { bInList = true; if (pFileData->GetSize() == lSize && tCurrent - pFileData->GetLastChange() >= g_pOptions->GetNzbDirFileAge()) { bCanProcess = true; delete pFileData; m_FileList.erase(it); } else { pFileData->SetSize(lSize); if (pFileData->GetSize() != lSize) { pFileData->SetLastChange(tCurrent); } } break; } } if (!bInList) { FileData* pFileData = new FileData(szFullFilename); pFileData->SetSize(lSize); pFileData->SetLastChange(tCurrent); m_FileList.push_back(pFileData); } return bCanProcess; } /** * Remove old files from the list of monitored files. * Normally these files are deleted from the list when they are processed. * However if a file was detected by function "CanProcessFile" once but wasn't * processed later (for example if the user deleted it), it will stay in the list, * until we remove it here. */ void Scanner::DropOldFiles() { time_t tCurrent = time(NULL); int i = 0; for (FileList::iterator it = m_FileList.begin(); it != m_FileList.end(); ) { FileData* pFileData = *it; if ((tCurrent - pFileData->GetLastChange() >= (g_pOptions->GetNzbDirInterval() + g_pOptions->GetNzbDirFileAge()) * 2) || // can occur if the system clock was adjusted tCurrent < pFileData->GetLastChange()) { debug("Removing file %s from scan file list", pFileData->GetFilename()); delete pFileData; m_FileList.erase(it); it = m_FileList.begin() + i; } else { it++; i++; } } } void Scanner::ProcessIncomingFile(const char* szDirectory, const char* szBaseFilename, const char* szFullFilename, const char* szCategory) { const char* szExtension = strrchr(szBaseFilename, '.'); if (!szExtension) { return; } char* szNZBName = strdup(""); char* szNZBCategory = strdup(szCategory); NZBParameterList* pParameters = new NZBParameterList(); int iPriority = 0; bool bAddTop = false; bool bAddPaused = false; const char* szDupeKey = NULL; int iDupeScore = 0; EDupeMode eDupeMode = dmScore; EAddStatus eAddStatus = asSkipped; bool bAdded = false; QueueData* pQueueData = NULL; for (QueueList::iterator it = m_QueueList.begin(); it != m_QueueList.end(); it++) { QueueData* pQueueData1 = *it; if (Util::SameFilename(pQueueData1->GetFilename(), szFullFilename)) { pQueueData = pQueueData1; free(szNZBName); szNZBName = strdup(pQueueData->GetNZBName()); free(szNZBCategory); szNZBCategory = strdup(pQueueData->GetCategory()); iPriority = pQueueData->GetPriority(); szDupeKey = pQueueData->GetDupeKey(); iDupeScore = pQueueData->GetDupeScore(); eDupeMode = pQueueData->GetDupeMode(); bAddTop = pQueueData->GetAddTop(); bAddPaused = pQueueData->GetAddPaused(); pParameters->CopyFrom(pQueueData->GetParameters()); } } InitPPParameters(szNZBCategory, pParameters); bool bExists = true; if (m_bNZBScript && strcasecmp(szExtension, ".nzb_processed")) { NZBScriptController::ExecuteScript(g_pOptions->GetNZBProcess(), szFullFilename, szDirectory, &szNZBName, &szNZBCategory, &iPriority, pParameters, &bAddTop, &bAddPaused); bExists = Util::FileExists(szFullFilename); if (bExists && strcasecmp(szExtension, ".nzb")) { char bakname2[1024]; bool bRenameOK = Util::RenameBak(szFullFilename, "processed", false, bakname2, 1024); if (!bRenameOK) { char szSysErrStr[256]; error("Could not rename file %s to %s: %s", szFullFilename, bakname2, Util::GetLastErrorMessage(szSysErrStr, sizeof(szSysErrStr))); } } } if (!strcasecmp(szExtension, ".nzb_processed")) { char szRenamedName[1024]; bool bRenameOK = Util::RenameBak(szFullFilename, "nzb", true, szRenamedName, 1024); if (bRenameOK) { bAdded = AddFileToQueue(szRenamedName, szNZBName, szNZBCategory, iPriority, szDupeKey, iDupeScore, eDupeMode, pParameters, bAddTop, bAddPaused); } else { char szSysErrStr[256]; error("Could not rename file %s to %s: %s", szFullFilename, szRenamedName, Util::GetLastErrorMessage(szSysErrStr, sizeof(szSysErrStr))); eAddStatus = asFailed; } } else if (bExists && !strcasecmp(szExtension, ".nzb")) { bAdded = AddFileToQueue(szFullFilename, szNZBName, szNZBCategory, iPriority, szDupeKey, iDupeScore, eDupeMode, pParameters, bAddTop, bAddPaused); } delete pParameters; free(szNZBName); free(szNZBCategory); if (pQueueData) { pQueueData->SetAddStatus(eAddStatus == asFailed ? asFailed : bAdded ? asSuccess : asSkipped); } } void Scanner::InitPPParameters(const char* szCategory, NZBParameterList* pParameters) { bool bUnpack = g_pOptions->GetUnpack(); const char* szDefScript = g_pOptions->GetDefScript(); if (szCategory && *szCategory) { Options::Category* pCategory = g_pOptions->FindCategory(szCategory, false); if (pCategory) { bUnpack = pCategory->GetUnpack(); if (pCategory->GetDefScript() && *pCategory->GetDefScript()) { szDefScript = pCategory->GetDefScript(); } } } pParameters->SetParameter("*Unpack:", bUnpack ? "yes" : "no"); if (szDefScript && *szDefScript) { // split szDefScript into tokens and create pp-parameter for each token char* szDefScript2 = strdup(szDefScript); char* saveptr; char* szScriptName = strtok_r(szDefScript2, ",;", &saveptr); while (szScriptName) { szScriptName = Util::Trim(szScriptName); if (szScriptName[0] != '\0') { char szParam[1024]; snprintf(szParam, 1024, "%s:", szScriptName); szParam[1024-1] = '\0'; pParameters->SetParameter(szParam, "yes"); } szScriptName = strtok_r(NULL, ",;", &saveptr); } free(szDefScript2); } } bool Scanner::AddFileToQueue(const char* szFilename, const char* szNZBName, const char* szCategory, int iPriority, const char* szDupeKey, int iDupeScore, EDupeMode eDupeMode, NZBParameterList* pParameters, bool bAddTop, bool bAddPaused) { const char* szBasename = Util::BaseFileName(szFilename); info("Collection %s found", szBasename); NZBFile* pNZBFile = NZBFile::Create(szFilename, szCategory); bool bOK = pNZBFile != NULL; if (!bOK) { error("Could not add collection %s to queue", szBasename); } char bakname2[1024]; if (!Util::RenameBak(szFilename, pNZBFile ? "queued" : "error", false, bakname2, 1024)) { bOK = false; char szSysErrStr[256]; error("Could not rename file %s to %s: %s", szFilename, bakname2, Util::GetLastErrorMessage(szSysErrStr, sizeof(szSysErrStr))); } if (bOK) { pNZBFile->GetNZBInfo()->SetQueuedFilename(bakname2); if (szNZBName && strlen(szNZBName) > 0) { pNZBFile->GetNZBInfo()->SetName(NULL); #ifdef WIN32 char* szAnsiFilename = strdup(szNZBName); WebUtil::Utf8ToAnsi(szAnsiFilename, strlen(szAnsiFilename) + 1); pNZBFile->GetNZBInfo()->SetFilename(szAnsiFilename); free(szAnsiFilename); #else pNZBFile->GetNZBInfo()->SetFilename(szNZBName); #endif pNZBFile->GetNZBInfo()->BuildDestDirName(); } pNZBFile->GetNZBInfo()->SetDupeKey(szDupeKey); pNZBFile->GetNZBInfo()->SetDupeScore(iDupeScore); pNZBFile->GetNZBInfo()->SetDupeMode(eDupeMode); if (pNZBFile->GetPassword()) { pNZBFile->GetNZBInfo()->GetParameters()->SetParameter("*Unpack:Password", pNZBFile->GetPassword()); } pNZBFile->GetNZBInfo()->GetParameters()->CopyFrom(pParameters); for (NZBFile::FileInfos::iterator it = pNZBFile->GetFileInfos()->begin(); it != pNZBFile->GetFileInfos()->end(); it++) { FileInfo* pFileInfo = *it; pFileInfo->SetPriority(iPriority); pFileInfo->SetPaused(bAddPaused); } g_pQueueCoordinator->AddNZBFileToQueue(pNZBFile, bAddTop); } delete pNZBFile; return bOK; } void Scanner::ScanNZBDir(bool bSyncMode) { m_mutexScan.Lock(); m_bScanning = true; m_bRequestedNZBDirScan = true; m_mutexScan.Unlock(); while (bSyncMode && (m_bScanning || m_bRequestedNZBDirScan)) { usleep(100 * 1000); } } Scanner::EAddStatus Scanner::AddExternalFile(const char* szNZBName, const char* szCategory, int iPriority, const char* szDupeKey, int iDupeScore, EDupeMode eDupeMode, NZBParameterList* pParameters, bool bAddTop, bool bAddPaused, const char* szFileName, const char* szBuffer, int iBufSize) { bool bNZB = false; char szTempFileName[1024]; if (szFileName) { strncpy(szTempFileName, szFileName, 1024); szTempFileName[1024-1] = '\0'; } else { int iNum = 1; while (iNum == 1 || Util::FileExists(szTempFileName)) { snprintf(szTempFileName, 1024, "%snzb-%i.tmp", g_pOptions->GetTempDir(), iNum); szTempFileName[1024-1] = '\0'; iNum++; } if (!Util::SaveBufferIntoFile(szTempFileName, szBuffer, iBufSize)) { error("Could not create file %s", szTempFileName); return asFailed; } char buf[1024]; strncpy(buf, szBuffer, 1024); buf[1024-1] = '\0'; bNZB = !strncmp(buf, "GetNzbDir(), szValidNZBName); char *szExt = strrchr(szValidNZBName, '.'); if (szExt) { *szExt = '\0'; szExt++; } int iNum = 2; while (Util::FileExists(szScanFileName)) { if (szExt) { snprintf(szScanFileName, 1024, "%s%s_%i.%s", g_pOptions->GetNzbDir(), szValidNZBName, iNum, szExt); } else { snprintf(szScanFileName, 1024, "%s%s_%i", g_pOptions->GetNzbDir(), szValidNZBName, iNum); } szScanFileName[1024-1] = '\0'; iNum++; } m_mutexScan.Lock(); if (!Util::MoveFile(szTempFileName, szScanFileName)) { char szSysErrStr[256]; error("Could not move file %s to %s: %s", szTempFileName, szScanFileName, Util::GetLastErrorMessage(szSysErrStr, sizeof(szSysErrStr))); remove(szTempFileName); m_mutexScan.Unlock(); // UNLOCK return asFailed; } char* szUseCategory = strdup(szCategory ? szCategory : ""); Options::Category *pCategory = g_pOptions->FindCategory(szCategory, true); if (pCategory && strcmp(szCategory, pCategory->GetName())) { free(szUseCategory); szUseCategory = strdup(pCategory->GetName()); detail("Category %s matched to %s for %s", szCategory, szUseCategory, szNZBName); } EAddStatus eAddStatus = asSkipped; QueueData* pQueueData = new QueueData(szScanFileName, szNZBName, szUseCategory, iPriority, szDupeKey, iDupeScore, eDupeMode, pParameters, bAddTop, bAddPaused, &eAddStatus); free(szUseCategory); m_QueueList.push_back(pQueueData); m_mutexScan.Unlock(); ScanNZBDir(true); return eAddStatus; } nzbget-12.0+dfsg/Scanner.h000066400000000000000000000101061226450633000154050ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 836 $ * $Date: 2013-09-23 22:18:54 +0200 (Mon, 23 Sep 2013) $ * */ #ifndef SCANNER_H #define SCANNER_H #include #include #include "DownloadInfo.h" #include "Thread.h" class Scanner { public: enum EAddStatus { asSkipped, asSuccess, asFailed }; private: class FileData { private: char* m_szFilename; long long m_iSize; time_t m_tLastChange; public: FileData(const char* szFilename); ~FileData(); const char* GetFilename() { return m_szFilename; } long long GetSize() { return m_iSize; } void SetSize(long long lSize) { m_iSize = lSize; } time_t GetLastChange() { return m_tLastChange; } void SetLastChange(time_t tLastChange) { m_tLastChange = tLastChange; } }; typedef std::deque FileList; class QueueData { private: char* m_szFilename; char* m_szNZBName; char* m_szCategory; int m_iPriority; char* m_szDupeKey; int m_iDupeScore; EDupeMode m_eDupeMode; NZBParameterList m_Parameters; bool m_bAddTop; bool m_bAddPaused; EAddStatus* m_pAddStatus; public: QueueData(const char* szFilename, const char* szNZBName, const char* szCategory, int iPriority, const char* szDupeKey, int iDupeScore, EDupeMode eDupeMode, NZBParameterList* pParameters, bool bAddTop, bool bAddPaused, EAddStatus* pAddStatus); ~QueueData(); const char* GetFilename() { return m_szFilename; } const char* GetNZBName() { return m_szNZBName; } const char* GetCategory() { return m_szCategory; } int GetPriority() { return m_iPriority; } const char* GetDupeKey() { return m_szDupeKey; } int GetDupeScore() { return m_iDupeScore; } EDupeMode GetDupeMode() { return m_eDupeMode; } NZBParameterList* GetParameters() { return &m_Parameters; } bool GetAddTop() { return m_bAddTop; } bool GetAddPaused() { return m_bAddPaused; } void SetAddStatus(EAddStatus eAddStatus); }; typedef std::deque QueueList; bool m_bRequestedNZBDirScan; int m_iNZBDirInterval; bool m_bNZBScript; int m_iPass; FileList m_FileList; QueueList m_QueueList; bool m_bScanning; Mutex m_mutexScan; void CheckIncomingNZBs(const char* szDirectory, const char* szCategory, bool bCheckStat); bool AddFileToQueue(const char* szFilename, const char* szNZBName, const char* szCategory, int iPriority, const char* szDupeKey, int iDupeScore, EDupeMode eDupeMode, NZBParameterList* pParameters, bool bAddTop, bool bAddPaused); void ProcessIncomingFile(const char* szDirectory, const char* szBaseFilename, const char* szFullFilename, const char* szCategory); bool CanProcessFile(const char* szFullFilename, bool bCheckStat); void InitPPParameters(const char* szCategory, NZBParameterList* pParameters); void DropOldFiles(); void ClearQueueList(); public: Scanner(); ~Scanner(); void ScanNZBDir(bool bSyncMode); void Check(); EAddStatus AddExternalFile(const char* szNZBName, const char* szCategory, int iPriority, const char* szDupeKey, int iDupeScore, EDupeMode eDupeMode, NZBParameterList* pParameters, bool bAddTop, bool bAddPaused, const char* szFileName, const char* szBuffer, int iBufSize); }; #endif nzbget-12.0+dfsg/Scheduler.cpp000066400000000000000000000207641226450633000163000ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2008-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 914 $ * $Date: 2013-11-28 22:03:01 +0100 (Thu, 28 Nov 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #else #include #endif #include #include #include #include "nzbget.h" #include "Scheduler.h" #include "ScriptController.h" #include "Options.h" #include "Log.h" #include "NewsServer.h" #include "ServerPool.h" #include "FeedInfo.h" #include "FeedCoordinator.h" extern Options* g_pOptions; extern ServerPool* g_pServerPool; extern FeedCoordinator* g_pFeedCoordinator; Scheduler::Task::Task(int iHours, int iMinutes, int iWeekDaysBits, ECommand eCommand, const char* szParam) { m_iHours = iHours; m_iMinutes = iMinutes; m_iWeekDaysBits = iWeekDaysBits; m_eCommand = eCommand; m_szParam = szParam ? strdup(szParam) : NULL; m_tLastExecuted = 0; } Scheduler::Task::~Task() { free(m_szParam); } Scheduler::Scheduler() { debug("Creating Scheduler"); m_tLastCheck = 0; m_TaskList.clear(); } Scheduler::~Scheduler() { debug("Destroying Scheduler"); for (TaskList::iterator it = m_TaskList.begin(); it != m_TaskList.end(); it++) { delete *it; } } void Scheduler::AddTask(Task* pTask) { m_mutexTaskList.Lock(); m_TaskList.push_back(pTask); m_mutexTaskList.Unlock(); } bool Scheduler::CompareTasks(Scheduler::Task* pTask1, Scheduler::Task* pTask2) { return (pTask1->m_iHours < pTask2->m_iHours) || ((pTask1->m_iHours == pTask2->m_iHours) && (pTask1->m_iMinutes < pTask2->m_iMinutes)); } void Scheduler::FirstCheck() { m_mutexTaskList.Lock(); m_TaskList.sort(CompareTasks); m_mutexTaskList.Unlock(); // check all tasks for the last week time_t tCurrent = time(NULL); m_tLastCheck = tCurrent - 60*60*24*7; m_bDetectClockChanges = false; m_bExecuteProcess = false; CheckTasks(); } void Scheduler::IntervalCheck() { m_bDetectClockChanges = true; m_bExecuteProcess = true; CheckTasks(); } void Scheduler::CheckTasks() { PrepareLog(); m_mutexTaskList.Lock(); time_t tCurrent = time(NULL); if (m_bDetectClockChanges) { // Detect large step changes of system time time_t tDiff = tCurrent - m_tLastCheck; if (tDiff > 60*90 || tDiff < -60*90) { debug("Reset scheduled tasks (detected clock adjustment greater than 90 minutes)"); m_bExecuteProcess = false; m_tLastCheck = tCurrent; for (TaskList::iterator it = m_TaskList.begin(); it != m_TaskList.end(); it++) { Task* pTask = *it; pTask->m_tLastExecuted = 0; } } } tm tmCurrent; localtime_r(&tCurrent, &tmCurrent); tm tmLastCheck; localtime_r(&m_tLastCheck, &tmLastCheck); tm tmLoop; memcpy(&tmLoop, &tmLastCheck, sizeof(tmLastCheck)); tmLoop.tm_hour = tmCurrent.tm_hour; tmLoop.tm_min = tmCurrent.tm_min; tmLoop.tm_sec = tmCurrent.tm_sec; time_t tLoop = mktime(&tmLoop); while (tLoop <= tCurrent) { for (TaskList::iterator it = m_TaskList.begin(); it != m_TaskList.end(); it++) { Task* pTask = *it; if (pTask->m_tLastExecuted != tLoop) { tm tmAppoint; memcpy(&tmAppoint, &tmLoop, sizeof(tmLoop)); tmAppoint.tm_hour = pTask->m_iHours; tmAppoint.tm_min = pTask->m_iMinutes; tmAppoint.tm_sec = 0; time_t tAppoint = mktime(&tmAppoint); tAppoint -= g_pOptions->GetTimeCorrection(); int iWeekDay = tmAppoint.tm_wday; if (iWeekDay == 0) { iWeekDay = 7; } bool bWeekDayOK = pTask->m_iWeekDaysBits == 0 || (pTask->m_iWeekDaysBits & (1 << (iWeekDay - 1))); bool bDoTask = bWeekDayOK && m_tLastCheck < tAppoint && tAppoint <= tCurrent; //debug("TEMP: 1) m_tLastCheck=%i, tCurrent=%i, tLoop=%i, tAppoint=%i, bWeekDayOK=%i, bDoTask=%i", m_tLastCheck, tCurrent, tLoop, tAppoint, (int)bWeekDayOK, (int)bDoTask); if (bDoTask) { ExecuteTask(pTask); pTask->m_tLastExecuted = tLoop; } } } tLoop += 60*60*24; // inc day localtime_r(&tLoop, &tmLoop); } m_tLastCheck = tCurrent; m_mutexTaskList.Unlock(); PrintLog(); } void Scheduler::ExecuteTask(Task* pTask) { const char* szCommandName[] = { "Pause", "Unpause", "Set download rate", "Execute program", "Pause Scan", "Unpause Scan", "Enable Server", "Disable Server", "Fetch Feed" }; debug("Executing scheduled command: %s", szCommandName[pTask->m_eCommand]); switch (pTask->m_eCommand) { case scDownloadRate: if (!Util::EmptyStr(pTask->m_szParam)) { g_pOptions->SetDownloadRate(atoi(pTask->m_szParam) * 1024); m_bDownloadRateChanged = true; } break; case scPauseDownload: case scUnpauseDownload: m_bPauseDownload = pTask->m_eCommand == scPauseDownload; m_bPauseDownloadChanged = true; break; case scProcess: if (m_bExecuteProcess) { SchedulerScriptController::StartScript(pTask->m_szParam); } break; case scPauseScan: case scUnpauseScan: g_pOptions->SetPauseScan(pTask->m_eCommand == scPauseScan); m_bPauseScanChanged = true; break; case scActivateServer: case scDeactivateServer: EditServer(pTask->m_eCommand == scActivateServer, pTask->m_szParam); break; case scFetchFeed: if (m_bExecuteProcess) { FetchFeed(pTask->m_szParam); break; } } } void Scheduler::PrepareLog() { m_bDownloadRateChanged = false; m_bPauseDownloadChanged = false; m_bPauseScanChanged = false; m_bServerChanged = false; } void Scheduler::PrintLog() { if (m_bDownloadRateChanged) { info("Scheduler: setting download rate to %i KB/s", g_pOptions->GetDownloadRate() / 1024); } if (m_bPauseScanChanged) { info("Scheduler: %s scan", g_pOptions->GetPauseScan() ? "pausing" : "unpausing"); } if (m_bServerChanged) { int index = 0; for (Servers::iterator it = g_pServerPool->GetServers()->begin(); it != g_pServerPool->GetServers()->end(); it++, index++) { NewsServer* pServer = *it; if (pServer->GetActive() != m_ServerStatusList[index]) { info("Scheduler: %s %s", pServer->GetActive() ? "activating" : "deactivating", pServer->GetName()); } } g_pServerPool->Changed(); } } void Scheduler::EditServer(bool bActive, const char* szServerList) { char* szServerList2 = strdup(szServerList); char* saveptr; char* szServer = strtok_r(szServerList2, ",;", &saveptr); while (szServer) { szServer = Util::Trim(szServer); if (!Util::EmptyStr(szServer)) { int iID = atoi(szServer); for (Servers::iterator it = g_pServerPool->GetServers()->begin(); it != g_pServerPool->GetServers()->end(); it++) { NewsServer* pServer = *it; if ((iID > 0 && pServer->GetID() == iID) || !strcasecmp(pServer->GetName(), szServer)) { if (!m_bServerChanged) { // store old server status for logging m_ServerStatusList.clear(); m_ServerStatusList.reserve(g_pServerPool->GetServers()->size()); for (Servers::iterator it2 = g_pServerPool->GetServers()->begin(); it2 != g_pServerPool->GetServers()->end(); it2++) { NewsServer* pServer2 = *it2; m_ServerStatusList.push_back(pServer2->GetActive()); } } m_bServerChanged = true; pServer->SetActive(bActive); break; } } } szServer = strtok_r(NULL, ",;", &saveptr); } free(szServerList2); } void Scheduler::FetchFeed(const char* szFeedList) { char* szFeedList2 = strdup(szFeedList); char* saveptr; char* szFeed = strtok_r(szFeedList2, ",;", &saveptr); while (szFeed) { szFeed = Util::Trim(szFeed); if (!Util::EmptyStr(szFeed)) { int iID = atoi(szFeed); for (Feeds::iterator it = g_pFeedCoordinator->GetFeeds()->begin(); it != g_pFeedCoordinator->GetFeeds()->end(); it++) { FeedInfo* pFeed = *it; if (pFeed->GetID() == iID || !strcasecmp(pFeed->GetName(), szFeed) || !strcasecmp("0", szFeed)) { g_pFeedCoordinator->FetchFeed(pFeed->GetID()); break; } } } szFeed = strtok_r(NULL, ",;", &saveptr); } free(szFeedList2); } nzbget-12.0+dfsg/Scheduler.h000066400000000000000000000047741226450633000157500ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2008-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 906 $ * $Date: 2013-11-12 21:54:45 +0100 (Tue, 12 Nov 2013) $ * */ #ifndef SCHEDULER_H #define SCHEDULER_H #include #include #include #include "Thread.h" class Scheduler { public: enum ECommand { scPauseDownload, scUnpauseDownload, scDownloadRate, scProcess, scPauseScan, scUnpauseScan, scActivateServer, scDeactivateServer, scFetchFeed }; class Task { private: int m_iHours; int m_iMinutes; int m_iWeekDaysBits; ECommand m_eCommand; char* m_szParam; time_t m_tLastExecuted; public: Task(int iHours, int iMinutes, int iWeekDaysBits, ECommand eCommand, const char* szParam); ~Task(); friend class Scheduler; }; private: typedef std::list TaskList; typedef std::vector ServerStatusList; TaskList m_TaskList; Mutex m_mutexTaskList; time_t m_tLastCheck; bool m_bDetectClockChanges; bool m_bDownloadRateChanged; bool m_bExecuteProcess; bool m_bPauseDownloadChanged; bool m_bPauseDownload; bool m_bPauseScanChanged; bool m_bServerChanged; ServerStatusList m_ServerStatusList; void ExecuteTask(Task* pTask); void CheckTasks(); static bool CompareTasks(Scheduler::Task* pTask1, Scheduler::Task* pTask2); void PrepareLog(); void PrintLog(); void EditServer(bool bActive, const char* szServerList); void FetchFeed(const char* szFeedList); public: Scheduler(); ~Scheduler(); void AddTask(Task* pTask); void FirstCheck(); void IntervalCheck(); bool GetPauseDownloadChanged() { return m_bPauseDownloadChanged; } bool GetPauseDownload() { return m_bPauseDownload; } }; #endif nzbget-12.0+dfsg/ScriptController.cpp000066400000000000000000001001201226450633000176530ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 909 $ * $Date: 2013-11-18 21:37:20 +0100 (Mon, 18 Nov 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #ifndef WIN32 #include #include #include #endif #include #include #include #include #include #include "nzbget.h" #include "ScriptController.h" #include "Log.h" #include "Util.h" // System global variable holding environments variables extern char** environ; extern Options* g_pOptions; extern char* (*g_szEnvironmentVariables)[]; extern DownloadQueueHolder* g_pDownloadQueueHolder; static const int POSTPROCESS_PARCHECK = 92; static const int POSTPROCESS_SUCCESS = 93; static const int POSTPROCESS_ERROR = 94; static const int POSTPROCESS_NONE = 95; #ifndef WIN32 #define CHILD_WATCHDOG 1 #endif #ifdef CHILD_WATCHDOG /** * Sometimes the forked child process doesn't start properly and hangs * just during the starting. I didn't find any explanation about what * could cause that problem except of a general advice, that * "a forking in a multithread application is not recommended". * * Workaround: * 1) child process prints a line into stdout directly after the start; * 2) parent process waits for a line for 60 seconds. If it didn't receive it * the cild process assumed to hang and will be killed. Another attempt * will be made. */ class ChildWatchDog : public Thread { private: pid_t m_hProcessID; protected: virtual void Run(); public: void SetProcessID(pid_t hProcessID) { m_hProcessID = hProcessID; } }; void ChildWatchDog::Run() { static const int WAIT_SECONDS = 60; time_t tStart = time(NULL); while (!IsStopped() && (time(NULL) - tStart) < WAIT_SECONDS) { usleep(10 * 1000); } if (!IsStopped()) { info("Restarting hanging child process"); kill(m_hProcessID, SIGKILL); } } #endif EnvironmentStrings::EnvironmentStrings() { } EnvironmentStrings::~EnvironmentStrings() { Clear(); } void EnvironmentStrings::Clear() { for (Strings::iterator it = m_strings.begin(); it != m_strings.end(); it++) { free(*it); } m_strings.clear(); } void EnvironmentStrings::InitFromCurrentProcess() { for (int i = 0; (*g_szEnvironmentVariables)[i]; i++) { char* szVar = (*g_szEnvironmentVariables)[i]; Append(strdup(szVar)); } } void EnvironmentStrings::Append(char* szString) { m_strings.push_back(szString); } #ifdef WIN32 /* * Returns environment block in format suitable for using with CreateProcess. * The allocated memory must be freed by caller using "free()". */ char* EnvironmentStrings::GetStrings() { int iSize = 1; for (Strings::iterator it = m_strings.begin(); it != m_strings.end(); it++) { char* szVar = *it; iSize += strlen(szVar) + 1; } char* szStrings = (char*)malloc(iSize); char* szPtr = szStrings; for (Strings::iterator it = m_strings.begin(); it != m_strings.end(); it++) { char* szVar = *it; strcpy(szPtr, szVar); szPtr += strlen(szVar) + 1; } *szPtr = '\0'; return szStrings; } #else /* * Returns environment block in format suitable for using with execve * The allocated memory must be freed by caller using "free()". */ char** EnvironmentStrings::GetStrings() { char** pStrings = (char**)malloc((m_strings.size() + 1) * sizeof(char*)); char** pPtr = pStrings; for (Strings::iterator it = m_strings.begin(); it != m_strings.end(); it++) { char* szVar = *it; *pPtr = szVar; pPtr++; } *pPtr = NULL; return pStrings; } #endif ScriptController::ScriptController() { m_szScript = NULL; m_szWorkingDir = NULL; m_szArgs = NULL; m_bFreeArgs = false; m_szInfoName = NULL; m_szLogPrefix = NULL; m_bTerminated = false; m_bDetached = false; m_environmentStrings.InitFromCurrentProcess(); } ScriptController::~ScriptController() { if (m_bFreeArgs) { for (const char** szArgPtr = m_szArgs; *szArgPtr; szArgPtr++) { free((char*)*szArgPtr); } free(m_szArgs); } } void ScriptController::ResetEnv() { m_environmentStrings.Clear(); m_environmentStrings.InitFromCurrentProcess(); } void ScriptController::SetEnvVar(const char* szName, const char* szValue) { int iLen = strlen(szName) + strlen(szValue) + 2; char* szVar = (char*)malloc(iLen); snprintf(szVar, iLen, "%s=%s", szName, szValue); m_environmentStrings.Append(szVar); } void ScriptController::SetIntEnvVar(const char* szName, int iValue) { char szValue[1024]; snprintf(szValue, 10, "%i", iValue); szValue[1024-1] = '\0'; SetEnvVar(szName, szValue); } /** * If szStripPrefix is not NULL, only options, whose names start with the prefix * are processed. The prefix is then stripped from the names. * If szStripPrefix is NULL, all options are processed; without stripping. */ void ScriptController::PrepareEnvOptions(const char* szStripPrefix) { int iPrefixLen = szStripPrefix ? strlen(szStripPrefix) : 0; Options::OptEntries* pOptEntries = g_pOptions->LockOptEntries(); for (Options::OptEntries::iterator it = pOptEntries->begin(); it != pOptEntries->end(); it++) { Options::OptEntry* pOptEntry = *it; if (szStripPrefix && !strncmp(pOptEntry->GetName(), szStripPrefix, iPrefixLen) && (int)strlen(pOptEntry->GetName()) > iPrefixLen) { SetEnvVarSpecial("NZBPO", pOptEntry->GetName() + iPrefixLen, pOptEntry->GetValue()); } else if (!szStripPrefix) { SetEnvVarSpecial("NZBOP", pOptEntry->GetName(), pOptEntry->GetValue()); } } g_pOptions->UnlockOptEntries(); } /** * If szStripPrefix is not NULL, only pp-parameters, whose names start with the prefix * are processed. The prefix is then stripped from the names. * If szStripPrefix is NULL, all pp-parameters are processed; without stripping. */ void ScriptController::PrepareEnvParameters(NZBInfo* pNZBInfo, const char* szStripPrefix) { int iPrefixLen = szStripPrefix ? strlen(szStripPrefix) : 0; for (NZBParameterList::iterator it = pNZBInfo->GetParameters()->begin(); it != pNZBInfo->GetParameters()->end(); it++) { NZBParameter* pParameter = *it; const char* szValue = pParameter->GetValue(); #ifdef WIN32 char* szAnsiValue = strdup(szValue); WebUtil::Utf8ToAnsi(szAnsiValue, strlen(szAnsiValue) + 1); szValue = szAnsiValue; #endif if (szStripPrefix && !strncmp(pParameter->GetName(), szStripPrefix, iPrefixLen) && (int)strlen(pParameter->GetName()) > iPrefixLen) { SetEnvVarSpecial("NZBPR", pParameter->GetName() + iPrefixLen, szValue); } else if (!szStripPrefix) { SetEnvVarSpecial("NZBPR", pParameter->GetName(), szValue); } #ifdef WIN32 free(szAnsiValue); #endif } } void ScriptController::SetEnvVarSpecial(const char* szPrefix, const char* szName, const char* szValue) { char szVarname[1024]; snprintf(szVarname, sizeof(szVarname), "%s_%s", szPrefix, szName); szVarname[1024-1] = '\0'; // Original name SetEnvVar(szVarname, szValue); char szNormVarname[1024]; strncpy(szNormVarname, szVarname, sizeof(szVarname)); szNormVarname[1024-1] = '\0'; // Replace special characters with "_" and convert to upper case for (char* szPtr = szNormVarname; *szPtr; szPtr++) { if (strchr(".:*!\"$%&/()=`+~#'{}[]@- ", *szPtr)) *szPtr = '_'; *szPtr = toupper(*szPtr); } // Another env var with normalized name (replaced special chars and converted to upper case) if (strcmp(szVarname, szNormVarname)) { SetEnvVar(szNormVarname, szValue); } } void ScriptController::PrepareArgs() { #ifdef WIN32 if (!m_szArgs) { // Special support for script languages: // automatically find the app registered for this extension and run it const char* szExtension = strrchr(GetScript(), '.'); if (szExtension && strcasecmp(szExtension, ".exe") && strcasecmp(szExtension, ".bat") && strcasecmp(szExtension, ".cmd")) { debug("Looking for associated program for %s", szExtension); char szCommand[512]; int iBufLen = 512-1; if (Util::RegReadStr(HKEY_CLASSES_ROOT, szExtension, NULL, szCommand, &iBufLen)) { szCommand[iBufLen] = '\0'; debug("Extension: %s", szCommand); char szRegPath[512]; snprintf(szRegPath, 512, "%s\\shell\\open\\command", szCommand); szRegPath[512-1] = '\0'; iBufLen = 512-1; if (Util::RegReadStr(HKEY_CLASSES_ROOT, szRegPath, NULL, szCommand, &iBufLen)) { szCommand[iBufLen] = '\0'; debug("Command: %s", szCommand); DWORD_PTR pArgs[] = { (DWORD_PTR)GetScript(), (DWORD_PTR)0 }; if (FormatMessage(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY, szCommand, 0, 0, m_szCmdLine, sizeof(m_szCmdLine), (va_list*)pArgs)) { debug("CmdLine: %s", m_szCmdLine); return; } } } warn("Could not found associated program for %s. Trying to execute %s directly", szExtension, Util::BaseFileName(GetScript())); } } #endif if (!m_szArgs) { m_szStdArgs[0] = GetScript(); m_szStdArgs[1] = NULL; SetArgs(m_szStdArgs, false); } } int ScriptController::Execute() { PrepareEnvOptions(NULL); PrepareArgs(); int iExitCode = 0; int pipein; #ifdef CHILD_WATCHDOG bool bChildConfirmed = false; while (!bChildConfirmed && !m_bTerminated) { #endif #ifdef WIN32 // build command line char* szCmdLine = NULL; if (m_szArgs) { char szCmdLineBuf[2048]; int iUsedLen = 0; for (const char** szArgPtr = m_szArgs; *szArgPtr; szArgPtr++) { snprintf(szCmdLineBuf + iUsedLen, 2048 - iUsedLen, "\"%s\" ", *szArgPtr); iUsedLen += strlen(*szArgPtr) + 3; } szCmdLineBuf[iUsedLen < 2048 ? iUsedLen - 1 : 2048 - 1] = '\0'; szCmdLine = szCmdLineBuf; } else { szCmdLine = m_szCmdLine; } // create pipes to write and read data HANDLE hReadPipe, hWritePipe; SECURITY_ATTRIBUTES SecurityAttributes; memset(&SecurityAttributes, 0, sizeof(SecurityAttributes)); SecurityAttributes.nLength = sizeof(SecurityAttributes); SecurityAttributes.bInheritHandle = TRUE; CreatePipe(&hReadPipe, &hWritePipe, &SecurityAttributes, 0); STARTUPINFO StartupInfo; memset(&StartupInfo, 0, sizeof(StartupInfo)); StartupInfo.cb = sizeof(StartupInfo); StartupInfo.dwFlags = STARTF_USESTDHANDLES; StartupInfo.hStdInput = 0; StartupInfo.hStdOutput = hWritePipe; StartupInfo.hStdError = hWritePipe; PROCESS_INFORMATION ProcessInfo; memset(&ProcessInfo, 0, sizeof(ProcessInfo)); char* szEnvironmentStrings = m_environmentStrings.GetStrings(); BOOL bOK = CreateProcess(NULL, szCmdLine, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW, szEnvironmentStrings, m_szWorkingDir, &StartupInfo, &ProcessInfo); if (!bOK) { DWORD dwErrCode = GetLastError(); char szErrMsg[255]; szErrMsg[255-1] = '\0'; if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwErrCode, 0, szErrMsg, 255, NULL)) { error("Could not start %s: %s", m_szInfoName, szErrMsg); } else { error("Could not start %s: error %i", m_szInfoName, dwErrCode); } if (!Util::FileExists(m_szScript)) { error("Could not find file %s", m_szScript); } free(szEnvironmentStrings); return -1; } free(szEnvironmentStrings); debug("Child Process-ID: %i", (int)ProcessInfo.dwProcessId); m_hProcess = ProcessInfo.hProcess; // close unused "write" end CloseHandle(hWritePipe); pipein = _open_osfhandle((intptr_t)hReadPipe, _O_RDONLY); #else int p[2]; int pipeout; // create the pipe if (pipe(p)) { error("Could not open pipe: errno %i", errno); return -1; } char** pEnvironmentStrings = m_environmentStrings.GetStrings(); pipein = p[0]; pipeout = p[1]; debug("forking"); pid_t pid = fork(); if (pid == -1) { error("Could not start %s: errno %i", m_szInfoName, errno); free(pEnvironmentStrings); return -1; } else if (pid == 0) { // here goes the second instance // create new process group (see Terminate() where it is used) setsid(); // close up the "read" end close(pipein); // make the pipeout to be the same as stdout and stderr dup2(pipeout, 1); dup2(pipeout, 2); close(pipeout); #ifdef CHILD_WATCHDOG fwrite("\n", 1, 1, stdout); fflush(stdout); #endif chdir(m_szWorkingDir); environ = pEnvironmentStrings; execvp(m_szScript, (char* const*)m_szArgs); if (errno == EACCES) { fprintf(stdout, "[WARNING] Fixing permissions for %s\n", m_szScript); fflush(stdout); Util::FixExecPermission(m_szScript); execvp(m_szScript, (char* const*)m_szArgs); } // NOTE: the text "[ERROR] Could not start " is checked later, // by changing adjust the dependent code below. fprintf(stdout, "[ERROR] Could not start %s: %s", m_szScript, strerror(errno)); fflush(stdout); _exit(254); } // continue the first instance debug("forked"); debug("Child Process-ID: %i", (int)pid); free(pEnvironmentStrings); m_hProcess = pid; // close unused "write" end close(pipeout); #endif // open the read end m_pReadpipe = fdopen(pipein, "r"); if (!m_pReadpipe) { error("Could not open pipe to %s", m_szInfoName); return -1; } #ifdef CHILD_WATCHDOG debug("Creating child watchdog"); ChildWatchDog* pWatchDog = new ChildWatchDog(); pWatchDog->SetAutoDestroy(false); pWatchDog->SetProcessID(pid); pWatchDog->Start(); #endif char* buf = (char*)malloc(10240); debug("Entering pipe-loop"); bool bFirstLine = true; bool bStartError = false; while (!m_bTerminated && !m_bDetached && !feof(m_pReadpipe)) { if (ReadLine(buf, 10240, m_pReadpipe) && m_pReadpipe) { #ifdef CHILD_WATCHDOG if (!bChildConfirmed) { bChildConfirmed = true; pWatchDog->Stop(); debug("Child confirmed"); continue; } #endif if (bFirstLine && !strncmp(buf, "[ERROR] Could not start ", 24)) { bStartError = true; } ProcessOutput(buf); bFirstLine = false; } } debug("Exited pipe-loop"); #ifdef CHILD_WATCHDOG debug("Destroying WatchDog"); if (!bChildConfirmed) { pWatchDog->Stop(); } while (pWatchDog->IsRunning()) { usleep(1 * 1000); } delete pWatchDog; #endif free(buf); if (m_pReadpipe) { fclose(m_pReadpipe); } if (m_bTerminated) { warn("Interrupted %s", m_szInfoName); } iExitCode = 0; if (!m_bTerminated && !m_bDetached) { #ifdef WIN32 WaitForSingleObject(m_hProcess, INFINITE); DWORD dExitCode = 0; GetExitCodeProcess(m_hProcess, &dExitCode); iExitCode = dExitCode; #else int iStatus = 0; waitpid(m_hProcess, &iStatus, 0); if (WIFEXITED(iStatus)) { iExitCode = WEXITSTATUS(iStatus); if (iExitCode == 254 && bStartError) { iExitCode = -1; } } #endif } #ifdef CHILD_WATCHDOG } // while (!bChildConfirmed && !m_bTerminated) #endif debug("Exit code %i", iExitCode); return iExitCode; } void ScriptController::Terminate() { debug("Stopping %s", m_szInfoName); m_bTerminated = true; #ifdef WIN32 BOOL bOK = TerminateProcess(m_hProcess, -1); #else pid_t hKillProcess = m_hProcess; if (getpgid(hKillProcess) == hKillProcess) { // if the child process has its own group (setsid() was successful), kill the whole group hKillProcess = -hKillProcess; } bool bOK = kill(hKillProcess, SIGKILL) == 0; #endif if (bOK) { debug("Terminated %s", m_szInfoName); } else { error("Could not terminate %s", m_szInfoName); } debug("Stopped %s", m_szInfoName); } void ScriptController::Detach() { debug("Detaching %s", m_szInfoName); m_bDetached = true; FILE* pReadpipe = m_pReadpipe; m_pReadpipe = NULL; fclose(pReadpipe); } bool ScriptController::ReadLine(char* szBuf, int iBufSize, FILE* pStream) { return fgets(szBuf, iBufSize, pStream); } void ScriptController::ProcessOutput(char* szText) { debug("Processing output received from script"); for (char* pend = szText + strlen(szText) - 1; pend >= szText && (*pend == '\n' || *pend == '\r' || *pend == ' '); pend--) *pend = '\0'; if (szText[0] == '\0') { // skip empty lines return; } if (!strncmp(szText, "[INFO] ", 7)) { PrintMessage(Message::mkInfo, "%s", szText + 7); } else if (!strncmp(szText, "[WARNING] ", 10)) { PrintMessage(Message::mkWarning, "%s", szText + 10); } else if (!strncmp(szText, "[ERROR] ", 8)) { PrintMessage(Message::mkError, "%s", szText + 8); } else if (!strncmp(szText, "[DETAIL] ", 9)) { PrintMessage(Message::mkDetail, "%s", szText + 9); } else if (!strncmp(szText, "[DEBUG] ", 8)) { PrintMessage(Message::mkDebug, "%s", szText + 8); } else { PrintMessage(Message::mkInfo, "%s", szText); } debug("Processing output received from script - completed"); } void ScriptController::AddMessage(Message::EKind eKind, const char* szText) { switch (eKind) { case Message::mkDetail: detail("%s", szText); break; case Message::mkInfo: info("%s", szText); break; case Message::mkWarning: warn("%s", szText); break; case Message::mkError: error("%s", szText); break; case Message::mkDebug: debug("%s", szText); break; } } void ScriptController::PrintMessage(Message::EKind eKind, const char* szFormat, ...) { char tmp2[1024]; va_list ap; va_start(ap, szFormat); vsnprintf(tmp2, 1024, szFormat, ap); tmp2[1024-1] = '\0'; va_end(ap); char tmp3[1024]; if (m_szLogPrefix) { snprintf(tmp3, 1024, "%s: %s", m_szLogPrefix, tmp2); } else { strncpy(tmp3, tmp2, 1024); } tmp3[1024-1] = '\0'; AddMessage(eKind, tmp3); } void PostScriptController::StartJob(PostInfo* pPostInfo) { PostScriptController* pScriptController = new PostScriptController(); pScriptController->m_pPostInfo = pPostInfo; pScriptController->SetWorkingDir(g_pOptions->GetDestDir()); pScriptController->SetAutoDestroy(false); pScriptController->m_iPrefixLen = 0; pPostInfo->SetPostThread(pScriptController); pScriptController->Start(); } void PostScriptController::Run() { FileList activeList; // the locking is needed for accessing the members of NZBInfo g_pDownloadQueueHolder->LockQueue(); for (NZBParameterList::iterator it = m_pPostInfo->GetNZBInfo()->GetParameters()->begin(); it != m_pPostInfo->GetNZBInfo()->GetParameters()->end(); it++) { NZBParameter* pParameter = *it; const char* szVarname = pParameter->GetName(); if (strlen(szVarname) > 0 && szVarname[0] != '*' && szVarname[strlen(szVarname)-1] == ':' && (!strcasecmp(pParameter->GetValue(), "yes") || !strcasecmp(pParameter->GetValue(), "on") || !strcasecmp(pParameter->GetValue(), "1"))) { char* szScriptName = strdup(szVarname); szScriptName[strlen(szScriptName)-1] = '\0'; // remove trailing ':' activeList.push_back(szScriptName); } } m_pPostInfo->GetNZBInfo()->GetScriptStatuses()->Clear(); g_pDownloadQueueHolder->UnlockQueue(); Options::ScriptList scriptList; g_pOptions->LoadScriptList(&scriptList); for (Options::ScriptList::iterator it = scriptList.begin(); it != scriptList.end(); it++) { Options::Script* pScript = *it; for (FileList::iterator it2 = activeList.begin(); it2 != activeList.end(); it2++) { char* szActiveName = *it2; // if any script has requested par-check, do not execute other scripts if (Util::SameFilename(pScript->GetName(), szActiveName) && !m_pPostInfo->GetRequestParCheck()) { ExecuteScript(pScript->GetName(), pScript->GetDisplayName(), pScript->GetLocation()); } } } for (FileList::iterator it = activeList.begin(); it != activeList.end(); it++) { free(*it); } m_pPostInfo->SetStage(PostInfo::ptFinished); m_pPostInfo->SetWorking(false); } void PostScriptController::ExecuteScript(const char* szScriptName, const char* szDisplayName, const char* szLocation) { PrintMessage(Message::mkInfo, "Executing post-process-script %s for %s", szScriptName, m_pPostInfo->GetInfoName()); SetScript(szLocation); SetArgs(NULL, false); char szInfoName[1024]; snprintf(szInfoName, 1024, "post-process-script %s for %s", szScriptName, m_pPostInfo->GetInfoName()); szInfoName[1024-1] = '\0'; SetInfoName(szInfoName); SetLogPrefix(szDisplayName); m_iPrefixLen = strlen(szDisplayName) + 2; // 2 = strlen(": "); PrepareParams(szScriptName); int iExitCode = Execute(); szInfoName[0] = 'P'; // uppercase SetLogPrefix(NULL); ScriptStatus::EStatus eStatus = AnalyseExitCode(iExitCode); // the locking is needed for accessing the members of NZBInfo g_pDownloadQueueHolder->LockQueue(); m_pPostInfo->GetNZBInfo()->GetScriptStatuses()->Add(szScriptName, eStatus); g_pDownloadQueueHolder->UnlockQueue(); } void PostScriptController::PrepareParams(const char* szScriptName) { // the locking is needed for accessing the members of NZBInfo g_pDownloadQueueHolder->LockQueue(); // Reset ResetEnv(); SetEnvVar("NZBPP_NZBNAME", m_pPostInfo->GetNZBInfo()->GetName()); SetEnvVar("NZBPP_DIRECTORY", m_pPostInfo->GetNZBInfo()->GetDestDir()); SetEnvVar("NZBPP_NZBFILENAME", m_pPostInfo->GetNZBInfo()->GetFilename()); SetEnvVar("NZBPP_FINALDIR", m_pPostInfo->GetNZBInfo()->GetFinalDir()); SetEnvVar("NZBPP_CATEGORY", m_pPostInfo->GetNZBInfo()->GetCategory()); SetIntEnvVar("NZBPP_HEALTH", m_pPostInfo->GetNZBInfo()->CalcHealth()); SetIntEnvVar("NZBPP_CRITICALHEALTH", m_pPostInfo->GetNZBInfo()->CalcCriticalHealth()); int iParStatus[] = { 0, 0, 1, 2, 3, 4 }; SetIntEnvVar("NZBPP_PARSTATUS", iParStatus[m_pPostInfo->GetNZBInfo()->GetParStatus()]); int iUnpackStatus[] = { 0, 0, 1, 2, 3, 4 }; SetIntEnvVar("NZBPP_UNPACKSTATUS", iUnpackStatus[m_pPostInfo->GetNZBInfo()->GetUnpackStatus()]); SetIntEnvVar("NZBPP_NZBID", m_pPostInfo->GetNZBInfo()->GetID()); SetIntEnvVar("NZBPP_HEALTHDELETED", (int)m_pPostInfo->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsHealth); SetIntEnvVar("NZBPP_TOTALARTICLES", (int)m_pPostInfo->GetNZBInfo()->GetTotalArticles()); SetIntEnvVar("NZBPP_SUCCESSARTICLES", (int)m_pPostInfo->GetNZBInfo()->GetSuccessArticles()); SetIntEnvVar("NZBPP_FAILEDARTICLES", (int)m_pPostInfo->GetNZBInfo()->GetFailedArticles()); for (ServerStatList::iterator it = m_pPostInfo->GetNZBInfo()->GetServerStats()->begin(); it != m_pPostInfo->GetNZBInfo()->GetServerStats()->end(); it++) { ServerStat* pServerStat = *it; char szName[50]; snprintf(szName, 50, "NZBPP_SERVER%i_SUCCESSARTICLES", pServerStat->GetServerID()); szName[50-1] = '\0'; SetIntEnvVar(szName, pServerStat->GetSuccessArticles()); snprintf(szName, 50, "NZBPP_SERVER%i_FAILEDARTICLES", pServerStat->GetServerID()); szName[50-1] = '\0'; SetIntEnvVar(szName, pServerStat->GetFailedArticles()); } PrepareEnvParameters(m_pPostInfo->GetNZBInfo(), NULL); char szParamPrefix[1024]; snprintf(szParamPrefix, 1024, "%s:", szScriptName); szParamPrefix[1024-1] = '\0'; PrepareEnvParameters(m_pPostInfo->GetNZBInfo(), szParamPrefix); PrepareEnvOptions(szParamPrefix); g_pDownloadQueueHolder->UnlockQueue(); } ScriptStatus::EStatus PostScriptController::AnalyseExitCode(int iExitCode) { // The ScriptStatus is accumulated for all scripts: // If any script has failed the status is "failure", etc. switch (iExitCode) { case POSTPROCESS_SUCCESS: PrintMessage(Message::mkInfo, "%s successful", GetInfoName()); return ScriptStatus::srSuccess; case POSTPROCESS_ERROR: case -1: // Execute() returns -1 if the process could not be started (file not found or other problem) PrintMessage(Message::mkError, "%s failed", GetInfoName()); return ScriptStatus::srFailure; case POSTPROCESS_NONE: PrintMessage(Message::mkInfo, "%s skipped", GetInfoName()); return ScriptStatus::srNone; #ifndef DISABLE_PARCHECK case POSTPROCESS_PARCHECK: if (m_pPostInfo->GetNZBInfo()->GetParStatus() > NZBInfo::psSkipped) { PrintMessage(Message::mkError, "%s requested par-check/repair, but the collection was already checked", GetInfoName()); return ScriptStatus::srFailure; } else { PrintMessage(Message::mkInfo, "%s requested par-check/repair", GetInfoName()); m_pPostInfo->SetRequestParCheck(true); return ScriptStatus::srSuccess; } break; #endif default: PrintMessage(Message::mkError, "%s failed (terminated with unknown status)", GetInfoName()); return ScriptStatus::srFailure; } } void PostScriptController::AddMessage(Message::EKind eKind, const char* szText) { const char* szMsgText = szText + m_iPrefixLen; if (!strncmp(szMsgText, "[NZB] ", 6)) { debug("Command %s detected", szMsgText + 6); if (!strncmp(szMsgText + 6, "FINALDIR=", 9)) { g_pDownloadQueueHolder->LockQueue(); m_pPostInfo->GetNZBInfo()->SetFinalDir(szMsgText + 6 + 9); g_pDownloadQueueHolder->UnlockQueue(); } else if (!strncmp(szMsgText + 6, "NZBPR_", 6)) { char* szParam = strdup(szMsgText + 6 + 6); char* szValue = strchr(szParam, '='); if (szValue) { *szValue = '\0'; g_pDownloadQueueHolder->LockQueue(); m_pPostInfo->GetNZBInfo()->GetParameters()->SetParameter(szParam, szValue + 1); g_pDownloadQueueHolder->UnlockQueue(); } else { error("Invalid command \"%s\" received from %s", szMsgText, GetInfoName()); } free(szParam); } else { error("Invalid command \"%s\" received from %s", szMsgText, GetInfoName()); } } else if (!strncmp(szMsgText, "[HISTORY] ", 10)) { m_pPostInfo->GetNZBInfo()->AppendMessage(eKind, 0, szMsgText); } else { ScriptController::AddMessage(eKind, szText); m_pPostInfo->AppendMessage(eKind, szText); } if (g_pOptions->GetPausePostProcess()) { time_t tStageTime = m_pPostInfo->GetStageTime(); time_t tStartTime = m_pPostInfo->GetStartTime(); time_t tWaitTime = time(NULL); // wait until Post-processor is unpaused while (g_pOptions->GetPausePostProcess() && !IsStopped()) { usleep(100 * 1000); // update time stamps time_t tDelta = time(NULL) - tWaitTime; if (tStageTime > 0) { m_pPostInfo->SetStageTime(tStageTime + tDelta); } if (tStartTime > 0) { m_pPostInfo->SetStartTime(tStartTime + tDelta); } } } } void PostScriptController::Stop() { debug("Stopping post-process-script"); Thread::Stop(); Terminate(); } void NZBScriptController::ExecuteScript(const char* szScript, const char* szNZBFilename, const char* szDirectory, char** pNZBName, char** pCategory, int* iPriority, NZBParameterList* pParameters, bool* bAddTop, bool* bAddPaused) { info("Executing nzb-process-script for %s", Util::BaseFileName(szNZBFilename)); NZBScriptController* pScriptController = new NZBScriptController(); pScriptController->SetScript(szScript); pScriptController->m_pNZBName = pNZBName; pScriptController->m_pCategory = pCategory; pScriptController->m_pParameters = pParameters; pScriptController->m_iPriority = iPriority; pScriptController->m_bAddTop = bAddTop; pScriptController->m_bAddPaused = bAddPaused; char szInfoName[1024]; snprintf(szInfoName, 1024, "nzb-process-script for %s", Util::BaseFileName(szNZBFilename)); szInfoName[1024-1] = '\0'; pScriptController->SetInfoName(szInfoName); pScriptController->SetEnvVar("NZBNP_FILENAME", szNZBFilename); pScriptController->SetEnvVar("NZBNP_NZBNAME", strlen(*pNZBName) > 0 ? *pNZBName : Util::BaseFileName(szNZBFilename)); pScriptController->SetEnvVar("NZBNP_CATEGORY", *pCategory); pScriptController->SetIntEnvVar("NZBNP_PRIORITY", *iPriority); pScriptController->SetIntEnvVar("NZBNP_TOP", *bAddTop ? 1 : 0); pScriptController->SetIntEnvVar("NZBNP_PAUSED", *bAddPaused ? 1 : 0); // remove trailing slash char szDir[1024]; strncpy(szDir, szDirectory, 1024); szDir[1024-1] = '\0'; int iLen = strlen(szDir); if (szDir[iLen-1] == PATH_SEPARATOR) { szDir[iLen-1] = '\0'; } pScriptController->SetEnvVar("NZBNP_DIRECTORY", szDir); char szLogPrefix[1024]; strncpy(szLogPrefix, Util::BaseFileName(szScript), 1024); szLogPrefix[1024-1] = '\0'; if (char* ext = strrchr(szLogPrefix, '.')) *ext = '\0'; // strip file extension pScriptController->SetLogPrefix(szLogPrefix); pScriptController->m_iPrefixLen = strlen(szLogPrefix) + 2; // 2 = strlen(": "); pScriptController->Execute(); delete pScriptController; } void NZBScriptController::AddMessage(Message::EKind eKind, const char* szText) { szText = szText + m_iPrefixLen; if (!strncmp(szText, "[NZB] ", 6)) { debug("Command %s detected", szText + 6); if (!strncmp(szText + 6, "NZBNAME=", 8)) { free(*m_pNZBName); *m_pNZBName = strdup(szText + 6 + 8); } else if (!strncmp(szText + 6, "CATEGORY=", 9)) { free(*m_pCategory); *m_pCategory = strdup(szText + 6 + 9); } else if (!strncmp(szText + 6, "NZBPR_", 6)) { char* szParam = strdup(szText + 6 + 6); char* szValue = strchr(szParam, '='); if (szValue) { *szValue = '\0'; m_pParameters->SetParameter(szParam, szValue + 1); } else { error("Invalid command \"%s\" received from %s", szText, GetInfoName()); } free(szParam); } else if (!strncmp(szText + 6, "PRIORITY=", 9)) { *m_iPriority = atoi(szText + 6 + 9); } else if (!strncmp(szText + 6, "TOP=", 4)) { *m_bAddTop = atoi(szText + 6 + 4) != 0; } else if (!strncmp(szText + 6, "PAUSED=", 7)) { *m_bAddPaused = atoi(szText + 6 + 7) != 0; } else { error("Invalid command \"%s\" received from %s", szText, GetInfoName()); } } else { ScriptController::AddMessage(eKind, szText); } } void NZBAddedScriptController::StartScript(DownloadQueue* pDownloadQueue, NZBInfo *pNZBInfo, const char* szScript) { NZBAddedScriptController* pScriptController = new NZBAddedScriptController(); pScriptController->SetScript(szScript); pScriptController->m_szNZBName = strdup(pNZBInfo->GetName()); pScriptController->SetEnvVar("NZBNA_NZBNAME", pNZBInfo->GetName()); // "NZBNA_NAME" is not correct but kept for compatibility with older versions where this name was used by mistake pScriptController->SetEnvVar("NZBNA_NAME", pNZBInfo->GetName()); pScriptController->SetEnvVar("NZBNA_FILENAME", pNZBInfo->GetFilename()); pScriptController->SetEnvVar("NZBNA_CATEGORY", pNZBInfo->GetCategory()); int iLastID = 0; int iMaxPriority = 0; for (FileQueue::iterator it = pDownloadQueue->GetFileQueue()->begin(); it != pDownloadQueue->GetFileQueue()->end(); it++) { FileInfo* pFileInfo = *it; if (pFileInfo->GetNZBInfo() == pNZBInfo && ( pFileInfo->GetPriority() > iMaxPriority || iLastID == 0)) { iMaxPriority = pFileInfo->GetPriority(); } if (pFileInfo->GetNZBInfo() == pNZBInfo && pFileInfo->GetID() > iLastID) { iLastID = pFileInfo->GetID(); } } pScriptController->SetIntEnvVar("NZBNA_LASTID", iLastID); pScriptController->SetIntEnvVar("NZBNA_PRIORITY", iMaxPriority); pScriptController->PrepareEnvParameters(pNZBInfo, NULL); pScriptController->SetAutoDestroy(true); pScriptController->Start(); } void NZBAddedScriptController::Run() { char szInfoName[1024]; snprintf(szInfoName, 1024, "nzb-added process-script for %s", m_szNZBName); szInfoName[1024-1] = '\0'; SetInfoName(szInfoName); info("Executing %s", szInfoName); char szLogPrefix[1024]; strncpy(szLogPrefix, Util::BaseFileName(GetScript()), 1024); szLogPrefix[1024-1] = '\0'; if (char* ext = strrchr(szLogPrefix, '.')) *ext = '\0'; // strip file extension SetLogPrefix(szLogPrefix); Execute(); free(m_szNZBName); } void SchedulerScriptController::StartScript(const char* szCommandLine) { char** argv = NULL; if (!Util::SplitCommandLine(szCommandLine, &argv)) { error("Could not execute scheduled process-script, failed to parse command line: %s", szCommandLine); return; } info("Executing scheduled process-script %s", Util::BaseFileName(argv[0])); SchedulerScriptController* pScriptController = new SchedulerScriptController(); pScriptController->SetScript(argv[0]); pScriptController->SetArgs((const char**)argv, true); pScriptController->SetAutoDestroy(true); pScriptController->Start(); } void SchedulerScriptController::Run() { char szInfoName[1024]; snprintf(szInfoName, 1024, "scheduled process-script %s", Util::BaseFileName(GetScript())); szInfoName[1024-1] = '\0'; SetInfoName(szInfoName); char szLogPrefix[1024]; strncpy(szLogPrefix, Util::BaseFileName(GetScript()), 1024); szLogPrefix[1024-1] = '\0'; if (char* ext = strrchr(szLogPrefix, '.')) *ext = '\0'; // strip file extension SetLogPrefix(szLogPrefix); Execute(); } nzbget-12.0+dfsg/ScriptController.h000066400000000000000000000113231226450633000173260ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 881 $ * $Date: 2013-10-17 21:35:43 +0200 (Thu, 17 Oct 2013) $ * */ #ifndef SCRIPTCONTROLLER_H #define SCRIPTCONTROLLER_H #include #include "Log.h" #include "Thread.h" #include "DownloadInfo.h" #include "Options.h" class EnvironmentStrings { private: typedef std::vector Strings; Strings m_strings; public: EnvironmentStrings(); ~EnvironmentStrings(); void Clear(); void InitFromCurrentProcess(); void Append(char* szString); #ifdef WIN32 char* GetStrings(); #else char** GetStrings(); #endif }; class ScriptController { private: const char* m_szScript; const char* m_szWorkingDir; const char** m_szArgs; bool m_bFreeArgs; const char* m_szStdArgs[2]; const char* m_szInfoName; const char* m_szLogPrefix; EnvironmentStrings m_environmentStrings; bool m_bTerminated; bool m_bDetached; FILE* m_pReadpipe; #ifdef WIN32 HANDLE m_hProcess; char m_szCmdLine[2048]; #else pid_t m_hProcess; #endif protected: void ProcessOutput(char* szText); virtual bool ReadLine(char* szBuf, int iBufSize, FILE* pStream); void PrintMessage(Message::EKind eKind, const char* szFormat, ...); virtual void AddMessage(Message::EKind eKind, const char* szText); bool GetTerminated() { return m_bTerminated; } void ResetEnv(); void PrepareEnvOptions(const char* szStripPrefix); void PrepareEnvParameters(NZBInfo* pNZBInfo, const char* szStripPrefix); void PrepareArgs(); public: ScriptController(); virtual ~ScriptController(); int Execute(); void Terminate(); void Detach(); void SetScript(const char* szScript) { m_szScript = szScript; } const char* GetScript() { return m_szScript; } void SetWorkingDir(const char* szWorkingDir) { m_szWorkingDir = szWorkingDir; } void SetArgs(const char** szArgs, bool bFreeArgs) { m_szArgs = szArgs; m_bFreeArgs = bFreeArgs; } void SetInfoName(const char* szInfoName) { m_szInfoName = szInfoName; } const char* GetInfoName() { return m_szInfoName; } void SetLogPrefix(const char* szLogPrefix) { m_szLogPrefix = szLogPrefix; } void SetEnvVar(const char* szName, const char* szValue); void SetEnvVarSpecial(const char* szPrefix, const char* szName, const char* szValue); void SetIntEnvVar(const char* szName, int iValue); }; class PostScriptController : public Thread, public ScriptController { private: PostInfo* m_pPostInfo; char m_szNZBName[1024]; int m_iPrefixLen; void ExecuteScript(const char* szScriptName, const char* szDisplayName, const char* szLocation); void PrepareParams(const char* szScriptName); ScriptStatus::EStatus AnalyseExitCode(int iExitCode); typedef std::deque FileList; protected: virtual void AddMessage(Message::EKind eKind, const char* szText); public: virtual void Run(); virtual void Stop(); static void StartJob(PostInfo* pPostInfo); static void InitParamsForNewNZB(NZBInfo* pNZBInfo); }; class NZBScriptController : public ScriptController { private: char** m_pNZBName; char** m_pCategory; int* m_iPriority; NZBParameterList* m_pParameters; bool* m_bAddTop; bool* m_bAddPaused; int m_iPrefixLen; protected: virtual void AddMessage(Message::EKind eKind, const char* szText); public: static void ExecuteScript(const char* szScript, const char* szNZBFilename, const char* szDirectory, char** pNZBName, char** pCategory, int* iPriority, NZBParameterList* pParameters, bool* bAddTop, bool* bAddPaused); }; class NZBAddedScriptController : public Thread, public ScriptController { private: char* m_szNZBName; public: virtual void Run(); static void StartScript(DownloadQueue* pDownloadQueue, NZBInfo *pNZBInfo, const char* szScript); }; class SchedulerScriptController : public Thread, public ScriptController { public: virtual void Run(); static void StartScript(const char* szCommandLine); }; #endif nzbget-12.0+dfsg/ServerPool.cpp000066400000000000000000000220201226450633000164450ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 743 $ * $Date: 2013-07-20 09:15:21 +0200 (Sat, 20 Jul 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #ifndef WIN32 #include #include #endif #include #include "nzbget.h" #include "ServerPool.h" #include "Log.h" static const int CONNECTION_HOLD_SECODNS = 5; ServerPool::PooledConnection::PooledConnection(NewsServer* server) : NNTPConnection(server) { m_bInUse = false; m_tFreeTime = 0; } ServerPool::ServerPool() { debug("Creating ServerPool"); m_iMaxNormLevel = 0; m_iTimeout = 60; m_iGeneration = 0; } ServerPool::~ ServerPool() { debug("Destroying ServerPool"); m_Levels.clear(); for (Servers::iterator it = m_Servers.begin(); it != m_Servers.end(); it++) { delete *it; } m_Servers.clear(); m_SortedServers.clear(); for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++) { delete *it; } m_Connections.clear(); } void ServerPool::AddServer(NewsServer* pNewsServer) { debug("Adding server to ServerPool"); m_Servers.push_back(pNewsServer); m_SortedServers.push_back(pNewsServer); } /* * Calculate normalized levels for all servers. * Normalized Level means: starting from 0 with step 1. * The servers of minimum Level must be always used even if they are not active; * this is to prevent backup servers to act as main servers. **/ void ServerPool::NormalizeLevels() { if (m_Servers.empty()) { return; } std::sort(m_SortedServers.begin(), m_SortedServers.end(), CompareServers); // find minimum level int iMinLevel = m_SortedServers.front()->GetLevel(); for (Servers::iterator it = m_SortedServers.begin(); it != m_SortedServers.end(); it++) { NewsServer* pNewsServer = *it; if (pNewsServer->GetLevel() < iMinLevel) { iMinLevel = pNewsServer->GetLevel(); } } m_iMaxNormLevel = 0; int iLastLevel = iMinLevel; for (Servers::iterator it = m_SortedServers.begin(); it != m_SortedServers.end(); it++) { NewsServer* pNewsServer = *it; if ((pNewsServer->GetActive() && pNewsServer->GetMaxConnections() > 0) || (pNewsServer->GetLevel() == iMinLevel)) { if (pNewsServer->GetLevel() != iLastLevel) { m_iMaxNormLevel++; } pNewsServer->SetNormLevel(m_iMaxNormLevel); iLastLevel = pNewsServer->GetLevel(); } else { pNewsServer->SetNormLevel(-1); } } } bool ServerPool::CompareServers(NewsServer* pServer1, NewsServer* pServer2) { return pServer1->GetLevel() < pServer2->GetLevel(); } void ServerPool::InitConnections() { debug("Initializing connections in ServerPool"); m_mutexConnections.Lock(); NormalizeLevels(); m_Levels.clear(); for (Servers::iterator it = m_SortedServers.begin(); it != m_SortedServers.end(); it++) { NewsServer* pNewsServer = *it; int iNormLevel = pNewsServer->GetNormLevel(); if (pNewsServer->GetNormLevel() > -1) { if ((int)m_Levels.size() <= iNormLevel) { m_Levels.push_back(0); } if (pNewsServer->GetActive()) { int iConnections = 0; for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++) { PooledConnection* pConnection = *it; if (pConnection->GetNewsServer() == pNewsServer) { iConnections++; } } for (int i = iConnections; i < pNewsServer->GetMaxConnections(); i++) { PooledConnection* pConnection = new PooledConnection(pNewsServer); pConnection->SetTimeout(m_iTimeout); m_Connections.push_back(pConnection); iConnections++; } m_Levels[iNormLevel] += iConnections; } } } m_iGeneration++; m_mutexConnections.Unlock(); } NNTPConnection* ServerPool::GetConnection(int iLevel, NewsServer* pWantServer, Servers* pIgnoreServers) { PooledConnection* pConnection = NULL; m_mutexConnections.Lock(); if (iLevel < (int)m_Levels.size() && m_Levels[iLevel] > 0) { for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++) { PooledConnection* pCandidateConnection = *it; NewsServer* pCandidateServer = pCandidateConnection->GetNewsServer(); if (!pCandidateConnection->GetInUse() && pCandidateServer->GetActive() && pCandidateServer->GetNormLevel() == iLevel && (!pWantServer || pCandidateServer == pWantServer || (pWantServer->GetGroup() > 0 && pWantServer->GetGroup() == pCandidateServer->GetGroup()))) { // free connection found, check if it's not from the server which should be ignored bool bUseConnection = true; if (pIgnoreServers && !pWantServer) { for (Servers::iterator it = pIgnoreServers->begin(); it != pIgnoreServers->end(); it++) { NewsServer* pIgnoreServer = *it; if (pIgnoreServer == pCandidateServer || (pIgnoreServer->GetGroup() > 0 && pIgnoreServer->GetGroup() == pCandidateServer->GetGroup() && pIgnoreServer->GetNormLevel() == pCandidateServer->GetNormLevel())) { bUseConnection = false; break; } } } if (bUseConnection) { pConnection = pCandidateConnection; pConnection->SetInUse(true); break; } } } if (pConnection) { m_Levels[iLevel]--; } } m_mutexConnections.Unlock(); return pConnection; } void ServerPool::FreeConnection(NNTPConnection* pConnection, bool bUsed) { if (bUsed) { debug("Freeing used connection"); } m_mutexConnections.Lock(); ((PooledConnection*)pConnection)->SetInUse(false); if (bUsed) { ((PooledConnection*)pConnection)->SetFreeTimeNow(); } if (pConnection->GetNewsServer()->GetNormLevel() > -1 && pConnection->GetNewsServer()->GetActive()) { m_Levels[pConnection->GetNewsServer()->GetNormLevel()]++; } m_mutexConnections.Unlock(); } void ServerPool::CloseUnusedConnections() { m_mutexConnections.Lock(); time_t curtime = ::time(NULL); int i = 0; for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); ) { PooledConnection* pConnection = *it; bool bDeleted = false; if (!pConnection->GetInUse() && (pConnection->GetNewsServer()->GetNormLevel() == -1 || !pConnection->GetNewsServer()->GetActive())) { debug("Closing (and deleting) unused connection to server%i", pConnection->GetNewsServer()->GetID()); if (pConnection->GetStatus() == Connection::csConnected) { pConnection->Disconnect(); } delete pConnection; m_Connections.erase(it); it = m_Connections.begin() + i; bDeleted = true; } if (!bDeleted && !pConnection->GetInUse() && pConnection->GetStatus() == Connection::csConnected) { int tdiff = (int)(curtime - pConnection->GetFreeTime()); if (tdiff > CONNECTION_HOLD_SECODNS) { debug("Closing (and keeping) unused connection to server%i", pConnection->GetNewsServer()->GetID()); pConnection->Disconnect(); } } if (!bDeleted) { it++; i++; } } m_mutexConnections.Unlock(); } void ServerPool::Changed() { debug("Server config has been changed"); InitConnections(); CloseUnusedConnections(); } void ServerPool::LogDebugInfo() { debug(" ServerPool"); debug(" ----------------"); debug(" Max-Level: %i", m_iMaxNormLevel); m_mutexConnections.Lock(); debug(" Servers: %i", m_Servers.size()); for (Servers::iterator it = m_Servers.begin(); it != m_Servers.end(); it++) { NewsServer* pNewsServer = *it; debug(" %i) %s (%s): Level=%i, NormLevel=%i", pNewsServer->GetID(), pNewsServer->GetName(), pNewsServer->GetHost(), pNewsServer->GetLevel(), pNewsServer->GetNormLevel()); } debug(" Levels: %i", m_Levels.size()); int index = 0; for (Levels::iterator it = m_Levels.begin(); it != m_Levels.end(); it++, index++) { int iSize = *it; debug(" %i: Size=%i", index, iSize); } debug(" Connections: %i", m_Connections.size()); for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++) { PooledConnection* pConnection = *it; debug(" %i) %s (%s): Level=%i, NormLevel=%i, InUse:%i", pConnection->GetNewsServer()->GetID(), pConnection->GetNewsServer()->GetName(), pConnection->GetNewsServer()->GetHost(), pConnection->GetNewsServer()->GetLevel(), pConnection->GetNewsServer()->GetNormLevel(), (int)pConnection->GetInUse()); } m_mutexConnections.Unlock(); } nzbget-12.0+dfsg/ServerPool.h000066400000000000000000000050221226450633000161150ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 794 $ * $Date: 2013-08-16 23:53:32 +0200 (Fri, 16 Aug 2013) $ * */ #ifndef SERVERPOOL_H #define SERVERPOOL_H #include #include #include "Thread.h" #include "NewsServer.h" #include "NNTPConnection.h" class ServerPool { private: class PooledConnection : public NNTPConnection { private: bool m_bInUse; time_t m_tFreeTime; public: PooledConnection(NewsServer* server); bool GetInUse() { return m_bInUse; } void SetInUse(bool bInUse) { m_bInUse = bInUse; } time_t GetFreeTime() { return m_tFreeTime; } void SetFreeTimeNow() { m_tFreeTime = ::time(NULL); } }; typedef std::vector Levels; typedef std::vector Connections; Servers m_Servers; Servers m_SortedServers; Connections m_Connections; Levels m_Levels; int m_iMaxNormLevel; Mutex m_mutexConnections; int m_iTimeout; int m_iGeneration; void NormalizeLevels(); static bool CompareServers(NewsServer* pServer1, NewsServer* pServer2); public: ServerPool(); ~ServerPool(); void SetTimeout(int iTimeout) { m_iTimeout = iTimeout; } void AddServer(NewsServer* pNewsServer); void InitConnections(); int GetMaxNormLevel() { return m_iMaxNormLevel; } Servers* GetServers() { return &m_Servers; } // Only for read access (no lockings) NNTPConnection* GetConnection(int iLevel, NewsServer* pWantServer, Servers* pIgnoreServers); void FreeConnection(NNTPConnection* pConnection, bool bUsed); void CloseUnusedConnections(); void Changed(); int GetGeneration() { return m_iGeneration; } void LogDebugInfo(); }; #endif nzbget-12.0+dfsg/TLS.cpp000066400000000000000000000260771226450633000150270ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2008-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 911 $ * $Date: 2013-11-24 20:29:52 +0100 (Sun, 24 Nov 2013) $ * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef WIN32 #define SKIP_DEFAULT_WINDOWS_HEADERS #include "win32.h" #endif #ifndef DISABLE_TLS #include #include #ifdef WIN32 #include #include #else #include #endif #include #include #include #include #include #ifdef WIN32 #include "nzbget.h" #endif #ifdef HAVE_LIBGNUTLS #include #if GNUTLS_VERSION_NUMBER <= 0x020b00 #define NEED_GCRYPT_LOCKING #endif #ifdef NEED_GCRYPT_LOCKING #include #endif /* NEED_GCRYPT_LOCKING */ #endif /* HAVE_LIBGNUTLS */ #ifdef HAVE_OPENSSL #include #include #endif /* HAVE_OPENSSL */ #ifndef WIN32 #include "nzbget.h" #endif #include "TLS.h" #include "Thread.h" #include "Log.h" #ifdef HAVE_LIBGNUTLS #ifdef NEED_GCRYPT_LOCKING /** * Mutexes for gcryptlib */ typedef std::list Mutexes; Mutexes* g_pGCryptLibMutexes; static int gcry_mutex_init(void **priv) { Mutex* pMutex = new Mutex(); g_pGCryptLibMutexes->push_back(pMutex); *priv = pMutex; return 0; } static int gcry_mutex_destroy(void **lock) { Mutex* pMutex = ((Mutex*)*lock); g_pGCryptLibMutexes->remove(pMutex); delete pMutex; return 0; } static int gcry_mutex_lock(void **lock) { ((Mutex*)*lock)->Lock(); return 0; } static int gcry_mutex_unlock(void **lock) { ((Mutex*)*lock)->Unlock(); return 0; } static struct gcry_thread_cbs gcry_threads_Mutex = { GCRY_THREAD_OPTION_USER, NULL, gcry_mutex_init, gcry_mutex_destroy, gcry_mutex_lock, gcry_mutex_unlock, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; #endif /* NEED_GCRYPT_LOCKING */ #endif /* HAVE_LIBGNUTLS */ #ifdef HAVE_OPENSSL /** * Mutexes for OpenSSL */ Mutex* *g_pOpenSSLMutexes; static void openssl_locking(int mode, int n, const char *file, int line) { Mutex* mutex = g_pOpenSSLMutexes[n]; if (mode & CRYPTO_LOCK) { mutex->Lock(); } else { mutex->Unlock(); } } /* static unsigned long openssl_thread_id(void) { #ifdef WIN32 return (unsigned long)GetCurrentThreadId(); #else return (unsigned long)pthread_self(); #endif } */ static struct CRYPTO_dynlock_value* openssl_dynlock_create(const char *file, int line) { return (CRYPTO_dynlock_value*)new Mutex(); } static void openssl_dynlock_destroy(struct CRYPTO_dynlock_value *l, const char *file, int line) { Mutex* mutex = (Mutex*)l; delete mutex; } static void openssl_dynlock_lock(int mode, struct CRYPTO_dynlock_value *l, const char *file, int line) { Mutex* mutex = (Mutex*)l; if (mode & CRYPTO_LOCK) { mutex->Lock(); } else { mutex->Unlock(); } } #endif /* HAVE_OPENSSL */ void TLSSocket::Init() { debug("Initializing TLS library"); #ifdef HAVE_LIBGNUTLS #ifdef NEED_GCRYPT_LOCKING g_pGCryptLibMutexes = new Mutexes(); #endif /* NEED_GCRYPT_LOCKING */ int error_code; #ifdef NEED_GCRYPT_LOCKING error_code = gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_Mutex); if (error_code != 0) { error("Could not initialize libcrypt"); return; } #endif /* NEED_GCRYPT_LOCKING */ error_code = gnutls_global_init(); if (error_code != 0) { error("Could not initialize libgnutls"); return; } #endif /* HAVE_LIBGNUTLS */ #ifdef HAVE_OPENSSL int iMaxMutexes = CRYPTO_num_locks(); g_pOpenSSLMutexes = (Mutex**)malloc(sizeof(Mutex*)*iMaxMutexes); for (int i=0; i < iMaxMutexes; i++) { g_pOpenSSLMutexes[i] = new Mutex(); } SSL_load_error_strings(); SSL_library_init(); OpenSSL_add_all_algorithms(); CRYPTO_set_locking_callback(openssl_locking); //CRYPTO_set_id_callback(openssl_thread_id); CRYPTO_set_dynlock_create_callback(openssl_dynlock_create); CRYPTO_set_dynlock_destroy_callback(openssl_dynlock_destroy); CRYPTO_set_dynlock_lock_callback(openssl_dynlock_lock); #endif /* HAVE_OPENSSL */ } void TLSSocket::Final() { debug("Finalizing TLS library"); #ifdef HAVE_LIBGNUTLS gnutls_global_deinit(); #ifdef NEED_GCRYPT_LOCKING // fixing memory leak in gcryptlib for (Mutexes::iterator it = g_pGCryptLibMutexes->begin(); it != g_pGCryptLibMutexes->end(); it++) { delete *it; } delete g_pGCryptLibMutexes; #endif /* NEED_GCRYPT_LOCKING */ #endif /* HAVE_LIBGNUTLS */ #ifdef HAVE_OPENSSL int iMaxMutexes = CRYPTO_num_locks(); for (int i=0; i < iMaxMutexes; i++) { delete g_pOpenSSLMutexes[i]; } free(g_pOpenSSLMutexes); #endif /* HAVE_OPENSSL */ } TLSSocket::TLSSocket(SOCKET iSocket, bool bIsClient, const char* szCertFile, const char* szKeyFile, const char* szCipher) { m_iSocket = iSocket; m_bIsClient = bIsClient; m_szCertFile = szCertFile ? strdup(szCertFile) : NULL; m_szKeyFile = szKeyFile ? strdup(szKeyFile) : NULL; m_szCipher = szCipher && strlen(szCipher) > 0 ? strdup(szCipher) : NULL; m_pContext = NULL; m_pSession = NULL; m_bSuppressErrors = false; m_bInitialized = false; m_bConnected = false; } TLSSocket::~TLSSocket() { free(m_szCertFile); free(m_szKeyFile); free(m_szCipher); Close(); } void TLSSocket::ReportError(const char* szErrMsg) { #ifdef HAVE_LIBGNUTLS const char* errstr = gnutls_strerror(m_iRetCode); if (m_bSuppressErrors) { debug("%s: %s", szErrMsg, errstr); } else { error("%s: %s", szErrMsg, errstr); } #endif /* HAVE_LIBGNUTLS */ #ifdef HAVE_OPENSSL int errcode; do { errcode = ERR_get_error(); char errstr[1024]; ERR_error_string_n(errcode, errstr, sizeof(errstr)); errstr[1024-1] = '\0'; if (m_bSuppressErrors) { debug("%s: %s", szErrMsg, errstr); } else if (errcode != 0) { error("%s: %s", szErrMsg, errstr); } else { error("%s", szErrMsg); } } while (errcode); #endif /* HAVE_OPENSSL */ } bool TLSSocket::Start() { #ifdef HAVE_LIBGNUTLS gnutls_certificate_credentials_t cred; m_iRetCode = gnutls_certificate_allocate_credentials(&cred); if (m_iRetCode != 0) { ReportError("Could not create TLS context"); return false; } m_pContext = cred; if (m_szCertFile && m_szKeyFile) { m_iRetCode = gnutls_certificate_set_x509_key_file((gnutls_certificate_credentials_t)m_pContext, m_szCertFile, m_szKeyFile, GNUTLS_X509_FMT_PEM); if (m_iRetCode != 0) { ReportError("Could not load certificate or key file"); Close(); return false; } } gnutls_session_t sess; m_iRetCode = gnutls_init(&sess, m_bIsClient ? GNUTLS_CLIENT : GNUTLS_SERVER); if (m_iRetCode != 0) { ReportError("Could not create TLS session"); Close(); return false; } m_pSession = sess; m_bInitialized = true; const char* szPriority = m_szCipher ? m_szCipher : "NORMAL"; m_iRetCode = gnutls_priority_set_direct((gnutls_session_t)m_pSession, szPriority, NULL); if (m_iRetCode != 0) { ReportError("Could not select cipher for TLS session"); Close(); return false; } m_iRetCode = gnutls_credentials_set((gnutls_session_t)m_pSession, GNUTLS_CRD_CERTIFICATE, (gnutls_certificate_credentials_t*)m_pContext); if (m_iRetCode != 0) { ReportError("Could not initialize TLS session"); Close(); return false; } gnutls_transport_set_ptr((gnutls_session_t)m_pSession, (gnutls_transport_ptr_t)(size_t)m_iSocket); m_iRetCode = gnutls_handshake((gnutls_session_t)m_pSession); if (m_iRetCode != 0) { ReportError("TLS handshake failed"); Close(); return false; } m_bConnected = true; return true; #endif /* HAVE_LIBGNUTLS */ #ifdef HAVE_OPENSSL m_pContext = SSL_CTX_new(SSLv23_method()); if (!m_pContext) { ReportError("Could not create TLS context"); return false; } if (m_szCertFile && m_szKeyFile) { if (SSL_CTX_use_certificate_file((SSL_CTX*)m_pContext, m_szCertFile, SSL_FILETYPE_PEM) != 1) { ReportError("Could not load certificate file"); Close(); return false; } if (SSL_CTX_use_PrivateKey_file((SSL_CTX*)m_pContext, m_szKeyFile, SSL_FILETYPE_PEM) != 1) { ReportError("Could not load key file"); Close(); return false; } } m_pSession = SSL_new((SSL_CTX*)m_pContext); if (!m_pSession) { ReportError("Could not create TLS session"); Close(); return false; } if (m_szCipher && !SSL_set_cipher_list((SSL*)m_pSession, m_szCipher)) { ReportError("Could not select cipher for TLS"); Close(); return false; } if (!SSL_set_fd((SSL*)m_pSession, m_iSocket)) { ReportError("Could not set the file descriptor for TLS"); Close(); return false; } int error_code = m_bIsClient ? SSL_connect((SSL*)m_pSession) : SSL_accept((SSL*)m_pSession); if (error_code < 1) { ReportError("TLS handshake failed"); Close(); return false; } m_bConnected = true; return true; #endif /* HAVE_OPENSSL */ } void TLSSocket::Close() { if (m_pSession) { #ifdef HAVE_LIBGNUTLS if (m_bConnected) { gnutls_bye((gnutls_session_t)m_pSession, GNUTLS_SHUT_WR); } if (m_bInitialized) { gnutls_deinit((gnutls_session_t)m_pSession); } #endif /* HAVE_LIBGNUTLS */ #ifdef HAVE_OPENSSL if (m_bConnected) { SSL_shutdown((SSL*)m_pSession); } SSL_free((SSL*)m_pSession); #endif /* HAVE_OPENSSL */ m_pSession = NULL; } if (m_pContext) { #ifdef HAVE_LIBGNUTLS gnutls_certificate_free_credentials((gnutls_certificate_credentials_t)m_pContext); #endif /* HAVE_LIBGNUTLS */ #ifdef HAVE_OPENSSL SSL_CTX_free((SSL_CTX*)m_pContext); #endif /* HAVE_OPENSSL */ m_pContext = NULL; } } int TLSSocket::Send(const char* pBuffer, int iSize) { int ret; #ifdef HAVE_LIBGNUTLS ret = gnutls_record_send((gnutls_session_t)m_pSession, pBuffer, iSize); #endif /* HAVE_LIBGNUTLS */ #ifdef HAVE_OPENSSL ret = SSL_write((SSL*)m_pSession, pBuffer, iSize); #endif /* HAVE_OPENSSL */ if (ret < 0) { #ifdef HAVE_OPENSSL if (ERR_peek_error() == 0) { ReportError("Could not write to TLS-Socket: Connection closed by remote host"); } else #endif /* HAVE_OPENSSL */ ReportError("Could not write to TLS-Socket"); return -1; } return ret; } int TLSSocket::Recv(char* pBuffer, int iSize) { int ret; #ifdef HAVE_LIBGNUTLS ret = gnutls_record_recv((gnutls_session_t)m_pSession, pBuffer, iSize); #endif /* HAVE_LIBGNUTLS */ #ifdef HAVE_OPENSSL ret = SSL_read((SSL*)m_pSession, pBuffer, iSize); #endif /* HAVE_OPENSSL */ if (ret < 0) { #ifdef HAVE_OPENSSL if (ERR_peek_error() == 0) { ReportError("Could not read from TLS-Socket: Connection closed by remote host"); } else #endif /* HAVE_OPENSSL */ { ReportError("Could not read from TLS-Socket"); } return -1; } return ret; } #endif nzbget-12.0+dfsg/TLS.h000066400000000000000000000034371226450633000144670ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2008-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 575 $ * $Date: 2013-03-04 23:01:14 +0100 (Mon, 04 Mar 2013) $ * */ #ifndef TLS_H #define TLS_H #ifndef DISABLE_TLS class TLSSocket { private: bool m_bIsClient; char* m_szCertFile; char* m_szKeyFile; char* m_szCipher; SOCKET m_iSocket; bool m_bSuppressErrors; int m_iRetCode; bool m_bInitialized; bool m_bConnected; // using "void*" to prevent the including of GnuTLS/OpenSSL header files into TLS.h void* m_pContext; void* m_pSession; void ReportError(const char* szErrMsg); public: TLSSocket(SOCKET iSocket, bool bIsClient, const char* szCertFile, const char* szKeyFile, const char* szCipher); ~TLSSocket(); static void Init(); static void Final(); bool Start(); void Close(); int Send(const char* pBuffer, int iSize); int Recv(char* pBuffer, int iSize); void SetSuppressErrors(bool bSuppressErrors) { m_bSuppressErrors = bSuppressErrors; } }; #endif #endif nzbget-12.0+dfsg/Thread.cpp000066400000000000000000000140231226450633000155600ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2010 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 397 $ * $Date: 2011-05-24 14:52:41 +0200 (Tue, 24 May 2011) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #ifdef WIN32 #include #else #include #include #endif #include "Log.h" #include "Thread.h" int Thread::m_iThreadCount = 1; // take the main program thread into account Mutex* Thread::m_pMutexThread; Mutex::Mutex() { #ifdef WIN32 m_pMutexObj = (CRITICAL_SECTION*)malloc(sizeof(CRITICAL_SECTION)); InitializeCriticalSection((CRITICAL_SECTION*)m_pMutexObj); #else m_pMutexObj = (pthread_mutex_t*)malloc(sizeof(pthread_mutex_t)); pthread_mutex_init((pthread_mutex_t*)m_pMutexObj, NULL); #endif } Mutex::~ Mutex() { #ifdef WIN32 DeleteCriticalSection((CRITICAL_SECTION*)m_pMutexObj); #else pthread_mutex_destroy((pthread_mutex_t*)m_pMutexObj); #endif free(m_pMutexObj); } void Mutex::Lock() { #ifdef WIN32 EnterCriticalSection((CRITICAL_SECTION*)m_pMutexObj); #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 (((CRITICAL_SECTION*)m_pMutexObj)->RecursionCount > 1) { error("Internal program error: inconsistent thread-lock detected"); } #endif #else pthread_mutex_lock((pthread_mutex_t*)m_pMutexObj); #endif } void Mutex::Unlock() { #ifdef WIN32 LeaveCriticalSection((CRITICAL_SECTION*)m_pMutexObj); #else pthread_mutex_unlock((pthread_mutex_t*)m_pMutexObj); #endif } #ifdef HAVE_SPINLOCK SpinLock::SpinLock() { #ifdef WIN32 m_pSpinLockObj = (CRITICAL_SECTION *)malloc(sizeof(CRITICAL_SECTION)); InitializeCriticalSectionAndSpinCount((CRITICAL_SECTION *)m_pSpinLockObj, 0x00FFFFFF); #else m_pSpinLockObj = (pthread_spinlock_t *)malloc(sizeof(pthread_spinlock_t)); pthread_spin_init((pthread_spinlock_t *)m_pSpinLockObj, PTHREAD_PROCESS_PRIVATE); #endif } SpinLock::~SpinLock() { #ifdef WIN32 DeleteCriticalSection((CRITICAL_SECTION *)m_pSpinLockObj); #else pthread_spin_destroy((pthread_spinlock_t *)m_pSpinLockObj); #endif free((void*)m_pSpinLockObj); } void SpinLock::Lock() { #ifdef WIN32 EnterCriticalSection((CRITICAL_SECTION *)m_pSpinLockObj); #else pthread_spin_lock((pthread_spinlock_t *)m_pSpinLockObj); #endif } void SpinLock::Unlock() { #ifdef WIN32 LeaveCriticalSection((CRITICAL_SECTION *)m_pSpinLockObj); #else pthread_spin_unlock((pthread_spinlock_t *)m_pSpinLockObj); #endif } #endif void Thread::Init() { debug("Initializing global thread data"); m_pMutexThread = new Mutex(); } void Thread::Final() { debug("Finalizing global thread data"); delete m_pMutexThread; } Thread::Thread() { debug("Creating Thread"); #ifdef WIN32 m_pThreadObj = NULL; #else m_pThreadObj = (pthread_t*)malloc(sizeof(pthread_t)); *((pthread_t*)m_pThreadObj) = 0; #endif m_bRunning = false; m_bStopped = false; m_bAutoDestroy = false; } Thread::~Thread() { debug("Destroying Thread"); #ifndef WIN32 free(m_pThreadObj); #endif } void Thread::Start() { debug("Starting Thread"); m_bRunning = true; // NOTE: we must guarantee, that in a time we set m_bRunning // to value returned from pthread_create, the thread-object still exists. // This is not obviously! // pthread_create could wait long enough before returning result // back to allow the started thread to complete its job and terminate. // We lock mutex m_pMutexThread on calling pthread_create; the started thread // then also try to lock the mutex (see thread_handler) and therefore // must wait until we unlock it m_pMutexThread->Lock(); #ifdef WIN32 m_pThreadObj = (HANDLE)_beginthread(Thread::thread_handler, 0, (void *)this); m_bRunning = m_pThreadObj != NULL; #else pthread_attr_t m_Attr; pthread_attr_init(&m_Attr); pthread_attr_setdetachstate(&m_Attr, PTHREAD_CREATE_DETACHED); pthread_attr_setinheritsched(&m_Attr , PTHREAD_INHERIT_SCHED); m_bRunning = !pthread_create((pthread_t*)m_pThreadObj, &m_Attr, Thread::thread_handler, (void *) this); pthread_attr_destroy(&m_Attr); #endif m_pMutexThread->Unlock(); } void Thread::Stop() { debug("Stopping Thread"); m_bStopped = true; } bool Thread::Kill() { debug("Killing Thread"); m_pMutexThread->Lock(); #ifdef WIN32 bool terminated = TerminateThread((HANDLE)m_pThreadObj, 0) != 0; #else bool terminated = pthread_cancel(*(pthread_t*)m_pThreadObj) == 0; #endif if (terminated) { m_iThreadCount--; } m_pMutexThread->Unlock(); return terminated; } #ifdef WIN32 void __cdecl Thread::thread_handler(void* pObject) #else void* Thread::thread_handler(void* pObject) #endif { m_pMutexThread->Lock(); m_iThreadCount++; m_pMutexThread->Unlock(); debug("Entering Thread-func"); Thread* pThread = (Thread*)pObject; pThread->Run(); debug("Thread-func exited"); pThread->m_bRunning = false; if (pThread->m_bAutoDestroy) { debug("Autodestroying Thread-object"); delete pThread; } m_pMutexThread->Lock(); m_iThreadCount--; m_pMutexThread->Unlock(); #ifndef WIN32 return NULL; #endif } int Thread::GetThreadCount() { m_pMutexThread->Lock(); int iThreadCount = m_iThreadCount; m_pMutexThread->Unlock(); return iThreadCount; } nzbget-12.0+dfsg/Thread.h000066400000000000000000000044011226450633000152240ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2010 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 397 $ * $Date: 2011-05-24 14:52:41 +0200 (Tue, 24 May 2011) $ * */ #ifndef THREAD_H #define THREAD_H class Mutex { private: void* m_pMutexObj; public: Mutex(); ~Mutex(); void Lock(); void Unlock(); }; #ifdef HAVE_SPINLOCK class SpinLock { private: #ifdef WIN32 void* m_pSpinLockObj; #else volatile void* m_pSpinLockObj; #endif public: SpinLock(); ~SpinLock(); void Lock(); void Unlock(); }; #endif class Thread { private: static Mutex* m_pMutexThread; static int m_iThreadCount; void* m_pThreadObj; bool m_bRunning; bool m_bStopped; bool m_bAutoDestroy; #ifdef WIN32 static void __cdecl thread_handler(void* pObject); #else static void *thread_handler(void* pObject); #endif public: Thread(); virtual ~Thread(); static void Init(); static void Final(); virtual void Start(); virtual void Stop(); bool Kill(); bool IsStopped() { return m_bStopped; }; bool IsRunning() const { return m_bRunning; } void SetRunning(bool bOnOff) { m_bRunning = bOnOff; } bool GetAutoDestroy() { return m_bAutoDestroy; } void SetAutoDestroy(bool bAutoDestroy) { m_bAutoDestroy = bAutoDestroy; } static int GetThreadCount(); protected: virtual void Run() {}; // Virtual function - override in derivatives }; #endif nzbget-12.0+dfsg/Unpack.cpp000066400000000000000000000527321226450633000156030ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 927 $ * $Date: 2013-12-25 23:43:58 +0100 (Wed, 25 Dec 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #include #ifndef WIN32 #include #endif #include #include "nzbget.h" #include "Unpack.h" #include "Log.h" #include "Util.h" #include "ParCoordinator.h" extern Options* g_pOptions; extern DownloadQueueHolder* g_pDownloadQueueHolder; void UnpackController::FileList::Clear() { for (iterator it = begin(); it != end(); it++) { free(*it); } clear(); } bool UnpackController::FileList::Exists(const char* szFilename) { for (iterator it = begin(); it != end(); it++) { char* szFilename1 = *it; if (!strcmp(szFilename1, szFilename)) { return true; } } return false; } void UnpackController::StartJob(PostInfo* pPostInfo) { UnpackController* pUnpackController = new UnpackController(); pUnpackController->m_pPostInfo = pPostInfo; pUnpackController->SetAutoDestroy(false); pPostInfo->SetPostThread(pUnpackController); pUnpackController->Start(); } void UnpackController::Run() { // the locking is needed for accessing the members of NZBInfo g_pDownloadQueueHolder->LockQueue(); strncpy(m_szDestDir, m_pPostInfo->GetNZBInfo()->GetDestDir(), 1024); m_szDestDir[1024-1] = '\0'; strncpy(m_szName, m_pPostInfo->GetNZBInfo()->GetName(), 1024); m_szName[1024-1] = '\0'; m_bCleanedUpDisk = false; m_szPassword[0] = '\0'; m_szFinalDir[0] = '\0'; m_bFinalDirCreated = false; NZBParameter* pParameter = m_pPostInfo->GetNZBInfo()->GetParameters()->Find("*Unpack:", false); bool bUnpack = !(pParameter && !strcasecmp(pParameter->GetValue(), "no")); pParameter = m_pPostInfo->GetNZBInfo()->GetParameters()->Find("*Unpack:Password", false); if (pParameter) { strncpy(m_szPassword, pParameter->GetValue(), 1024-1); m_szPassword[1024-1] = '\0'; } g_pDownloadQueueHolder->UnlockQueue(); snprintf(m_szInfoName, 1024, "unpack for %s", m_szName); m_szInfoName[1024-1] = '\0'; snprintf(m_szInfoNameUp, 1024, "Unpack for %s", m_szName); // first letter in upper case m_szInfoNameUp[1024-1] = '\0'; m_bHasParFiles = ParCoordinator::FindMainPars(m_szDestDir, NULL); if (bUnpack) { bool bScanNonStdFiles = m_pPostInfo->GetNZBInfo()->GetRenameStatus() > NZBInfo::rsSkipped || m_pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psSuccess || !m_bHasParFiles; CheckArchiveFiles(bScanNonStdFiles); } if (bUnpack && (m_bHasRarFiles || m_bHasNonStdRarFiles || m_bHasSevenZipFiles || m_bHasSevenZipMultiFiles)) { SetInfoName(m_szInfoName); SetWorkingDir(m_szDestDir); PrintMessage(Message::mkInfo, "Unpacking %s", m_szName); CreateUnpackDir(); m_bUnpackOK = true; m_bUnpackStartError = false; m_bUnpackSpaceError = false; m_bUnpackPasswordError = false; if (m_bHasRarFiles || m_bHasNonStdRarFiles) { ExecuteUnrar(); } if (m_bHasSevenZipFiles && m_bUnpackOK) { ExecuteSevenZip(false); } if (m_bHasSevenZipMultiFiles && m_bUnpackOK) { ExecuteSevenZip(true); } Completed(); } else { PrintMessage(Message::mkInfo, (bUnpack ? "Nothing to unpack for %s" : "Unpack for %s skipped"), m_szName); #ifndef DISABLE_PARCHECK if (bUnpack && m_pPostInfo->GetNZBInfo()->GetParStatus() <= NZBInfo::psSkipped && m_pPostInfo->GetNZBInfo()->GetRenameStatus() <= NZBInfo::rsSkipped && m_bHasParFiles) { RequestParCheck(); } else #endif { m_pPostInfo->GetNZBInfo()->SetUnpackStatus(NZBInfo::usSkipped); m_pPostInfo->SetStage(PostInfo::ptQueued); } } m_pPostInfo->SetWorking(false); } void UnpackController::ExecuteUnrar() { // Format: // unrar x -y -p- -o+ *.rar ./_unpack char szPasswordParam[1024]; const char* szArgs[8]; szArgs[0] = g_pOptions->GetUnrarCmd(); szArgs[1] = "x"; szArgs[2] = "-y"; szArgs[3] = "-p-"; if (strlen(m_szPassword) > 0) { snprintf(szPasswordParam, 1024, "-p%s", m_szPassword); szArgs[3] = szPasswordParam; } szArgs[4] = "-o+"; szArgs[5] = m_bHasNonStdRarFiles ? "*.*" : "*.rar"; szArgs[6] = m_szUnpackDir; szArgs[7] = NULL; SetArgs(szArgs, false); SetScript(g_pOptions->GetUnrarCmd()); SetLogPrefix("Unrar"); m_bAllOKMessageReceived = false; m_eUnpacker = upUnrar; SetProgressLabel(""); int iExitCode = Execute(); SetLogPrefix(NULL); SetProgressLabel(""); m_bUnpackOK = iExitCode == 0 && m_bAllOKMessageReceived && !GetTerminated(); m_bUnpackStartError = iExitCode == -1; m_bUnpackSpaceError = iExitCode == 5; m_bUnpackPasswordError = iExitCode == 11; // only for rar5-archives if (!m_bUnpackOK && iExitCode > 0) { PrintMessage(Message::mkError, "Unrar error code: %i", iExitCode); } } void UnpackController::ExecuteSevenZip(bool bMultiVolumes) { // Format: // 7z x -y -p- -o./_unpack *.7z // OR // 7z x -y -p- -o./_unpack *.7z.001 char szPasswordParam[1024]; const char* szArgs[7]; szArgs[0] = g_pOptions->GetSevenZipCmd(); szArgs[1] = "x"; szArgs[2] = "-y"; szArgs[3] = "-p-"; if (strlen(m_szPassword) > 0) { snprintf(szPasswordParam, 1024, "-p%s", m_szPassword); szArgs[3] = szPasswordParam; } char szUnpackDirParam[1024]; snprintf(szUnpackDirParam, 1024, "-o%s", m_szUnpackDir); szArgs[4] = szUnpackDirParam; szArgs[5] = bMultiVolumes ? "*.7z.001" : "*.7z"; szArgs[6] = NULL; SetArgs(szArgs, false); SetScript(g_pOptions->GetSevenZipCmd()); m_bAllOKMessageReceived = false; m_eUnpacker = upSevenZip; PrintMessage(Message::mkInfo, "Executing 7-Zip"); SetLogPrefix("7-Zip"); SetProgressLabel(""); int iExitCode = Execute(); SetLogPrefix(NULL); SetProgressLabel(""); m_bUnpackOK = iExitCode == 0 && m_bAllOKMessageReceived && !GetTerminated(); m_bUnpackStartError = iExitCode == -1; if (!m_bUnpackOK && iExitCode > 0) { PrintMessage(Message::mkError, "7-Zip error code: %i", iExitCode); } } void UnpackController::Completed() { bool bCleanupSuccess = Cleanup(); if (m_bUnpackOK && bCleanupSuccess) { PrintMessage(Message::mkInfo, "%s %s", m_szInfoNameUp, "successful"); m_pPostInfo->GetNZBInfo()->SetUnpackStatus(NZBInfo::usSuccess); m_pPostInfo->GetNZBInfo()->SetUnpackCleanedUpDisk(m_bCleanedUpDisk); if (g_pOptions->GetParRename()) { //request par-rename check for extracted files m_pPostInfo->GetNZBInfo()->SetRenameStatus(NZBInfo::rsNone); } m_pPostInfo->SetStage(PostInfo::ptQueued); } else { #ifndef DISABLE_PARCHECK if (!m_bUnpackOK && m_pPostInfo->GetNZBInfo()->GetParStatus() <= NZBInfo::psSkipped && !m_bUnpackStartError && !m_bUnpackSpaceError && !m_bUnpackPasswordError && !GetTerminated() && m_bHasParFiles) { RequestParCheck(); } else #endif { PrintMessage(Message::mkError, "%s failed", m_szInfoNameUp); m_pPostInfo->GetNZBInfo()->SetUnpackStatus( m_bUnpackSpaceError ? NZBInfo::usSpace : m_bUnpackPasswordError ? NZBInfo::usPassword : NZBInfo::usFailure); m_pPostInfo->SetStage(PostInfo::ptQueued); } } } #ifndef DISABLE_PARCHECK void UnpackController::RequestParCheck() { PrintMessage(Message::mkInfo, "%s requested par-check/repair", m_szInfoNameUp); m_pPostInfo->SetRequestParCheck(true); m_pPostInfo->SetStage(PostInfo::ptFinished); } #endif void UnpackController::CreateUnpackDir() { m_bInterDir = strlen(g_pOptions->GetInterDir()) > 0 && !strncmp(m_szDestDir, g_pOptions->GetInterDir(), strlen(g_pOptions->GetInterDir())); if (m_bInterDir) { m_pPostInfo->GetNZBInfo()->BuildFinalDirName(m_szFinalDir, 1024); m_szFinalDir[1024-1] = '\0'; snprintf(m_szUnpackDir, 1024, "%s%c%s", m_szFinalDir, PATH_SEPARATOR, "_unpack"); m_bFinalDirCreated = !Util::DirectoryExists(m_szFinalDir); } else { snprintf(m_szUnpackDir, 1024, "%s%c%s", m_szDestDir, PATH_SEPARATOR, "_unpack"); } m_szUnpackDir[1024-1] = '\0'; char szErrBuf[1024]; if (!Util::ForceDirectories(m_szUnpackDir, szErrBuf, sizeof(szErrBuf))) { error("Could not create directory %s: %s", m_szUnpackDir, szErrBuf); } } void UnpackController::CheckArchiveFiles(bool bScanNonStdFiles) { m_bHasRarFiles = false; m_bHasNonStdRarFiles = false; m_bHasSevenZipFiles = false; m_bHasSevenZipMultiFiles = false; RegEx regExRar(".*\\.rar$"); RegEx regExRarMultiSeq(".*\\.(r|s)[0-9][0-9]$"); RegEx regExSevenZip(".*\\.7z$"); RegEx regExSevenZipMulti(".*\\.7z\\.[0-9]+$"); RegEx regExNumExt(".*\\.[0-9]+$"); DirBrowser dir(m_szDestDir); while (const char* filename = dir.Next()) { char szFullFilename[1024]; snprintf(szFullFilename, 1024, "%s%c%s", m_szDestDir, PATH_SEPARATOR, filename); szFullFilename[1024-1] = '\0'; if (strcmp(filename, ".") && strcmp(filename, "..") && !Util::DirectoryExists(szFullFilename)) { if (regExRar.Match(filename)) { m_bHasRarFiles = true; } else if (regExSevenZip.Match(filename)) { m_bHasSevenZipFiles = true; } else if (regExSevenZipMulti.Match(filename)) { m_bHasSevenZipMultiFiles = true; } else if (bScanNonStdFiles && !m_bHasNonStdRarFiles && !regExRarMultiSeq.Match(filename) && regExNumExt.Match(filename) && FileHasRarSignature(szFullFilename)) { m_bHasNonStdRarFiles = true; } } } } bool UnpackController::FileHasRarSignature(const char* szFilename) { char rar4Signature[] = { 0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00 }; char rar5Signature[] = { 0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x01, 0x00 }; char fileSignature[8]; int cnt = 0; FILE* infile; infile = fopen(szFilename, "rb"); if (infile) { cnt = (int)fread(fileSignature, 1, sizeof(fileSignature), infile); fclose(infile); } bool bRar = cnt == sizeof(fileSignature) && (!strcmp(rar4Signature, fileSignature) || !strcmp(rar5Signature, fileSignature)); return bRar; } bool UnpackController::Cleanup() { // By success: // - move unpacked files to destination dir; // - remove _unpack-dir; // - delete archive-files. // By failure: // - remove _unpack-dir. bool bOK = true; FileList extractedFiles; if (m_bUnpackOK) { // moving files back DirBrowser dir(m_szUnpackDir); while (const char* filename = dir.Next()) { if (strcmp(filename, ".") && strcmp(filename, "..") && strcmp(filename, ".AppleDouble") && strcmp(filename, ".DS_Store")) { char szSrcFile[1024]; snprintf(szSrcFile, 1024, "%s%c%s", m_szUnpackDir, PATH_SEPARATOR, filename); szSrcFile[1024-1] = '\0'; char szDstFile[1024]; snprintf(szDstFile, 1024, "%s%c%s", m_szFinalDir[0] != '\0' ? m_szFinalDir : m_szDestDir, PATH_SEPARATOR, filename); szDstFile[1024-1] = '\0'; // silently overwrite existing files remove(szDstFile); if (!Util::MoveFile(szSrcFile, szDstFile)) { PrintMessage(Message::mkError, "Could not move file %s to %s", szSrcFile, szDstFile); bOK = false; } extractedFiles.push_back(strdup(filename)); } } } if (bOK && !Util::DeleteDirectoryWithContent(m_szUnpackDir)) { PrintMessage(Message::mkError, "Could not remove temporary directory %s", m_szUnpackDir); } if (!m_bUnpackOK && m_bFinalDirCreated) { Util::RemoveDirectory(m_szFinalDir); } if (m_bUnpackOK && bOK && g_pOptions->GetUnpackCleanupDisk()) { PrintMessage(Message::mkInfo, "Deleting archive files"); RegEx regExRar(".*\\.rar$"); RegEx regExRarMultiSeq(".*\\.[r-z][0-9][0-9]$"); RegEx regExSevenZip(".*\\.7z$|.*\\.7z\\.[0-9]+$"); RegEx regExNumExt(".*\\.[0-9]+$"); DirBrowser dir(m_szDestDir); while (const char* filename = dir.Next()) { char szFullFilename[1024]; snprintf(szFullFilename, 1024, "%s%c%s", m_szDestDir, PATH_SEPARATOR, filename); szFullFilename[1024-1] = '\0'; if (strcmp(filename, ".") && strcmp(filename, "..") && !Util::DirectoryExists(szFullFilename) && (m_bInterDir || !extractedFiles.Exists(filename)) && (regExRar.Match(filename) || regExSevenZip.Match(filename) || (regExRarMultiSeq.Match(filename) && FileHasRarSignature(szFullFilename)) || (m_bHasNonStdRarFiles && regExNumExt.Match(filename) && FileHasRarSignature(szFullFilename)))) { PrintMessage(Message::mkInfo, "Deleting file %s", filename); if (remove(szFullFilename) != 0) { PrintMessage(Message::mkError, "Could not delete file %s", szFullFilename); } } } m_bCleanedUpDisk = true; } extractedFiles.Clear(); return bOK; } /** * Unrar prints progress information into the same line using backspace control character. * In order to print progress continuously we analyze the output after every char * and update post-job progress information. */ bool UnpackController::ReadLine(char* szBuf, int iBufSize, FILE* pStream) { bool bPrinted = false; int i = 0; for (; i < iBufSize - 1; i++) { int ch = fgetc(pStream); szBuf[i] = ch; szBuf[i+1] = '\0'; if (ch == EOF) { break; } if (ch == '\n') { i++; break; } char* szBackspace = strrchr(szBuf, '\b'); if (szBackspace) { if (!bPrinted) { char tmp[1024]; strncpy(tmp, szBuf, 1024); tmp[1024-1] = '\0'; char* szTmpPercent = strrchr(tmp, '\b'); if (szTmpPercent) { *szTmpPercent = '\0'; } if (strncmp(szBuf, "...", 3)) { ProcessOutput(tmp); } bPrinted = true; } if (strchr(szBackspace, '%')) { int iPercent = atoi(szBackspace + 1); m_pPostInfo->SetStageProgress(iPercent * 10); } } } szBuf[i] = '\0'; if (bPrinted) { szBuf[0] = '\0'; } return i > 0; } void UnpackController::AddMessage(Message::EKind eKind, const char* szText) { char szMsgText[1024]; strncpy(szMsgText, szText, 1024); szMsgText[1024-1] = '\0'; // Modify unrar messages for better readability: // remove the destination path part from message "Extracting file.xxx" if (m_eUnpacker == upUnrar && !strncmp(szText, "Unrar: Extracting ", 19) && !strncmp(szText + 19, m_szUnpackDir, strlen(m_szUnpackDir))) { snprintf(szMsgText, 1024, "Unrar: Extracting %s", szText + 19 + strlen(m_szUnpackDir) + 1); szMsgText[1024-1] = '\0'; } ScriptController::AddMessage(eKind, szMsgText); m_pPostInfo->AppendMessage(eKind, szMsgText); if (m_eUnpacker == upUnrar && !strncmp(szMsgText, "Unrar: UNRAR ", 6) && strstr(szMsgText, " Copyright ") && strstr(szMsgText, " Alexander Roshal")) { // reset start time for a case if user uses unpack-script to do some things // (like sending Wake-On-Lan message) before executing unrar m_pPostInfo->SetStageTime(time(NULL)); } if (m_eUnpacker == upUnrar && !strncmp(szMsgText, "Unrar: Extracting ", 18)) { SetProgressLabel(szMsgText + 7); } if (m_eUnpacker == upUnrar && !strncmp(szText, "Unrar: Extracting from ", 23)) { const char *szFilename = szText + 23; debug("Filename: %s", szFilename); SetProgressLabel(szText + 7); } if ((m_eUnpacker == upUnrar && !strncmp(szText, "Unrar: All OK", 13)) || (m_eUnpacker == upSevenZip && !strncmp(szText, "7-Zip: Everything is Ok", 23))) { m_bAllOKMessageReceived = true; } } void UnpackController::Stop() { debug("Stopping unpack"); Thread::Stop(); Terminate(); } void UnpackController::SetProgressLabel(const char* szProgressLabel) { g_pDownloadQueueHolder->LockQueue(); m_pPostInfo->SetProgressLabel(szProgressLabel); g_pDownloadQueueHolder->UnlockQueue(); } void MoveController::StartJob(PostInfo* pPostInfo) { MoveController* pMoveController = new MoveController(); pMoveController->m_pPostInfo = pPostInfo; pMoveController->SetAutoDestroy(false); pPostInfo->SetPostThread(pMoveController); pMoveController->Start(); } void MoveController::Run() { // the locking is needed for accessing the members of NZBInfo g_pDownloadQueueHolder->LockQueue(); char szNZBName[1024]; strncpy(szNZBName, m_pPostInfo->GetNZBInfo()->GetName(), 1024); szNZBName[1024-1] = '\0'; char szInfoName[1024]; snprintf(szInfoName, 1024, "move for %s", m_pPostInfo->GetNZBInfo()->GetName()); szInfoName[1024-1] = '\0'; SetInfoName(szInfoName); strncpy(m_szInterDir, m_pPostInfo->GetNZBInfo()->GetDestDir(), 1024); m_szInterDir[1024-1] = '\0'; m_pPostInfo->GetNZBInfo()->BuildFinalDirName(m_szDestDir, 1024); m_szDestDir[1024-1] = '\0'; g_pDownloadQueueHolder->UnlockQueue(); info("Moving completed files for %s", szNZBName); bool bOK = MoveFiles(); szInfoName[0] = 'M'; // uppercase if (bOK) { info("%s successful", szInfoName); // save new dest dir g_pDownloadQueueHolder->LockQueue(); m_pPostInfo->GetNZBInfo()->SetDestDir(m_szDestDir); m_pPostInfo->GetNZBInfo()->SetMoveStatus(NZBInfo::msSuccess); g_pDownloadQueueHolder->UnlockQueue(); } else { error("%s failed", szInfoName); m_pPostInfo->GetNZBInfo()->SetMoveStatus(NZBInfo::msFailure); } m_pPostInfo->SetStage(PostInfo::ptQueued); m_pPostInfo->SetWorking(false); } bool MoveController::MoveFiles() { char szErrBuf[1024]; if (!Util::ForceDirectories(m_szDestDir, szErrBuf, sizeof(szErrBuf))) { error("Could not create directory %s: %s", m_szDestDir, szErrBuf); return false; } bool bOK = true; DirBrowser dir(m_szInterDir); while (const char* filename = dir.Next()) { if (strcmp(filename, ".") && strcmp(filename, "..") && strcmp(filename, ".AppleDouble") && strcmp(filename, ".DS_Store")) { char szSrcFile[1024]; snprintf(szSrcFile, 1024, "%s%c%s", m_szInterDir, PATH_SEPARATOR, filename); szSrcFile[1024-1] = '\0'; char szDstFile[1024]; Util::MakeUniqueFilename(szDstFile, 1024, m_szDestDir, filename); PrintMessage(Message::mkInfo, "Moving file %s to %s", Util::BaseFileName(szSrcFile), m_szDestDir); if (!Util::MoveFile(szSrcFile, szDstFile)) { PrintMessage(Message::mkError, "Could not move file %s to %s! Errcode: %i", szSrcFile, szDstFile, errno); bOK = false; } } } if (bOK && !Util::DeleteDirectoryWithContent(m_szInterDir)) { PrintMessage(Message::mkError, "Could not remove intermediate directory %s", m_szInterDir); } return bOK; } void CleanupController::StartJob(PostInfo* pPostInfo) { CleanupController* pCleanupController = new CleanupController(); pCleanupController->m_pPostInfo = pPostInfo; pCleanupController->SetAutoDestroy(false); pPostInfo->SetPostThread(pCleanupController); pCleanupController->Start(); } void CleanupController::Run() { // the locking is needed for accessing the members of NZBInfo g_pDownloadQueueHolder->LockQueue(); char szNZBName[1024]; strncpy(szNZBName, m_pPostInfo->GetNZBInfo()->GetName(), 1024); szNZBName[1024-1] = '\0'; char szInfoName[1024]; snprintf(szInfoName, 1024, "cleanup for %s", m_pPostInfo->GetNZBInfo()->GetName()); szInfoName[1024-1] = '\0'; SetInfoName(szInfoName); strncpy(m_szDestDir, m_pPostInfo->GetNZBInfo()->GetDestDir(), 1024); m_szDestDir[1024-1] = '\0'; bool bInterDir = strlen(g_pOptions->GetInterDir()) > 0 && !strncmp(m_szDestDir, g_pOptions->GetInterDir(), strlen(g_pOptions->GetInterDir())); if (bInterDir) { m_pPostInfo->GetNZBInfo()->BuildFinalDirName(m_szFinalDir, 1024); m_szFinalDir[1024-1] = '\0'; } else { m_szFinalDir[0] = '\0'; } g_pDownloadQueueHolder->UnlockQueue(); info("Cleaning up %s", szNZBName); bool bDeleted = false; bool bOK = Cleanup(m_szDestDir, &bDeleted); if (bOK && m_szFinalDir[0] != '\0') { bool bDeleted2 = false; bOK = Cleanup(m_szFinalDir, &bDeleted2); bDeleted = bDeleted || bDeleted2; } szInfoName[0] = 'C'; // uppercase if (bOK && bDeleted) { info("%s successful", szInfoName); m_pPostInfo->GetNZBInfo()->SetCleanupStatus(NZBInfo::csSuccess); } else if (bOK) { info("Nothing to cleanup for %s", szNZBName); m_pPostInfo->GetNZBInfo()->SetCleanupStatus(NZBInfo::csSuccess); } else { error("%s failed", szInfoName); m_pPostInfo->GetNZBInfo()->SetCleanupStatus(NZBInfo::csFailure); } m_pPostInfo->SetStage(PostInfo::ptQueued); m_pPostInfo->SetWorking(false); } bool CleanupController::Cleanup(const char* szDestDir, bool *bDeleted) { *bDeleted = false; bool bOK = true; ExtList extList; // split ExtCleanupDisk into tokens and create a list char* szExtCleanupDisk = strdup(g_pOptions->GetExtCleanupDisk()); char* saveptr; char* szExt = strtok_r(szExtCleanupDisk, ",; ", &saveptr); while (szExt) { extList.push_back(szExt); szExt = strtok_r(NULL, ",; ", &saveptr); } DirBrowser dir(szDestDir); while (const char* filename = dir.Next()) { // check file extension int iFilenameLen = strlen(filename); bool bDeleteIt = false; for (ExtList::iterator it = extList.begin(); it != extList.end(); it++) { const char* szExt = *it; int iExtLen = strlen(szExt); if (iFilenameLen >= iExtLen && !strcasecmp(szExt, filename + iFilenameLen - iExtLen)) { bDeleteIt = true; break; } } if (bDeleteIt) { char szFullFilename[1024]; snprintf(szFullFilename, 1024, "%s%c%s", szDestDir, PATH_SEPARATOR, filename); szFullFilename[1024-1] = '\0'; PrintMessage(Message::mkInfo, "Deleting file %s", filename); if (remove(szFullFilename) != 0) { PrintMessage(Message::mkError, "Could not delete file %s! Errcode: %i", szFullFilename, errno); bOK = false; } *bDeleted = true; } } free(szExtCleanupDisk); return bOK; } nzbget-12.0+dfsg/Unpack.h000066400000000000000000000062541226450633000152460ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 905 $ * $Date: 2013-11-08 22:54:44 +0100 (Fri, 08 Nov 2013) $ * */ #ifndef UNPACK_H #define UNPACK_H #include #include "Log.h" #include "Thread.h" #include "DownloadInfo.h" #include "ScriptController.h" class UnpackController : public Thread, public ScriptController { private: enum EUnpacker { upUnrar, upSevenZip }; typedef std::deque FileListBase; class FileList : public FileListBase { public: void Clear(); bool Exists(const char* szFilename); }; private: PostInfo* m_pPostInfo; char m_szName[1024]; char m_szInfoName[1024]; char m_szInfoNameUp[1024]; char m_szDestDir[1024]; char m_szFinalDir[1024]; char m_szUnpackDir[1024]; char m_szPassword[1024]; bool m_bInterDir; bool m_bAllOKMessageReceived; bool m_bNoFilesMessageReceived; bool m_bHasParFiles; bool m_bHasRarFiles; bool m_bHasNonStdRarFiles; bool m_bHasSevenZipFiles; bool m_bHasSevenZipMultiFiles; bool m_bUnpackOK; bool m_bUnpackStartError; bool m_bUnpackSpaceError; bool m_bUnpackPasswordError; bool m_bCleanedUpDisk; EUnpacker m_eUnpacker; bool m_bFinalDirCreated; protected: virtual bool ReadLine(char* szBuf, int iBufSize, FILE* pStream); virtual void AddMessage(Message::EKind eKind, const char* szText); void ExecuteUnrar(); void ExecuteSevenZip(bool bMultiVolumes); void Completed(); void CreateUnpackDir(); bool Cleanup(); void CheckArchiveFiles(bool bScanNonStdFiles); void SetProgressLabel(const char* szProgressLabel); #ifndef DISABLE_PARCHECK void RequestParCheck(); #endif bool FileHasRarSignature(const char* szFilename); public: virtual void Run(); virtual void Stop(); static void StartJob(PostInfo* pPostInfo); }; class MoveController : public Thread, public ScriptController { private: PostInfo* m_pPostInfo; char m_szInterDir[1024]; char m_szDestDir[1024]; bool MoveFiles(); public: virtual void Run(); static void StartJob(PostInfo* pPostInfo); }; class CleanupController : public Thread, public ScriptController { private: PostInfo* m_pPostInfo; char m_szDestDir[1024]; char m_szFinalDir[1024]; bool Cleanup(const char* szDestDir, bool *bDeleted); typedef std::deque ExtList; public: virtual void Run(); static void StartJob(PostInfo* pPostInfo); }; #endif nzbget-12.0+dfsg/UrlCoordinator.cpp000066400000000000000000000266611226450633000173320ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2012-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 911 $ * $Date: 2013-11-24 20:29:52 +0100 (Sun, 24 Nov 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #include #ifndef WIN32 #include #include #endif #include "nzbget.h" #include "UrlCoordinator.h" #include "Options.h" #include "WebDownloader.h" #include "DiskState.h" #include "Log.h" #include "Util.h" #include "NZBFile.h" #include "QueueCoordinator.h" #include "Scanner.h" extern Options* g_pOptions; extern DiskState* g_pDiskState; extern QueueCoordinator* g_pQueueCoordinator; extern Scanner* g_pScanner; UrlDownloader::UrlDownloader() : WebDownloader() { m_szCategory = NULL; } UrlDownloader::~UrlDownloader() { free(m_szCategory); } void UrlDownloader::ProcessHeader(const char* szLine) { WebDownloader::ProcessHeader(szLine); if (!strncmp(szLine, "X-DNZB-Category:", 16)) { free(m_szCategory); char* szCategory = strdup(szLine + 16); m_szCategory = strdup(Util::Trim(szCategory)); free(szCategory); debug("Category: %s", m_szCategory); } else if (!strncmp(szLine, "X-DNZB-", 7)) { char* szModLine = strdup(szLine); char* szValue = strchr(szModLine, ':'); if (szValue) { *szValue = NULL; szValue++; while (*szValue == ' ') szValue++; Util::Trim(szValue); debug("X-DNZB: %s", szModLine); debug("Value: %s", szValue); char szParamName[100]; snprintf(szParamName, 100, "*DNZB:%s", szModLine + 7); szParamName[100-1] = '\0'; char* szVal = WebUtil::Latin1ToUtf8(szValue); m_ppParameters.SetParameter(szParamName, szVal); free(szVal); } free(szModLine); } } UrlCoordinator::UrlCoordinator() { debug("Creating UrlCoordinator"); m_bHasMoreJobs = true; m_bForce = false; } UrlCoordinator::~UrlCoordinator() { debug("Destroying UrlCoordinator"); // Cleanup debug("Deleting UrlDownloaders"); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { delete *it; } m_ActiveDownloads.clear(); DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue(); for (UrlQueue::iterator it = pDownloadQueue->GetUrlQueue()->begin(); it != pDownloadQueue->GetUrlQueue()->end(); it++) { delete *it; } pDownloadQueue->GetUrlQueue()->clear(); g_pQueueCoordinator->UnlockQueue(); debug("UrlCoordinator destroyed"); } void UrlCoordinator::Run() { debug("Entering UrlCoordinator-loop"); int iResetCounter = 0; while (!IsStopped()) { if (!(g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2()) || m_bForce || g_pOptions->GetUrlForce()) { // start download for next URL DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue(); if ((int)m_ActiveDownloads.size() < g_pOptions->GetUrlConnections()) { UrlInfo* pUrlInfo; bool bHasMoreUrls = GetNextUrl(pDownloadQueue, pUrlInfo); bool bUrlDownloadsRunning = !m_ActiveDownloads.empty(); m_bHasMoreJobs = bHasMoreUrls || bUrlDownloadsRunning; if (bHasMoreUrls && !IsStopped()) { StartUrlDownload(pUrlInfo); } if (!bHasMoreUrls) { m_bForce = false; } } g_pQueueCoordinator->UnlockQueue(); } int iSleepInterval = 100; usleep(iSleepInterval * 1000); iResetCounter += iSleepInterval; if (iResetCounter >= 1000) { // this code should not be called too often, once per second is OK ResetHangingDownloads(); iResetCounter = 0; } } // waiting for downloads debug("UrlCoordinator: waiting for Downloads to complete"); bool completed = false; while (!completed) { g_pQueueCoordinator->LockQueue(); completed = m_ActiveDownloads.size() == 0; g_pQueueCoordinator->UnlockQueue(); usleep(100 * 1000); ResetHangingDownloads(); } debug("UrlCoordinator: Downloads are completed"); debug("Exiting UrlCoordinator-loop"); } void UrlCoordinator::Stop() { Thread::Stop(); debug("Stopping UrlDownloads"); g_pQueueCoordinator->LockQueue(); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { (*it)->Stop(); } g_pQueueCoordinator->UnlockQueue(); debug("UrlDownloads are notified"); } void UrlCoordinator::ResetHangingDownloads() { const int TimeOut = g_pOptions->GetTerminateTimeout(); if (TimeOut == 0) { return; } g_pQueueCoordinator->LockQueue(); time_t tm = ::time(NULL); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end();) { UrlDownloader* pUrlDownloader = *it; if (tm - pUrlDownloader->GetLastUpdateTime() > TimeOut && pUrlDownloader->GetStatus() == UrlDownloader::adRunning) { UrlInfo* pUrlInfo = pUrlDownloader->GetUrlInfo(); debug("Terminating hanging download %s", pUrlDownloader->GetInfoName()); if (pUrlDownloader->Terminate()) { error("Terminated hanging download %s", pUrlDownloader->GetInfoName()); pUrlInfo->SetStatus(UrlInfo::aiUndefined); } else { error("Could not terminate hanging download %s", pUrlDownloader->GetInfoName()); } m_ActiveDownloads.erase(it); // it's not safe to destroy pUrlDownloader, because the state of object is unknown delete pUrlDownloader; it = m_ActiveDownloads.begin(); continue; } it++; } g_pQueueCoordinator->UnlockQueue(); } void UrlCoordinator::LogDebugInfo() { debug(" UrlCoordinator"); debug(" ----------------"); g_pQueueCoordinator->LockQueue(); debug(" Active Downloads: %i", m_ActiveDownloads.size()); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { UrlDownloader* pUrlDownloader = *it; pUrlDownloader->LogDebugInfo(); } g_pQueueCoordinator->UnlockQueue(); } void UrlCoordinator::AddUrlToQueue(UrlInfo* pUrlInfo, bool AddFirst) { debug("Adding NZB-URL to queue"); DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue(); pDownloadQueue->GetUrlQueue()->push_back(pUrlInfo); if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState->SaveDownloadQueue(pDownloadQueue); } if (pUrlInfo->GetForce()) { m_bForce = true; } g_pQueueCoordinator->UnlockQueue(); } /* * Returns next URL for download. */ bool UrlCoordinator::GetNextUrl(DownloadQueue* pDownloadQueue, UrlInfo* &pUrlInfo) { bool bPauseDownload = g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2(); for (UrlQueue::iterator at = pDownloadQueue->GetUrlQueue()->begin(); at != pDownloadQueue->GetUrlQueue()->end(); at++) { pUrlInfo = *at; if (pUrlInfo->GetStatus() == 0 && (!bPauseDownload || pUrlInfo->GetForce() || g_pOptions->GetUrlForce())) { return true; break; } } return false; } void UrlCoordinator::StartUrlDownload(UrlInfo* pUrlInfo) { debug("Starting new UrlDownloader"); UrlDownloader* pUrlDownloader = new UrlDownloader(); pUrlDownloader->SetAutoDestroy(true); pUrlDownloader->Attach(this); pUrlDownloader->SetUrlInfo(pUrlInfo); pUrlDownloader->SetURL(pUrlInfo->GetURL()); pUrlDownloader->SetForce(pUrlInfo->GetForce() || g_pOptions->GetUrlForce()); char tmp[1024]; pUrlInfo->GetName(tmp, 1024); pUrlDownloader->SetInfoName(tmp); snprintf(tmp, 1024, "%surl-%i.tmp", g_pOptions->GetTempDir(), pUrlInfo->GetID()); tmp[1024-1] = '\0'; pUrlDownloader->SetOutputFilename(tmp); pUrlInfo->SetStatus(UrlInfo::aiRunning); m_ActiveDownloads.push_back(pUrlDownloader); pUrlDownloader->Start(); } void UrlCoordinator::Update(Subject* pCaller, void* pAspect) { debug("Notification from UrlDownloader received"); UrlDownloader* pUrlDownloader = (UrlDownloader*) pCaller; if ((pUrlDownloader->GetStatus() == WebDownloader::adFinished) || (pUrlDownloader->GetStatus() == WebDownloader::adFailed) || (pUrlDownloader->GetStatus() == WebDownloader::adRetry)) { UrlCompleted(pUrlDownloader); } } void UrlCoordinator::UrlCompleted(UrlDownloader* pUrlDownloader) { debug("URL downloaded"); UrlInfo* pUrlInfo = pUrlDownloader->GetUrlInfo(); if (pUrlDownloader->GetStatus() == WebDownloader::adFinished) { pUrlInfo->SetStatus(UrlInfo::aiFinished); } else if (pUrlDownloader->GetStatus() == WebDownloader::adFailed) { pUrlInfo->SetStatus(UrlInfo::aiFailed); } else if (pUrlDownloader->GetStatus() == WebDownloader::adRetry) { pUrlInfo->SetStatus(UrlInfo::aiUndefined); } char filename[1024]; if (pUrlDownloader->GetOriginalFilename()) { strncpy(filename, pUrlDownloader->GetOriginalFilename(), 1024); filename[1024-1] = '\0'; } else { strncpy(filename, Util::BaseFileName(pUrlInfo->GetURL()), 1024); filename[1024-1] = '\0'; // TODO: decode URL escaping } Util::MakeValidFilename(filename, '_', false); debug("Filename: [%s]", filename); // delete Download from active jobs g_pQueueCoordinator->LockQueue(); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { UrlDownloader* pa = *it; if (pa == pUrlDownloader) { m_ActiveDownloads.erase(it); break; } } g_pQueueCoordinator->UnlockQueue(); Aspect aspect = { eaUrlCompleted, pUrlInfo }; Notify(&aspect); if (pUrlInfo->GetStatus() == UrlInfo::aiFinished) { // add nzb-file to download queue Scanner::EAddStatus eAddStatus = g_pScanner->AddExternalFile( pUrlInfo->GetNZBFilename() && strlen(pUrlInfo->GetNZBFilename()) > 0 ? pUrlInfo->GetNZBFilename() : filename, strlen(pUrlInfo->GetCategory()) > 0 ? pUrlInfo->GetCategory() : pUrlDownloader->GetCategory(), pUrlInfo->GetPriority(), pUrlInfo->GetDupeKey(), pUrlInfo->GetDupeScore(), pUrlInfo->GetDupeMode(), pUrlDownloader->GetParameters(), pUrlInfo->GetAddTop(), pUrlInfo->GetAddPaused(), pUrlDownloader->GetOutputFilename(), NULL, 0); if (eAddStatus != Scanner::asSuccess) { pUrlInfo->SetStatus(eAddStatus == Scanner::asFailed ? UrlInfo::aiScanFailed : UrlInfo::aiScanSkipped); } } // delete Download from Url Queue if (pUrlInfo->GetStatus() != UrlInfo::aiRetry) { DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue(); for (UrlQueue::iterator it = pDownloadQueue->GetUrlQueue()->begin(); it != pDownloadQueue->GetUrlQueue()->end(); it++) { UrlInfo* pa = *it; if (pa == pUrlInfo) { pDownloadQueue->GetUrlQueue()->erase(it); break; } } bool bDeleteObj = true; if (g_pOptions->GetKeepHistory() > 0 && pUrlInfo->GetStatus() != UrlInfo::aiFinished) { HistoryInfo* pHistoryInfo = new HistoryInfo(pUrlInfo); pHistoryInfo->SetTime(time(NULL)); pDownloadQueue->GetHistoryList()->push_front(pHistoryInfo); bDeleteObj = false; } if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState->SaveDownloadQueue(pDownloadQueue); } g_pQueueCoordinator->UnlockQueue(); if (bDeleteObj) { delete pUrlInfo; } } } nzbget-12.0+dfsg/UrlCoordinator.h000066400000000000000000000050131226450633000167630ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2012-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 749 $ * $Date: 2013-07-23 23:21:14 +0200 (Tue, 23 Jul 2013) $ * */ #ifndef URLCOORDINATOR_H #define URLCOORDINATOR_H #include #include #include #include "Thread.h" #include "WebDownloader.h" #include "DownloadInfo.h" #include "Observer.h" class UrlDownloader; class UrlCoordinator : public Thread, public Observer, public Subject { public: typedef std::list ActiveDownloads; enum EAspectAction { eaUrlAdded, eaUrlCompleted }; struct Aspect { EAspectAction eAction; UrlInfo* pUrlInfo; }; private: ActiveDownloads m_ActiveDownloads; bool m_bHasMoreJobs; bool m_bForce; bool GetNextUrl(DownloadQueue* pDownloadQueue, UrlInfo* &pUrlInfo); void StartUrlDownload(UrlInfo* pUrlInfo); void UrlCompleted(UrlDownloader* pUrlDownloader); void ResetHangingDownloads(); public: UrlCoordinator(); virtual ~UrlCoordinator(); virtual void Run(); virtual void Stop(); void Update(Subject* pCaller, void* pAspect); // Editing the queue void AddUrlToQueue(UrlInfo* pUrlInfo, bool AddFirst); bool HasMoreJobs() { return m_bHasMoreJobs; } void LogDebugInfo(); }; class UrlDownloader : public WebDownloader { private: UrlInfo* m_pUrlInfo; char* m_szCategory; NZBParameterList m_ppParameters; protected: virtual void ProcessHeader(const char* szLine); public: UrlDownloader(); ~UrlDownloader(); void SetUrlInfo(UrlInfo* pUrlInfo) { m_pUrlInfo = pUrlInfo; } UrlInfo* GetUrlInfo() { return m_pUrlInfo; } const char* GetCategory() { return m_szCategory; } NZBParameterList* GetParameters() { return &m_ppParameters; } }; #endif nzbget-12.0+dfsg/Util.cpp000066400000000000000000001362251226450633000152770ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 923 $ * $Date: 2013-12-19 22:28:45 +0100 (Thu, 19 Dec 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #include #include #include #ifdef WIN32 #include #include #else #include #include #include #endif #ifdef HAVE_REGEX_H #include #endif #ifndef DISABLE_GZIP #include #endif #include #include "nzbget.h" #include "Util.h" #ifndef WIN32 // function "svn_version" is automatically generated in file "svn_version.cpp" on each build const char* svn_version(void); #endif #ifdef WIN32 // getopt for WIN32: // from http://www.codeproject.com/cpp/xgetopt.asp // Original Author: Hans Dietrich (hdietrich2@hotmail.com) // Released to public domain from author (thanks) // Slightly modified by Andrey Prygunkov char *optarg; // global argument pointer int optind = 0; // global argv index int getopt(int argc, char *argv[], char *optstring) { static char *next = NULL; if (optind == 0) next = NULL; optarg = NULL; if (next == NULL || *next == '\0') { if (optind == 0) optind++; if (optind >= argc || argv[optind][0] != '-' || argv[optind][1] == '\0') { optarg = NULL; if (optind < argc) optarg = argv[optind]; return -1; } if (strcmp(argv[optind], "--") == 0) { optind++; optarg = NULL; if (optind < argc) optarg = argv[optind]; return -1; } next = argv[optind]; next++; // skip past - optind++; } char c = *next++; char *cp = strchr(optstring, c); if (cp == NULL || c == ':') { fprintf(stderr, "Invalid option %c", c); return '?'; } cp++; if (*cp == ':') { if (*next != '\0') { optarg = next; next = NULL; } else if (optind < argc) { optarg = argv[optind]; optind++; } else { fprintf(stderr, "Option %c needs an argument", c); return '?'; } } return c; } DirBrowser::DirBrowser(const char* szPath) { char szMask[MAX_PATH + 1]; snprintf(szMask, MAX_PATH + 1, "%s%c*.*", szPath, (int)PATH_SEPARATOR); szMask[MAX_PATH] = '\0'; m_hFile = _findfirst(szMask, &m_FindData); m_bFirst = true; } DirBrowser::~DirBrowser() { if (m_hFile != -1L) { _findclose(m_hFile); } } const char* DirBrowser::Next() { bool bOK = false; if (m_bFirst) { bOK = m_hFile != -1L; m_bFirst = false; } else { bOK = _findnext(m_hFile, &m_FindData) == 0; } if (bOK) { return m_FindData.name; } return NULL; } #else DirBrowser::DirBrowser(const char* szPath) { m_pDir = opendir(szPath); } DirBrowser::~DirBrowser() { if (m_pDir) { closedir(m_pDir); } } const char* DirBrowser::Next() { if (m_pDir) { m_pFindData = readdir(m_pDir); if (m_pFindData) { return m_pFindData->d_name; } } return NULL; } #endif StringBuilder::StringBuilder() { m_szBuffer = NULL; m_iBufferSize = 0; m_iUsedSize = 0; } StringBuilder::~StringBuilder() { free(m_szBuffer); } void StringBuilder::Append(const char* szStr) { int iPartLen = strlen(szStr); if (m_iUsedSize + iPartLen + 1 > m_iBufferSize) { m_iBufferSize += iPartLen + 10240; m_szBuffer = (char*)realloc(m_szBuffer, m_iBufferSize); } strcpy(m_szBuffer + m_iUsedSize, szStr); m_iUsedSize += iPartLen; m_szBuffer[m_iUsedSize] = '\0'; } char Util::VersionRevisionBuf[40]; char* Util::BaseFileName(const char* filename) { char* p = (char*)strrchr(filename, PATH_SEPARATOR); char* p1 = (char*)strrchr(filename, ALT_PATH_SEPARATOR); if (p1) { if ((p && p < p1) || !p) { p = p1; } } if (p) { return p + 1; } else { return (char*)filename; } } void Util::NormalizePathSeparators(char* szPath) { for (char* p = szPath; *p; p++) { if (*p == ALT_PATH_SEPARATOR) { *p = PATH_SEPARATOR; } } } bool Util::ForceDirectories(const char* szPath, char* szErrBuf, int iBufSize) { *szErrBuf = '\0'; char szSysErrStr[256]; char szNormPath[1024]; strncpy(szNormPath, szPath, 1024); szNormPath[1024-1] = '\0'; 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); if (!bOK && errno != ENOENT) { snprintf(szErrBuf, iBufSize, "could not read information for directory %s: errno %i, %s", szNormPath, errno, GetLastErrorMessage(szSysErrStr, sizeof(szSysErrStr))); szErrBuf[iBufSize-1] = 0; return false; } if (bOK && !S_ISDIR(buffer.st_mode)) { snprintf(szErrBuf, iBufSize, "path %s is not a directory", szNormPath); szErrBuf[iBufSize-1] = 0; return false; } if (!bOK #ifdef WIN32 && strlen(szNormPath) > 2 #endif ) { char szParentPath[1024]; strncpy(szParentPath, szNormPath, 1024); szParentPath[1024-1] = '\0'; 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) && !ForceDirectories(szParentPath, szErrBuf, iBufSize)) { return false; } } if (mkdir(szNormPath, S_DIRMODE) != 0 && errno != EEXIST) { snprintf(szErrBuf, iBufSize, "could not create directory %s: %s", szNormPath, GetLastErrorMessage(szSysErrStr, sizeof(szSysErrStr))); szErrBuf[iBufSize-1] = 0; return false; } if (stat(szNormPath, &buffer) != 0) { snprintf(szErrBuf, iBufSize, "could not read information for directory %s: %s", szNormPath, GetLastErrorMessage(szSysErrStr, sizeof(szSysErrStr))); szErrBuf[iBufSize-1] = 0; return false; } if (!S_ISDIR(buffer.st_mode)) { snprintf(szErrBuf, iBufSize, "path %s is not a directory", szNormPath); szErrBuf[iBufSize-1] = 0; return false; } } return true; } bool Util::GetCurrentDirectory(char* szBuffer, int iBufSize) { #ifdef WIN32 return ::GetCurrentDirectory(iBufSize, szBuffer) != NULL; #else return getcwd(szBuffer, iBufSize) != NULL; #endif } bool Util::SetCurrentDirectory(const char* szDirFilename) { #ifdef WIN32 return ::SetCurrentDirectory(szDirFilename); #else return chdir(szDirFilename) == 0; #endif } bool Util::DirEmpty(const char* szDirFilename) { DirBrowser dir(szDirFilename); while (const char* filename = dir.Next()) { if (strcmp(filename, ".") && strcmp(filename, "..")) { return false; } } return true; } bool Util::LoadFileIntoBuffer(const char* szFileName, char** pBuffer, int* pBufferLength) { FILE* pFile = fopen(szFileName, "rb"); 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 Util::SaveBufferIntoFile(const char* szFileName, const char* szBuffer, int iBufLen) { FILE* pFile = fopen(szFileName, "wb"); if (!pFile) { return false; } int iWrittenBytes = fwrite(szBuffer, 1, iBufLen, pFile); fclose(pFile); return iWrittenBytes == iBufLen; } bool Util::CreateSparseFile(const char* szFilename, int iSize) { bool bOK = false; #ifdef WIN32 HANDLE hFile = CreateFile(szFilename, GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_NEW, 0, NULL); if (hFile != INVALID_HANDLE_VALUE) { // first try to create sparse file (supported only on NTFS partitions), // it may fail but that's OK. DWORD dwBytesReturned; DeviceIoControl(hFile, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &dwBytesReturned, NULL); SetFilePointer(hFile, iSize, NULL, FILE_END); SetEndOfFile(hFile); CloseHandle(hFile); bOK = true; } #else // create file FILE* pFile = fopen(szFilename, "ab"); if (pFile) { fclose(pFile); } // there are 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, "ab"); 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, "ab"); char c = '0'; fwrite(&c, 1, iSize, pFile); bOK = ftell(pFile) == iSize; } fclose(pFile); } #endif return bOK; } bool Util::TruncateFile(const char* szFilename, int iSize) { bool bOK = false; #ifdef WIN32 FILE *file = fopen(szFilename, "r+b"); fseek(file, iSize, SEEK_SET); bOK = SetEndOfFile((HANDLE)_get_osfhandle(_fileno(file))) != 0; fclose(file); #else bOK = truncate(szFilename, iSize) == 0; #endif return bOK; } //replace bad chars in filename void Util::MakeValidFilename(char* szFilename, char cReplaceChar, bool bAllowSlashes) { const char* szReplaceChars = bAllowSlashes ? ":*?\"><'\n\r\t" : "\\/:*?\"><'\n\r\t"; char* p = szFilename; while (*p) { if (strchr(szReplaceChars, *p)) { *p = cReplaceChar; } if (bAllowSlashes && *p == ALT_PATH_SEPARATOR) { *p = PATH_SEPARATOR; } p++; } // remove trailing dots and spaces. 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] == '.' || szFilename[iLen - 1] == ' '); iLen--) { szFilename[iLen - 1] = '\0'; } } // returns TRUE if the name was changed by adding duplicate-suffix bool Util::MakeUniqueFilename(char* szDestBufFilename, int iDestBufSize, const char* szDestDir, const char* szBasename) { snprintf(szDestBufFilename, iDestBufSize, "%s%c%s", szDestDir, (int)PATH_SEPARATOR, szBasename); szDestBufFilename[iDestBufSize-1] = '\0'; int iDupeNumber = 0; while (FileExists(szDestBufFilename)) { iDupeNumber++; const char* szExtension = strrchr(szBasename, '.'); if (szExtension && szExtension != szBasename) { char szFilenameWithoutExt[1024]; strncpy(szFilenameWithoutExt, szBasename, 1024); int iEnd = szExtension - szBasename; szFilenameWithoutExt[iEnd < 1024 ? iEnd : 1024-1] = '\0'; if (!strcasecmp(szExtension, ".par2")) { char* szVolExtension = strrchr(szFilenameWithoutExt, '.'); if (szVolExtension && szVolExtension != szFilenameWithoutExt && !strncasecmp(szVolExtension, ".vol", 4)) { *szVolExtension = '\0'; szExtension = szBasename + (szVolExtension - szFilenameWithoutExt); } } snprintf(szDestBufFilename, iDestBufSize, "%s%c%s.duplicate%d%s", szDestDir, (int)PATH_SEPARATOR, szFilenameWithoutExt, iDupeNumber, szExtension); } else { snprintf(szDestBufFilename, iDestBufSize, "%s%c%s.duplicate%d", szDestDir, (int)PATH_SEPARATOR, szBasename, iDupeNumber); } szDestBufFilename[iDestBufSize-1] = '\0'; } return iDupeNumber > 0; } long long Util::JoinInt64(unsigned long Hi, unsigned long Lo) { return (((long long)Hi) << 32) + Lo; } void Util::SplitInt64(long long Int64, unsigned long* Hi, unsigned long* Lo) { *Hi = (unsigned long)(Int64 >> 32); *Lo = (unsigned long)(Int64 & 0xFFFFFFFF); } float Util::Int64ToFloat(long long Int64) { unsigned long Hi, Lo; SplitInt64(Int64, &Hi, &Lo); return ((unsigned long)(1 << 30)) * 4.0f * Hi + Lo; } /* Base64 decryption is taken from * Article "BASE 64 Decoding and Encoding Class 2003" by Jan Raddatz * http://www.codeguru.com/cpp/cpp/algorithms/article.php/c5099/ */ const static char BASE64_DEALPHABET [128] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 9 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 10 - 19 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20 - 29 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 30 - 39 0, 0, 0, 62, 0, 0, 0, 63, 52, 53, // 40 - 49 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, // 50 - 59 0, 61, 0, 0, 0, 0, 1, 2, 3, 4, // 60 - 69 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 70 - 79 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 80 - 89 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, // 90 - 99 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // 100 - 109 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, // 110 - 119 49, 50, 51, 0, 0, 0, 0, 0 // 120 - 127 }; unsigned int DecodeByteQuartet(char* szInputBuffer, char* szOutputBuffer) { unsigned int buffer = 0; if (szInputBuffer[3] == '=') { if (szInputBuffer[2] == '=') { buffer = (buffer | BASE64_DEALPHABET [(int)szInputBuffer[0]]) << 6; buffer = (buffer | BASE64_DEALPHABET [(int)szInputBuffer[1]]) << 6; buffer = buffer << 14; szOutputBuffer [0] = (char)(buffer >> 24); return 1; } else { buffer = (buffer | BASE64_DEALPHABET [(int)szInputBuffer[0]]) << 6; buffer = (buffer | BASE64_DEALPHABET [(int)szInputBuffer[1]]) << 6; buffer = (buffer | BASE64_DEALPHABET [(int)szInputBuffer[2]]) << 6; buffer = buffer << 8; szOutputBuffer [0] = (char)(buffer >> 24); szOutputBuffer [1] = (char)(buffer >> 16); return 2; } } else { buffer = (buffer | BASE64_DEALPHABET [(int)szInputBuffer[0]]) << 6; buffer = (buffer | BASE64_DEALPHABET [(int)szInputBuffer[1]]) << 6; buffer = (buffer | BASE64_DEALPHABET [(int)szInputBuffer[2]]) << 6; buffer = (buffer | BASE64_DEALPHABET [(int)szInputBuffer[3]]) << 6; buffer = buffer << 2; szOutputBuffer [0] = (char)(buffer >> 24); szOutputBuffer [1] = (char)(buffer >> 16); szOutputBuffer [2] = (char)(buffer >> 8); return 3; } return 0; } bool Util::MoveFile(const char* szSrcFilename, const char* szDstFilename) { bool bOK = rename(szSrcFilename, szDstFilename) == 0; #ifndef WIN32 if (!bOK && (errno == EXDEV)) { FILE* infile = fopen(szSrcFilename, "rb"); if (!infile) { return false; } FILE* outfile = fopen(szDstFilename, "wb+"); if (!outfile) { fclose(infile); return false; } static const int BUFFER_SIZE = 1024 * 50; char* buffer = (char*)malloc(BUFFER_SIZE); int cnt = BUFFER_SIZE; while (cnt == BUFFER_SIZE) { cnt = (int)fread(buffer, 1, BUFFER_SIZE, infile); fwrite(buffer, 1, cnt, outfile); } fclose(infile); fclose(outfile); free(buffer); bOK = remove(szSrcFilename) == 0; } #endif return bOK; } bool Util::FileExists(const char* szFilename) { struct stat buffer; bool bExists = !stat(szFilename, &buffer) && S_ISREG(buffer.st_mode); return bExists; } bool Util::FileExists(const char* szPath, const char* szFilenameWithoutPath) { char fullFilename[1024]; snprintf(fullFilename, 1024, "%s%c%s", szPath, (int)PATH_SEPARATOR, szFilenameWithoutPath); fullFilename[1024-1] = '\0'; bool bExists = Util::FileExists(fullFilename); return bExists; } bool Util::DirectoryExists(const char* szDirFilename) { struct stat buffer; bool bExists = !stat(szDirFilename, &buffer) && S_ISDIR(buffer.st_mode); return bExists; } bool Util::CreateDirectory(const char* szDirFilename) { mkdir(szDirFilename, S_DIRMODE); return DirectoryExists(szDirFilename); } bool Util::RemoveDirectory(const char* szDirFilename) { #ifdef WIN32 return ::RemoveDirectory(szDirFilename); #else return remove(szDirFilename) == 0; #endif } bool Util::DeleteDirectoryWithContent(const char* szDirFilename) { bool bOK = true; DirBrowser dir(szDirFilename); while (const char* filename = dir.Next()) { char szFullFilename[1024]; snprintf(szFullFilename, 1024, "%s%c%s", szDirFilename, PATH_SEPARATOR, filename); szFullFilename[1024-1] = '\0'; if (strcmp(filename, ".") && strcmp(filename, "..")) { if (Util::DirectoryExists(szFullFilename)) { bOK &= DeleteDirectoryWithContent(szFullFilename); } else { bOK &= remove(szFullFilename) == 0; } } } return bOK && RemoveDirectory(szDirFilename); } long long Util::FileSize(const char* szFilename) { #ifdef WIN32 struct _stat32i64 buffer; _stat32i64(szFilename, &buffer); #else struct stat buffer; stat(szFilename, &buffer); #endif return buffer.st_size; } long long Util::FreeDiskSize(const char* szPath) { #ifdef WIN32 ULARGE_INTEGER lFree, lDummy; if (GetDiskFreeSpaceEx(szPath, &lFree, &lDummy, &lDummy)) { return lFree.QuadPart; } #else struct statvfs diskdata; if (!statvfs(szPath, &diskdata)) { return (long long)diskdata.f_frsize * (long long)diskdata.f_bavail; } #endif return -1; } bool Util::RenameBak(const char* szFilename, const char* szBakPart, bool bRemoveOldExtension, char* szNewNameBuf, int iNewNameBufSize) { char szChangedFilename[1024]; if (bRemoveOldExtension) { strncpy(szChangedFilename, szFilename, 1024); szChangedFilename[1024-1] = '\0'; char* szExtension = strrchr(szChangedFilename, '.'); if (szExtension) { *szExtension = '\0'; } } char bakname[1024]; snprintf(bakname, 1024, "%s.%s", bRemoveOldExtension ? szChangedFilename : szFilename, szBakPart); bakname[1024-1] = '\0'; int i = 2; struct stat buffer; while (!stat(bakname, &buffer)) { snprintf(bakname, 1024, "%s.%i.%s", bRemoveOldExtension ? szChangedFilename : szFilename, i++, szBakPart); bakname[1024-1] = '\0'; } if (szNewNameBuf) { strncpy(szNewNameBuf, bakname, iNewNameBufSize); } bool bOK = !rename(szFilename, bakname); return bOK; } #ifndef WIN32 bool Util::ExpandHomePath(const char* szFilename, char* szBuffer, int iBufSize) { if (szFilename && (szFilename[0] == '~') && (szFilename[1] == '/')) { // expand home-dir char* home = getenv("HOME"); if (!home) { struct passwd *pw = getpwuid(getuid()); if (pw) { home = pw->pw_dir; } } if (!home) { return false; } if (home[strlen(home)-1] == '/') { snprintf(szBuffer, iBufSize, "%s%s", home, szFilename + 2); } else { snprintf(szBuffer, iBufSize, "%s/%s", home, szFilename + 2); } szBuffer[iBufSize - 1] = '\0'; } else { strncpy(szBuffer, szFilename, iBufSize); szBuffer[iBufSize - 1] = '\0'; } return true; } #endif void Util::ExpandFileName(const char* szFilename, char* szBuffer, int iBufSize) { #ifdef WIN32 _fullpath(szBuffer, szFilename, iBufSize); #else if (szFilename[0] != '\0' && szFilename[0] != '/') { char szCurDir[MAX_PATH + 1]; getcwd(szCurDir, sizeof(szCurDir) - 1); // 1 char reserved for adding backslash int iOffset = 0; if (szFilename[0] == '.' && szFilename[1] == '/') { iOffset += 2; } snprintf(szBuffer, iBufSize, "%s/%s", szCurDir, szFilename + iOffset); } else { strncpy(szBuffer, szFilename, iBufSize); szBuffer[iBufSize - 1] = '\0'; } #endif } void Util::FormatFileSize(char * szBuffer, int iBufLen, long long lFileSize) { if (lFileSize > 1024 * 1024 * 1000) { snprintf(szBuffer, iBufLen, "%.2f GB", (float)(Util::Int64ToFloat(lFileSize) / 1024 / 1024 / 1024)); } else if (lFileSize > 1024 * 1000) { snprintf(szBuffer, iBufLen, "%.2f MB", (float)(Util::Int64ToFloat(lFileSize) / 1024 / 1024)); } else if (lFileSize > 1000) { snprintf(szBuffer, iBufLen, "%.2f KB", (float)(Util::Int64ToFloat(lFileSize) / 1024)); } else { snprintf(szBuffer, iBufLen, "%i B", (int)lFileSize); } szBuffer[iBufLen - 1] = '\0'; } bool Util::SameFilename(const char* szFilename1, const char* szFilename2) { #ifdef WIN32 return strcasecmp(szFilename1, szFilename2) == 0; #else return strcmp(szFilename1, szFilename2) == 0; #endif } #ifndef WIN32 void Util::FixExecPermission(const char* szFilename) { struct stat buffer; bool bOK = !stat(szFilename, &buffer); if (bOK) { buffer.st_mode = buffer.st_mode | S_IXUSR | S_IXGRP | S_IXOTH; chmod(szFilename, buffer.st_mode); } } #endif char* Util::GetLastErrorMessage(char* szBuffer, int iBufLen) { szBuffer[0] = '\0'; strerror_r(errno, szBuffer, iBufLen); szBuffer[iBufLen-1] = '\0'; return szBuffer; } void Util::InitVersionRevision() { #ifndef WIN32 if ((strlen(svn_version()) > 0) && strstr(VERSION, "testing")) { snprintf(VersionRevisionBuf, sizeof(VersionRevisionBuf), "%s-r%s", VERSION, svn_version()); } else #endif { snprintf(VersionRevisionBuf, sizeof(VersionRevisionBuf), "%s", VERSION); } } bool Util::SplitCommandLine(const char* szCommandLine, char*** argv) { int iArgCount = 0; char szBuf[1024]; char* pszArgList[100]; unsigned int iLen = 0; bool bEscaping = false; bool bSpace = true; for (const char* p = szCommandLine; ; p++) { if (*p) { const char c = *p; if (bEscaping) { if (c == '\'') { if (p[1] == '\'' && iLen < sizeof(szBuf) - 1) { szBuf[iLen++] = c; p++; } else { bEscaping = false; bSpace = true; } } else if (iLen < sizeof(szBuf) - 1) { szBuf[iLen++] = c; } } else { if (c == ' ') { bSpace = true; } else if (c == '\'' && bSpace) { bEscaping = true; bSpace = false; } else if (iLen < sizeof(szBuf) - 1) { szBuf[iLen++] = c; bSpace = false; } } } if ((bSpace || !*p) && iLen > 0 && iArgCount < 100) { //add token szBuf[iLen] = '\0'; if (argv) { pszArgList[iArgCount] = strdup(szBuf); } (iArgCount)++; iLen = 0; } if (!*p) { break; } } if (argv) { pszArgList[iArgCount] = NULL; *argv = (char**)malloc((iArgCount + 1) * sizeof(char*)); memcpy(*argv, pszArgList, sizeof(char*) * (iArgCount + 1)); } return iArgCount > 0; } void Util::TrimRight(char* szStr) { char* szEnd = szStr + strlen(szStr) - 1; while (szEnd >= szStr && (*szEnd == '\n' || *szEnd == '\r' || *szEnd == ' ' || *szEnd == '\t')) { *szEnd = '\0'; szEnd--; } } char* Util::Trim(char* szStr) { TrimRight(szStr); while (*szStr == '\n' || *szStr == '\r' || *szStr == ' ' || *szStr == '\t') { szStr++; } return szStr; } char* Util::ReduceStr(char* szStr, const char* szFrom, const char* szTo) { int iLenFrom = strlen(szFrom); int iLenTo = strlen(szTo); // assert(iLenTo < iLenFrom); while (char* p = strstr(szStr, szFrom)) { const char* src = szTo; while ((*p++ = *src++)) ; src = --p - iLenTo + iLenFrom; while ((*p++ = *src++)) ; } return szStr; } /* Calculate Hash using Bob Jenkins (1996) algorithm * http://burtleburtle.net/bob/c/lookup2.c */ typedef unsigned int ub4; /* unsigned 4-byte quantities */ typedef unsigned char ub1; #define hashsize(n) ((ub4)1<<(n)) #define hashmask(n) (hashsize(n)-1) #define mix(a,b,c) \ { \ a -= b; a -= c; a ^= (c>>13); \ b -= c; b -= a; b ^= (a<<8); \ c -= a; c -= b; c ^= (b>>13); \ a -= b; a -= c; a ^= (c>>12); \ b -= c; b -= a; b ^= (a<<16); \ c -= a; c -= b; c ^= (b>>5); \ a -= b; a -= c; a ^= (c>>3); \ b -= c; b -= a; b ^= (a<<10); \ c -= a; c -= b; c ^= (b>>15); \ } ub4 hash(register ub1 *k, register ub4 length, register ub4 initval) // register ub1 *k; /* the key */ // register ub4 length; /* the length of the key */ // register ub4 initval; /* the previous hash, or an arbitrary value */ { register ub4 a,b,c,len; /* Set up the internal state */ len = length; a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */ c = initval; /* the previous hash value */ /*---------------------------------------- handle most of the key */ while (len >= 12) { a += (k[0] +((ub4)k[1]<<8) +((ub4)k[2]<<16) +((ub4)k[3]<<24)); b += (k[4] +((ub4)k[5]<<8) +((ub4)k[6]<<16) +((ub4)k[7]<<24)); c += (k[8] +((ub4)k[9]<<8) +((ub4)k[10]<<16)+((ub4)k[11]<<24)); mix(a,b,c); k += 12; len -= 12; } /*------------------------------------- handle the last 11 bytes */ c += length; switch(len) /* all the case statements fall through */ { case 11: c+=((ub4)k[10]<<24); case 10: c+=((ub4)k[9]<<16); case 9 : c+=((ub4)k[8]<<8); /* the first byte of c is reserved for the length */ case 8 : b+=((ub4)k[7]<<24); case 7 : b+=((ub4)k[6]<<16); case 6 : b+=((ub4)k[5]<<8); case 5 : b+=k[4]; case 4 : a+=((ub4)k[3]<<24); case 3 : a+=((ub4)k[2]<<16); case 2 : a+=((ub4)k[1]<<8); case 1 : a+=k[0]; /* case 0: nothing left to add */ } mix(a,b,c); /*-------------------------------------------- report the result */ return c; } unsigned int Util::HashBJ96(const char* szBuffer, int iBufSize, unsigned int iInitValue) { return (unsigned int)hash((ub1*)szBuffer, (ub4)iBufSize, (ub4)iInitValue); } #ifdef WIN32 bool Util::RegReadStr(HKEY hKey, const char* szKeyName, const char* szValueName, char* szBuffer, int* iBufLen) { HKEY hSubKey; if (!RegOpenKeyEx(hKey, szKeyName, 0, KEY_READ, &hSubKey)) { DWORD iRetBytes = *iBufLen; LONG iRet = RegQueryValueEx(hSubKey, szValueName, NULL, NULL, (LPBYTE)szBuffer, &iRetBytes); *iBufLen = iRetBytes; RegCloseKey(hSubKey); return iRet == 0; } return false; } #endif unsigned int WebUtil::DecodeBase64(char* szInputBuffer, int iInputBufferLength, char* szOutputBuffer) { unsigned int InputBufferIndex = 0; unsigned int OutputBufferIndex = 0; unsigned int InputBufferLength = iInputBufferLength > 0 ? iInputBufferLength : strlen(szInputBuffer); char ByteQuartet [4]; int i = 0; while (InputBufferIndex < InputBufferLength) { // Ignore all characters except the ones in BASE64_ALPHABET if ((szInputBuffer [InputBufferIndex] >= 48 && szInputBuffer [InputBufferIndex] <= 57) || (szInputBuffer [InputBufferIndex] >= 65 && szInputBuffer [InputBufferIndex] <= 90) || (szInputBuffer [InputBufferIndex] >= 97 && szInputBuffer [InputBufferIndex] <= 122) || szInputBuffer [InputBufferIndex] == '+' || szInputBuffer [InputBufferIndex] == '/' || szInputBuffer [InputBufferIndex] == '=') { ByteQuartet [i] = szInputBuffer [InputBufferIndex]; i++; } InputBufferIndex++; if (i == 4) { OutputBufferIndex += DecodeByteQuartet(ByteQuartet, szOutputBuffer + OutputBufferIndex); i = 0; } } // OutputBufferIndex gives us the next position of the next decoded character // inside our output buffer and thus represents the number of decoded characters // in our buffer. return OutputBufferIndex; } /* END - Base64 */ char* WebUtil::XmlEncode(const char* raw) { // calculate the required outputstring-size based on number of xml-entities and their sizes int iReqSize = strlen(raw); for (const char* p = raw; *p; p++) { unsigned char ch = *p; switch (ch) { case '>': case '<': iReqSize += 4; break; case '&': iReqSize += 5; break; case '\'': case '\"': iReqSize += 6; break; default: if (ch < 0x20 || ch >= 0x80) { iReqSize += 10; break; } } } char* result = (char*)malloc(iReqSize + 1); // copy string char* output = result; for (const char* p = raw; ; p++) { unsigned char ch = *p; switch (ch) { case '\0': goto BreakLoop; case '<': strcpy(output, "<"); output += 4; break; case '>': strcpy(output, ">"); output += 4; break; case '&': strcpy(output, "&"); output += 5; break; case '\'': strcpy(output, "'"); output += 6; break; case '\"': strcpy(output, """); output += 6; break; default: if (ch < 0x20 || ch > 0x80) { unsigned int cp = ch; // decode utf8 if ((cp >> 5) == 0x6 && (p[1] & 0xc0) == 0x80) { // 2 bytes if (!(ch = *++p)) goto BreakLoop; // read next char cp = ((cp << 6) & 0x7ff) + (ch & 0x3f); } else if ((cp >> 4) == 0xe && (p[1] & 0xc0) == 0x80) { // 3 bytes if (!(ch = *++p)) goto BreakLoop; // read next char cp = ((cp << 12) & 0xffff) + ((ch << 6) & 0xfff); if (!(ch = *++p)) goto BreakLoop; // read next char cp += ch & 0x3f; } else if ((cp >> 3) == 0x1e && (p[1] & 0xc0) == 0x80) { // 4 bytes if (!(ch = *++p)) goto BreakLoop; // read next char cp = ((cp << 18) & 0x1fffff) + ((ch << 12) & 0x3ffff); if (!(ch = *++p)) goto BreakLoop; // read next char cp += (ch << 6) & 0xfff; if (!(ch = *++p)) goto BreakLoop; // read next char cp += ch & 0x3f; } // accept only valid XML 1.0 characters if (cp == 0x9 || cp == 0xA || cp == 0xD || (0x20 <= cp && cp <= 0xD7FF) || (0xE000 <= cp && cp <= 0xFFFD) || (0x10000 <= cp && cp <= 0x10FFFF)) { sprintf(output, "&#x%06x;", cp); output += 10; } else { // replace invalid characters with dots sprintf(output, "."); output += 1; } } else { *output++ = ch; } break; } } BreakLoop: *output = '\0'; return result; } void WebUtil::XmlDecode(char* raw) { char* output = raw; for (char* p = raw;;) { switch (*p) { case '\0': goto BreakLoop; case '&': { p++; if (!strncmp(p, "lt;", 3)) { *output++ = '<'; p += 3; } else if (!strncmp(p, "gt;", 3)) { *output++ = '>'; p += 3; } else if (!strncmp(p, "amp;", 4)) { *output++ = '&'; p += 4; } else if (!strncmp(p, "apos;", 5)) { *output++ = '\''; p += 5; } else if (!strncmp(p, "quot;", 5)) { *output++ = '\"'; p += 5; } else if (*p == '#') { int code = atoi(p+1); p = strchr(p+1, ';'); if (p) p++; *output++ = (char)code; } else { // unknown entity *output++ = *(p-1); p++; } break; } default: *output++ = *p++; break; } } BreakLoop: *output = '\0'; } const char* WebUtil::XmlFindTag(const char* szXml, const char* szTag, int* pValueLength) { char szOpenTag[100]; snprintf(szOpenTag, 100, "<%s>", szTag); szOpenTag[100-1] = '\0'; char szCloseTag[100]; snprintf(szCloseTag, 100, "", szTag); szCloseTag[100-1] = '\0'; char szOpenCloseTag[100]; snprintf(szOpenCloseTag, 100, "<%s/>", szTag); szOpenCloseTag[100-1] = '\0'; const char* pstart = strstr(szXml, szOpenTag); const char* pstartend = strstr(szXml, szOpenCloseTag); if (!pstart && !pstartend) return NULL; if (pstartend && (!pstart || pstartend < pstart)) { *pValueLength = 0; return pstartend; } const char* pend = strstr(pstart, szCloseTag); if (!pend) return NULL; int iTagLen = strlen(szOpenTag); *pValueLength = (int)(pend - pstart - iTagLen); return pstart + iTagLen; } bool WebUtil::XmlParseTagValue(const char* szXml, const char* szTag, char* szValueBuf, int iValueBufSize, const char** pTagEnd) { int iValueLen = 0; const char* szValue = XmlFindTag(szXml, szTag, &iValueLen); if (!szValue) { return false; } int iLen = iValueLen < iValueBufSize ? iValueLen : iValueBufSize - 1; strncpy(szValueBuf, szValue, iLen); szValueBuf[iLen] = '\0'; if (pTagEnd) { *pTagEnd = szValue + iValueLen; } return true; } char* WebUtil::JsonEncode(const char* raw) { // calculate the required outputstring-size based on number of escape-entities and their sizes int iReqSize = strlen(raw); for (const char* p = raw; *p; p++) { unsigned char ch = *p; switch (ch) { case '\"': case '\\': case '/': case '\b': case '\f': case '\n': case '\r': case '\t': iReqSize++; break; default: if (ch < 0x20 || ch >= 0x80) { iReqSize += 6; break; } } } char* result = (char*)malloc(iReqSize + 1); // copy string char* output = result; for (const char* p = raw; ; p++) { unsigned char ch = *p; switch (ch) { case '\0': goto BreakLoop; case '"': strcpy(output, "\\\""); output += 2; break; case '\\': strcpy(output, "\\\\"); output += 2; break; case '/': strcpy(output, "\\/"); output += 2; break; case '\b': strcpy(output, "\\b"); output += 2; break; case '\f': strcpy(output, "\\f"); output += 2; break; case '\n': strcpy(output, "\\n"); output += 2; break; case '\r': strcpy(output, "\\r"); output += 2; break; case '\t': strcpy(output, "\\t"); output += 2; break; default: if (ch < 0x20 || ch > 0x80) { unsigned int cp = ch; // decode utf8 if ((cp >> 5) == 0x6 && (p[1] & 0xc0) == 0x80) { // 2 bytes if (!(ch = *++p)) goto BreakLoop; // read next char cp = ((cp << 6) & 0x7ff) + (ch & 0x3f); } else if ((cp >> 4) == 0xe && (p[1] & 0xc0) == 0x80) { // 3 bytes if (!(ch = *++p)) goto BreakLoop; // read next char cp = ((cp << 12) & 0xffff) + ((ch << 6) & 0xfff); if (!(ch = *++p)) goto BreakLoop; // read next char cp += ch & 0x3f; } else if ((cp >> 3) == 0x1e && (p[1] & 0xc0) == 0x80) { // 4 bytes if (!(ch = *++p)) goto BreakLoop; // read next char cp = ((cp << 18) & 0x1fffff) + ((ch << 12) & 0x3ffff); if (!(ch = *++p)) goto BreakLoop; // read next char cp += (ch << 6) & 0xfff; if (!(ch = *++p)) goto BreakLoop; // read next char cp += ch & 0x3f; } // we support only Unicode range U+0000-U+FFFF sprintf(output, "\\u%04x", cp <= 0xFFFF ? cp : '.'); output += 6; } else { *output++ = ch; } break; } } BreakLoop: *output = '\0'; return result; } void WebUtil::JsonDecode(char* raw) { char* output = raw; for (char* p = raw;;) { switch (*p) { case '\0': goto BreakLoop; case '\\': { p++; switch (*p) { case '"': *output++ = '"'; break; case '\\': *output++ = '\\'; break; case '/': *output++ = '/'; break; case 'b': *output++ = '\b'; break; case 'f': *output++ = '\f'; break; case 'n': *output++ = '\n'; break; case 'r': *output++ = '\r'; break; case 't': *output++ = '\t'; break; case 'u': *output++ = (char)strtol(p + 1, NULL, 16); p += 4; break; default: // unknown escape-sequence, should never occur *output++ = *p; break; } p++; break; } default: *output++ = *p++; break; } } BreakLoop: *output = '\0'; } const char* WebUtil::JsonFindField(const char* szJsonText, const char* szFieldName, int* pValueLength) { char szOpenTag[100]; snprintf(szOpenTag, 100, "\"%s\"", szFieldName); szOpenTag[100-1] = '\0'; const char* pstart = strstr(szJsonText, szOpenTag); if (!pstart) return NULL; pstart += strlen(szOpenTag); return JsonNextValue(pstart, pValueLength); } const char* WebUtil::JsonNextValue(const char* szJsonText, int* pValueLength) { const char* pstart = szJsonText; while (*pstart && strchr(" ,[{:\r\n\t\f", *pstart)) pstart++; if (!*pstart) return NULL; const char* pend = pstart; char ch = *pend; bool bStr = ch == '"'; if (bStr) { ch = *++pend; } while (ch) { if (ch == '\\') { if (!*++pend || !*++pend) return NULL; ch = *pend; } if (bStr && ch == '"') { pend++; break; } else if (!bStr && strchr(" ,]}\r\n\t\f", ch)) { break; } ch = *++pend; } *pValueLength = (int)(pend - pstart); return pstart; } void WebUtil::HttpUnquote(char* raw) { if (*raw != '"') { return; } char *output = raw; for (char *p = raw+1;;) { switch (*p) { case '\0': case '"': goto BreakLoop; case '\\': p++; *output++ = *p; break; default: *output++ = *p++; break; } } BreakLoop: *output = '\0'; } #ifdef WIN32 bool WebUtil::Utf8ToAnsi(char* szBuffer, int iBufLen) { WCHAR* wstr = (WCHAR*)malloc(iBufLen * 2); int errcode = MultiByteToWideChar(CP_UTF8, 0, szBuffer, -1, wstr, iBufLen); if (errcode > 0) { errcode = WideCharToMultiByte(CP_ACP, 0, wstr, -1, szBuffer, iBufLen, "_", NULL); } free(wstr); return errcode > 0; } bool WebUtil::AnsiToUtf8(char* szBuffer, int iBufLen) { WCHAR* wstr = (WCHAR*)malloc(iBufLen * 2); int errcode = MultiByteToWideChar(CP_ACP, 0, szBuffer, -1, wstr, iBufLen); if (errcode > 0) { errcode = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, szBuffer, iBufLen, NULL, NULL); } free(wstr); return errcode > 0; } #endif char* WebUtil::Latin1ToUtf8(const char* szStr) { char *res = (char*)malloc(strlen(szStr) * 2 + 1); const unsigned char *in = (const unsigned char*)szStr; unsigned char *out = (unsigned char*)res; while (*in) { if (*in < 128) { *out++ = *in++; } else { *out++ = 0xc2 + (*in > 0xbf); *out++ = (*in++ & 0x3f) + 0x80; } } *out = '\0'; return res; } /* The date/time can be formatted according to RFC822 in different ways. Examples: Wed, 26 Jun 2013 01:02:54 -0600 Wed, 26 Jun 2013 01:02:54 GMT 26 Jun 2013 01:02:54 -0600 26 Jun 2013 01:02 -0600 26 Jun 2013 01:02 A This function however supports only the first format! */ time_t WebUtil::ParseRfc822DateTime(const char* szDateTimeStr) { char month[4]; int day, year, hours, minutes, seconds, zonehours, zoneminutes; int r = sscanf(szDateTimeStr, "%*s %d %3s %d %d:%d:%d %3d %2d", &day, &month[0], &year, &hours, &minutes, &seconds, &zonehours, &zoneminutes); if (r != 8) { return 0; } int mon = 0; if (!strcasecmp(month, "Jan")) mon = 0; else if (!strcasecmp(month, "Feb")) mon = 1; else if (!strcasecmp(month, "Mar")) mon = 2; else if (!strcasecmp(month, "Apr")) mon = 3; else if (!strcasecmp(month, "May")) mon = 4; else if (!strcasecmp(month, "Jun")) mon = 5; else if (!strcasecmp(month, "Jul")) mon = 6; else if (!strcasecmp(month, "Aug")) mon = 7; else if (!strcasecmp(month, "Sep")) mon = 8; else if (!strcasecmp(month, "Oct")) mon = 9; else if (!strcasecmp(month, "Nov")) mon = 10; else if (!strcasecmp(month, "Dec")) mon = 11; struct tm rawtime; memset(&rawtime, 0, sizeof(rawtime)); rawtime.tm_year = year - 1900; rawtime.tm_mon = mon; rawtime.tm_mday = day; rawtime.tm_hour = hours; rawtime.tm_min = minutes; rawtime.tm_sec = seconds; time_t enctime = mktime(&rawtime); enctime = enctime - (zonehours * 60 + (zonehours > 0 ? zoneminutes : -zoneminutes)) * 60; return enctime; } URL::URL(const char* szAddress) { m_szAddress = NULL; m_szProtocol = NULL; m_szUser = NULL; m_szPassword = NULL; m_szHost = NULL; m_szResource = NULL; m_iPort = 0; m_bTLS = false; m_bValid = false; if (szAddress) { m_szAddress = strdup(szAddress); ParseURL(); } } URL::~URL() { free(m_szAddress); free(m_szProtocol); free(m_szUser); free(m_szPassword); free(m_szHost); free(m_szResource); } void URL::ParseURL() { // Examples: // http://user:password@host:port/path/to/resource?param // http://user@host:port/path/to/resource?param // http://host:port/path/to/resource?param // http://host/path/to/resource?param // http://host char* protEnd = strstr(m_szAddress, "://"); if (!protEnd) { // Bad URL return; } m_szProtocol = (char*)malloc(protEnd - m_szAddress + 1); strncpy(m_szProtocol, m_szAddress, protEnd - m_szAddress); m_szProtocol[protEnd - m_szAddress] = 0; char* hostStart = protEnd + 3; char* slash = strchr(hostStart, '/'); char* hostEnd = NULL; char* amp = strchr(hostStart, '@'); if (amp && (!slash || amp < slash)) { // parse user/password char* userend = amp - 1; char* pass = strchr(hostStart, ':'); if (pass && pass < amp) { int iLen = (int)(amp - pass - 1); if (iLen > 0) { m_szPassword = (char*)malloc(iLen + 1); strncpy(m_szPassword, pass + 1, iLen); m_szPassword[iLen] = 0; } userend = pass - 1; } int iLen = (int)(userend - hostStart + 1); if (iLen > 0) { m_szUser = (char*)malloc(iLen + 1); strncpy(m_szUser, hostStart, iLen); m_szUser[iLen] = 0; } hostStart = amp + 1; } if (slash) { char* resEnd = m_szAddress + strlen(m_szAddress); m_szResource = (char*)malloc(resEnd - slash + 1 + 1); strncpy(m_szResource, slash, resEnd - slash + 1); m_szResource[resEnd - slash + 1] = 0; hostEnd = slash - 1; } else { m_szResource = strdup("/"); hostEnd = m_szAddress + strlen(m_szAddress); } char* colon = strchr(hostStart, ':'); if (colon && colon < hostEnd) { hostEnd = colon - 1; m_iPort = atoi(colon + 1); } m_szHost = (char*)malloc(hostEnd - hostStart + 1 + 1); strncpy(m_szHost, hostStart, hostEnd - hostStart + 1); m_szHost[hostEnd - hostStart + 1] = 0; m_bValid = true; } RegEx::RegEx(const char *szPattern, int iMatchBufSize) { #ifdef HAVE_REGEX_H m_pContext = malloc(sizeof(regex_t)); m_bValid = regcomp((regex_t*)m_pContext, szPattern, REG_EXTENDED | REG_ICASE | (iMatchBufSize > 0 ? 0 : REG_NOSUB)) == 0; m_iMatchBufSize = iMatchBufSize; if (iMatchBufSize > 0) { m_pMatches = malloc(sizeof(regmatch_t) * iMatchBufSize); } else { m_pMatches = NULL; } #else m_bValid = false; #endif } RegEx::~RegEx() { #ifdef HAVE_REGEX_H regfree((regex_t*)m_pContext); free(m_pContext); free(m_pMatches); #endif } bool RegEx::Match(const char *szStr) { #ifdef HAVE_REGEX_H return m_bValid ? regexec((regex_t*)m_pContext, szStr, m_iMatchBufSize, (regmatch_t*)m_pMatches, 0) == 0 : false; #else return false; #endif } int RegEx::GetMatchCount() { #ifdef HAVE_REGEX_H int iCount = 0; if (m_pMatches) { regmatch_t* pMatches = (regmatch_t*)m_pMatches; while (iCount < m_iMatchBufSize && pMatches[iCount].rm_so > -1) { iCount++; } } return iCount; #else return 0; #endif } int RegEx::GetMatchStart(int index) { #ifdef HAVE_REGEX_H regmatch_t* pMatches = (regmatch_t*)m_pMatches; return pMatches[index].rm_so; #else return NULL; #endif } int RegEx::GetMatchLen(int index) { #ifdef HAVE_REGEX_H regmatch_t* pMatches = (regmatch_t*)m_pMatches; return pMatches[index].rm_eo - pMatches[index].rm_so; #else return 0; #endif } WildMask::WildMask(const char *szPattern, bool bWantsPositions) { m_szPattern = strdup(szPattern); m_bWantsPositions = bWantsPositions; m_WildStart = NULL; m_WildLen = NULL; m_iArrLen = 0; } WildMask::~WildMask() { free(m_szPattern); free(m_WildStart); free(m_WildLen); } void WildMask::ExpandArray() { m_iWildCount++; if (m_iWildCount > m_iArrLen) { m_iArrLen += 100; m_WildStart = (int*)realloc(m_WildStart, sizeof(*m_WildStart) * m_iArrLen); m_WildLen = (int*)realloc(m_WildLen, sizeof(*m_WildLen) * m_iArrLen); } } // Based on code from http://bytes.com/topic/c/answers/212179-string-matching // Extended to save positions of matches. bool WildMask::Match(const char *szStr) { const char* pat = m_szPattern; const char* str = szStr; const char *spos, *wpos; m_iWildCount = 0; bool qmark = false; bool star = false; spos = wpos = str; while (*str && *pat != '*') { if (m_bWantsPositions && (*pat == '?' || *pat == '#')) { if (!qmark) { ExpandArray(); m_WildStart[m_iWildCount-1] = str - szStr; m_WildLen[m_iWildCount-1] = 0; qmark = true; } } else if (m_bWantsPositions && qmark) { m_WildLen[m_iWildCount-1] = str - (szStr + m_WildStart[m_iWildCount-1]); qmark = false; } if (!(tolower(*pat) == tolower(*str) || *pat == '?' || (*pat == '#' && strchr("0123456789", *str)))) { return false; } str++; pat++; } if (m_bWantsPositions && qmark) { m_WildLen[m_iWildCount-1] = str - (szStr + m_WildStart[m_iWildCount-1]); qmark = false; } while (*str) { if (*pat == '*') { if (m_bWantsPositions && qmark) { m_WildLen[m_iWildCount-1] = str - (szStr + m_WildStart[m_iWildCount-1]); qmark = false; } if (m_bWantsPositions && !star) { ExpandArray(); m_WildStart[m_iWildCount-1] = str - szStr; m_WildLen[m_iWildCount-1] = 0; star = true; } if (*++pat == '\0') { if (m_bWantsPositions && star) { m_WildLen[m_iWildCount-1] = strlen(str); } return true; } wpos = pat; spos = str + 1; } else if (*pat == '?' || (*pat == '#' && strchr("0123456789", *str))) { if (m_bWantsPositions && !qmark) { ExpandArray(); m_WildStart[m_iWildCount-1] = str - szStr; m_WildLen[m_iWildCount-1] = 0; qmark = true; } pat++; str++; } else if (tolower(*pat) == tolower(*str)) { if (m_bWantsPositions && qmark) { m_WildLen[m_iWildCount-1] = str - (szStr + m_WildStart[m_iWildCount-1]); qmark = false; } else if (m_bWantsPositions && star) { m_WildLen[m_iWildCount-1] = str - (szStr + m_WildStart[m_iWildCount-1]); star = false; } pat++; str++; } else { if (m_bWantsPositions && qmark) { m_iWildCount--; qmark = false; } pat = wpos; str = spos++; star = true; } } if (m_bWantsPositions && qmark) { m_WildLen[m_iWildCount-1] = str - (szStr + m_WildStart[m_iWildCount-1]); } if (*pat == '*' && m_bWantsPositions && !star) { ExpandArray(); m_WildStart[m_iWildCount-1] = str - szStr; m_WildLen[m_iWildCount-1] = strlen(str); } while (*pat == '*') { pat++; } return *pat == '\0'; } #ifndef DISABLE_GZIP unsigned int ZLib::GZipLen(int iInputBufferLength) { z_stream zstr; memset(&zstr, 0, sizeof(zstr)); return (unsigned int)deflateBound(&zstr, iInputBufferLength); } unsigned int ZLib::GZip(const void* szInputBuffer, int iInputBufferLength, void* szOutputBuffer, int iOutputBufferLength) { z_stream zstr; zstr.zalloc = Z_NULL; zstr.zfree = Z_NULL; zstr.opaque = Z_NULL; zstr.next_in = (Bytef*)szInputBuffer; zstr.avail_in = iInputBufferLength; zstr.next_out = (Bytef*)szOutputBuffer; zstr.avail_out = iOutputBufferLength; /* add 16 to MAX_WBITS to enforce gzip format */ if (Z_OK != deflateInit2(&zstr, Z_DEFAULT_COMPRESSION, Z_DEFLATED, MAX_WBITS + 16, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY)) { return 0; } unsigned int total_out = 0; if (deflate(&zstr, Z_FINISH) == Z_STREAM_END) { total_out = (unsigned int)zstr.total_out; } deflateEnd(&zstr); return total_out; } GUnzipStream::GUnzipStream(int BufferSize) { m_iBufferSize = BufferSize; m_pZStream = malloc(sizeof(z_stream)); m_pOutputBuffer = malloc(BufferSize); memset(m_pZStream, 0, sizeof(z_stream)); /* add 16 to MAX_WBITS to enforce gzip format */ int ret = inflateInit2(((z_stream*)m_pZStream), MAX_WBITS + 16); if (ret != Z_OK) { free(m_pZStream); m_pZStream = NULL; } } GUnzipStream::~GUnzipStream() { if (m_pZStream) { inflateEnd(((z_stream*)m_pZStream)); free(m_pZStream); } free(m_pOutputBuffer); } void GUnzipStream::Write(const void *pInputBuffer, int iInputBufferLength) { ((z_stream*)m_pZStream)->next_in = (Bytef*)pInputBuffer; ((z_stream*)m_pZStream)->avail_in = iInputBufferLength; } GUnzipStream::EStatus GUnzipStream::Read(const void **pOutputBuffer, int *iOutputBufferLength) { ((z_stream*)m_pZStream)->next_out = (Bytef*)m_pOutputBuffer; ((z_stream*)m_pZStream)->avail_out = m_iBufferSize; *iOutputBufferLength = 0; if (!m_pZStream) { return zlError; } int ret = inflate(((z_stream*)m_pZStream), Z_NO_FLUSH); switch (ret) { case Z_STREAM_END: case Z_OK: *iOutputBufferLength = m_iBufferSize - ((z_stream*)m_pZStream)->avail_out; *pOutputBuffer = m_pOutputBuffer; return ret == Z_STREAM_END ? zlFinished : zlOK; case Z_BUF_ERROR: return zlOK; } return zlError; } #endif nzbget-12.0+dfsg/Util.h000066400000000000000000000252031226450633000147350ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 909 $ * $Date: 2013-11-18 21:37:20 +0100 (Mon, 18 Nov 2013) $ * */ #ifndef UTIL_H #define UTIL_H #ifdef WIN32 #include #include #else #include #endif #ifdef WIN32 extern int optind, opterr; extern char *optarg; int getopt(int argc, char *argv[], char *optstring); #endif class DirBrowser { private: #ifdef WIN32 struct _finddata_t m_FindData; intptr_t m_hFile; bool m_bFirst; #else DIR* m_pDir; struct dirent* m_pFindData; #endif public: DirBrowser(const char* szPath); ~DirBrowser(); const char* Next(); }; class StringBuilder { private: char* m_szBuffer; int m_iBufferSize; int m_iUsedSize; public: StringBuilder(); ~StringBuilder(); void Append(const char* szStr); const char* GetBuffer() { return m_szBuffer; } }; class Util { public: static char* BaseFileName(const char* filename); static void NormalizePathSeparators(char* szPath); static bool LoadFileIntoBuffer(const char* szFileName, char** pBuffer, int* pBufferLength); static bool SaveBufferIntoFile(const char* szFileName, const char* szBuffer, int iBufLen); static bool CreateSparseFile(const char* szFilename, int iSize); static bool TruncateFile(const char* szFilename, int iSize); static void MakeValidFilename(char* szFilename, char cReplaceChar, bool bAllowSlashes); static bool MakeUniqueFilename(char* szDestBufFilename, int iDestBufSize, const char* szDestDir, const char* szBasename); static bool MoveFile(const char* szSrcFilename, const char* szDstFilename); static bool FileExists(const char* szFilename); static bool FileExists(const char* szPath, const char* szFilenameWithoutPath); static bool DirectoryExists(const char* szDirFilename); static bool CreateDirectory(const char* szDirFilename); static bool RemoveDirectory(const char* szDirFilename); static bool DeleteDirectoryWithContent(const char* szDirFilename); static bool ForceDirectories(const char* szPath, char* szErrBuf, int iBufSize); static bool GetCurrentDirectory(char* szBuffer, int iBufSize); static bool SetCurrentDirectory(const char* szDirFilename); static long long FileSize(const char* szFilename); static long long FreeDiskSize(const char* szPath); static bool DirEmpty(const char* szDirFilename); static bool RenameBak(const char* szFilename, const char* szBakPart, bool bRemoveOldExtension, char* szNewNameBuf, int iNewNameBufSize); #ifndef WIN32 static bool ExpandHomePath(const char* szFilename, char* szBuffer, int iBufSize); static void FixExecPermission(const char* szFilename); #endif static void ExpandFileName(const char* szFilename, char* szBuffer, int iBufSize); static void FormatFileSize(char* szBuffer, int iBufLen, long long lFileSize); static bool SameFilename(const char* szFilename1, const char* szFilename2); static char* GetLastErrorMessage(char* szBuffer, int iBufLen); /* * Split command line int arguments. * Uses spaces and single quotation marks as separators. * Returns bool if sucessful or false if bad escaping was detected. * Parameter "argv" may be NULL if only a syntax check is needed. * Parsed parameters returned in Array "argv", which contains at least one element. * The last element in array is NULL. * Restrictions: the number of arguments is limited to 100 and each arguments must * be maximum 1024 chars long. * If these restrictions are exceeded, only first 100 arguments and only first 1024 * for each argument are returned (the functions still returns "true"). */ static bool SplitCommandLine(const char* szCommandLine, char*** argv); static long long JoinInt64(unsigned long Hi, unsigned long Lo); static void SplitInt64(long long Int64, unsigned long* Hi, unsigned long* Lo); /** * Int64ToFloat converts Int64 to float. * Simple (float)Int64 does not work on all compilers, * for example on ARM for NSLU2 (unslung). */ static float Int64ToFloat(long long Int64); static void TrimRight(char* szStr); static char* Trim(char* szStr); static bool EmptyStr(const char* szStr) { return !szStr || !*szStr; } /* replace all occurences of szFrom to szTo in string szStr with a limitation that szTo must be shorter than szFrom */ static char* ReduceStr(char* szStr, const char* szFrom, const char* szTo); /* Calculate Hash using Bob Jenkins (1996) algorithm */ static unsigned int HashBJ96(const char* szBuffer, int iBufSize, unsigned int iInitValue); #ifdef WIN32 static bool RegReadStr(HKEY hKey, const char* szKeyName, const char* szValueName, char* szBuffer, int* iBufLen); #endif /* * Returns program version and revision number as string formatted like "0.7.0-r295". * If revision number is not available only version is returned ("0.7.0"). */ static const char* VersionRevision() { return VersionRevisionBuf; }; /* * Initialize buffer for program version and revision number. * This function must be called during program initialization before any * call to "VersionRevision()". */ static void InitVersionRevision(); static char VersionRevisionBuf[40]; }; class WebUtil { public: static unsigned int DecodeBase64(char* szInputBuffer, int iInputBufferLength, char* szOutputBuffer); /* * Encodes string to be used as content of xml-tag. * Returns new string allocated with malloc, it need to be freed by caller. */ static char* XmlEncode(const char* raw); /* * Decodes string from xml. * The string is decoded on the place overwriting the content of raw-data. */ static void XmlDecode(char* raw); /* * Returns pointer to tag-content and length of content in iValueLength * The returned pointer points to the part of source-string, no additional strings are allocated. */ static const char* XmlFindTag(const char* szXml, const char* szTag, int* pValueLength); /* * Parses tag-content into szValueBuf. */ static bool XmlParseTagValue(const char* szXml, const char* szTag, char* szValueBuf, int iValueBufSize, const char** pTagEnd); /* * Creates JSON-string by replace the certain characters with escape-sequences. * Returns new string allocated with malloc, it need to be freed by caller. */ static char* JsonEncode(const char* raw); /* * Decodes JSON-string. * The string is decoded on the place overwriting the content of raw-data. */ static void JsonDecode(char* raw); /* * Returns pointer to field-content and length of content in iValueLength * The returned pointer points to the part of source-string, no additional strings are allocated. */ static const char* JsonFindField(const char* szJsonText, const char* szFieldName, int* pValueLength); /* * Returns pointer to field-content and length of content in iValueLength * The returned pointer points to the part of source-string, no additional strings are allocated. */ static const char* JsonNextValue(const char* szJsonText, int* pValueLength); /* * Unquote http quoted string. * The string is decoded on the place overwriting the content of raw-data. */ static void HttpUnquote(char* raw); #ifdef WIN32 static bool Utf8ToAnsi(char* szBuffer, int iBufLen); static bool AnsiToUtf8(char* szBuffer, int iBufLen); #endif /* * Converts ISO-8859-1 (aka Latin-1) into UTF-8. * Returns new string allocated with malloc, it needs to be freed by caller. */ static char* Latin1ToUtf8(const char* szStr); static time_t ParseRfc822DateTime(const char* szDateTimeStr); }; class URL { private: char* m_szAddress; char* m_szProtocol; char* m_szUser; char* m_szPassword; char* m_szHost; char* m_szResource; int m_iPort; bool m_bTLS; bool m_bValid; void ParseURL(); public: URL(const char* szAddress); ~URL(); bool IsValid() { return m_bValid; } const char* GetAddress() { return m_szAddress; } const char* GetProtocol() { return m_szProtocol; } const char* GetUser() { return m_szUser; } const char* GetPassword() { return m_szPassword; } const char* GetHost() { return m_szHost; } const char* GetResource() { return m_szResource; } int GetPort() { return m_iPort; } bool GetTLS() { return m_bTLS; } }; class RegEx { private: void* m_pContext; bool m_bValid; void* m_pMatches; int m_iMatchBufSize; public: RegEx(const char *szPattern, int iMatchBufSize = 100); ~RegEx(); bool IsValid() { return m_bValid; } bool Match(const char *szStr); int GetMatchCount(); int GetMatchStart(int index); int GetMatchLen(int index); }; class WildMask { private: char* m_szPattern; bool m_bWantsPositions; int m_iWildCount; int* m_WildStart; int* m_WildLen; int m_iArrLen; void ExpandArray(); public: WildMask(const char *szPattern, bool bWantsPositions = false); ~WildMask(); bool Match(const char *szStr); int GetMatchCount() { return m_iWildCount; } int GetMatchStart(int index) { return m_WildStart[index]; } int GetMatchLen(int index) { return m_WildLen[index]; } }; #ifndef DISABLE_GZIP class ZLib { public: /* * calculates the size required for output buffer */ static unsigned int GZipLen(int iInputBufferLength); /* * returns the size of bytes written to szOutputBuffer or 0 if the buffer is too small or an error occured. */ static unsigned int GZip(const void* szInputBuffer, int iInputBufferLength, void* szOutputBuffer, int iOutputBufferLength); }; class GUnzipStream { public: enum EStatus { zlError, zlFinished, zlOK }; private: void* m_pZStream; void* m_pOutputBuffer; int m_iBufferSize; public: GUnzipStream(int BufferSize); ~GUnzipStream(); /* * set next memory block for uncompression */ void Write(const void *pInputBuffer, int iInputBufferLength); /* * get next uncompressed memory block. * iOutputBufferLength - the size of uncompressed block. if it is "0" the next compressed block must be provided via "Write". */ EStatus Read(const void **pOutputBuffer, int *iOutputBufferLength); }; #endif #endif nzbget-12.0+dfsg/WebDownloader.cpp000066400000000000000000000344261226450633000171160ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2012-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 911 $ * $Date: 2013-11-24 20:29:52 +0100 (Sun, 24 Nov 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #ifdef WIN32 #include #else #include #include #endif #include #include #include "nzbget.h" #include "WebDownloader.h" #include "Log.h" #include "Options.h" #include "Util.h" extern Options* g_pOptions; WebDownloader::WebDownloader() { debug("Creating WebDownloader"); m_szURL = NULL; m_szOutputFilename = NULL; m_pConnection = NULL; m_szInfoName = NULL; m_bConfirmedLength = false; m_eStatus = adUndefined; m_szOriginalFilename = NULL; m_bForce = false; m_bRetry = true; SetLastUpdateTimeNow(); } WebDownloader::~WebDownloader() { debug("Destroying WebDownloader"); free(m_szURL); free(m_szInfoName); free(m_szOutputFilename); free(m_szOriginalFilename); } void WebDownloader::SetOutputFilename(const char* v) { m_szOutputFilename = strdup(v); } void WebDownloader::SetInfoName(const char* v) { m_szInfoName = strdup(v); } void WebDownloader::SetURL(const char * szURL) { free(m_szURL); m_szURL = strdup(szURL); } void WebDownloader::SetStatus(EStatus eStatus) { m_eStatus = eStatus; Notify(NULL); } void WebDownloader::Run() { debug("Entering WebDownloader-loop"); SetStatus(adRunning); int iRemainedDownloadRetries = g_pOptions->GetRetries() > 0 ? g_pOptions->GetRetries() : 1; int iRemainedConnectRetries = iRemainedDownloadRetries > 10 ? iRemainedDownloadRetries : 10; if (!m_bRetry) { iRemainedDownloadRetries = 1; iRemainedConnectRetries = 1; } m_iRedirects = 0; EStatus Status = adFailed; while (!IsStopped() && iRemainedDownloadRetries > 0 && iRemainedConnectRetries > 0) { SetLastUpdateTimeNow(); Status = Download(); if ((((Status == adFailed) && (iRemainedDownloadRetries > 1)) || ((Status == adConnectError) && (iRemainedConnectRetries > 1))) && !IsStopped() && !(!m_bForce && (g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2()))) { detail("Waiting %i sec to retry", g_pOptions->GetRetryInterval()); int msec = 0; while (!IsStopped() && (msec < g_pOptions->GetRetryInterval() * 1000) && !(!m_bForce && (g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2()))) { usleep(100 * 1000); msec += 100; } } if (IsStopped() || (!m_bForce && (g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2()))) { Status = adRetry; break; } if (Status == adFinished || Status == adFatalError || Status == adNotFound) { break; } if (Status == adRedirect) { m_iRedirects++; if (m_iRedirects > 5) { warn("Too many redirects for %s", m_szInfoName); Status = adFailed; break; } } if (Status != adConnectError) { iRemainedDownloadRetries--; } else { iRemainedConnectRetries--; } } if (Status != adFinished && Status != adRetry) { Status = adFailed; } if (Status == adFailed) { if (IsStopped()) { detail("Download %s cancelled", m_szInfoName); } else { error("Download %s failed", m_szInfoName); } } if (Status == adFinished) { detail("Download %s completed", m_szInfoName); } SetStatus(Status); debug("Exiting WebDownloader-loop"); } WebDownloader::EStatus WebDownloader::Download() { EStatus Status = adRunning; URL url(m_szURL); Status = CreateConnection(&url); if (Status != adRunning) { return Status; } m_pConnection->SetSuppressErrors(false); // connection bool bConnected = m_pConnection->Connect(); if (!bConnected || IsStopped()) { FreeConnection(); return adConnectError; } // Okay, we got a Connection. Now start downloading. detail("Downloading %s", m_szInfoName); SendHeaders(&url); Status = DownloadHeaders(); if (Status == adRunning) { Status = DownloadBody(); } if (IsStopped()) { Status = adFailed; } FreeConnection(); if (Status != adFinished) { // Download failed, delete broken output file remove(m_szOutputFilename); } return Status; } WebDownloader::EStatus WebDownloader::CreateConnection(URL *pUrl) { if (!pUrl->IsValid()) { error("URL is not valid: %s", pUrl->GetAddress()); return adFatalError; } int iPort = pUrl->GetPort(); if (iPort == 0 && !strcasecmp(pUrl->GetProtocol(), "http")) { iPort = 80; } if (iPort == 0 && !strcasecmp(pUrl->GetProtocol(), "https")) { iPort = 443; } if (strcasecmp(pUrl->GetProtocol(), "http") && strcasecmp(pUrl->GetProtocol(), "https")) { error("Unsupported protocol in URL: %s", pUrl->GetAddress()); return adFatalError; } #ifdef DISABLE_TLS if (!strcasecmp(pUrl->GetProtocol(), "https")) { error("Program was compiled without TLS/SSL-support. Cannot download using https protocol. URL: %s", pUrl->GetAddress()); return adFatalError; } #endif bool bTLS = !strcasecmp(pUrl->GetProtocol(), "https"); m_pConnection = new Connection(pUrl->GetHost(), iPort, bTLS); return adRunning; } void WebDownloader::SendHeaders(URL *pUrl) { char tmp[1024]; // retrieve file snprintf(tmp, 1024, "GET %s HTTP/1.0\r\n", pUrl->GetResource()); tmp[1024-1] = '\0'; m_pConnection->WriteLine(tmp); snprintf(tmp, 1024, "User-Agent: nzbget/%s\r\n", Util::VersionRevision()); tmp[1024-1] = '\0'; m_pConnection->WriteLine(tmp); snprintf(tmp, 1024, "Host: %s\r\n", pUrl->GetHost()); tmp[1024-1] = '\0'; m_pConnection->WriteLine(tmp); m_pConnection->WriteLine("Accept: */*\r\n"); #ifndef DISABLE_GZIP m_pConnection->WriteLine("Accept-Encoding: gzip\r\n"); #endif m_pConnection->WriteLine("Connection: close\r\n"); m_pConnection->WriteLine("\r\n"); } WebDownloader::EStatus WebDownloader::DownloadHeaders() { EStatus Status = adRunning; m_bConfirmedLength = false; const int LineBufSize = 1024*10; char* szLineBuf = (char*)malloc(LineBufSize); m_iContentLen = -1; bool bFirstLine = true; m_bGZip = false; m_bRedirecting = false; m_bRedirected = false; // Headers while (!IsStopped()) { SetLastUpdateTimeNow(); int iLen = 0; char* line = m_pConnection->ReadLine(szLineBuf, LineBufSize, &iLen); if (bFirstLine) { Status = CheckResponse(szLineBuf); if (Status != adRunning) { break; } bFirstLine = false; } // Have we encountered a timeout? if (!line) { if (!IsStopped()) { warn("URL %s failed: Unexpected end of file", m_szInfoName); } Status = adFailed; break; } debug("Header: %s", line); // detect body of response if (*line == '\r' || *line == '\n') { if (m_iContentLen == -1 && !m_bGZip) { warn("URL %s: Content-Length is not submitted by server, cannot verify whether the file is complete", m_szInfoName); } break; } Util::TrimRight(line); ProcessHeader(line); if (m_bRedirected) { Status = adRedirect; break; } } free(szLineBuf); return Status; } WebDownloader::EStatus WebDownloader::DownloadBody() { EStatus Status = adRunning; m_pOutFile = NULL; bool bEnd = false; const int LineBufSize = 1024*10; char* szLineBuf = (char*)malloc(LineBufSize); int iWrittenLen = 0; #ifndef DISABLE_GZIP m_pGUnzipStream = NULL; if (m_bGZip) { m_pGUnzipStream = new GUnzipStream(1024*10); } #endif // Body while (!IsStopped()) { SetLastUpdateTimeNow(); char* szBuffer; int iLen; m_pConnection->ReadBuffer(&szBuffer, &iLen); if (iLen == 0) { iLen = m_pConnection->TryRecv(szLineBuf, LineBufSize); szBuffer = szLineBuf; } // Have we encountered a timeout? if (iLen <= 0) { if (m_iContentLen == -1 && iWrittenLen > 0) { bEnd = true; break; } if (!IsStopped()) { warn("URL %s failed: Unexpected end of file", m_szInfoName); } Status = adFailed; break; } // write to output file if (!Write(szBuffer, iLen)) { Status = adFatalError; break; } iWrittenLen += iLen; //detect end of file if (iWrittenLen == m_iContentLen || (m_iContentLen == -1 && m_bGZip && m_bConfirmedLength)) { bEnd = true; break; } } free(szLineBuf); #ifndef DISABLE_GZIP delete m_pGUnzipStream; #endif if (m_pOutFile) { fclose(m_pOutFile); } if (!bEnd && Status == adRunning && !IsStopped()) { warn("URL %s failed: file incomplete", m_szInfoName); Status = adFailed; } if (bEnd) { Status = adFinished; } return Status; } WebDownloader::EStatus WebDownloader::CheckResponse(const char* szResponse) { if (!szResponse) { if (!IsStopped()) { warn("URL %s: Connection closed by remote host", m_szInfoName); } return adConnectError; } const char* szHTTPResponse = strchr(szResponse, ' '); if (strncmp(szResponse, "HTTP", 4) || !szHTTPResponse) { warn("URL %s failed: %s", m_szInfoName, szResponse); return adFailed; } szHTTPResponse++; if (!strncmp(szHTTPResponse, "400", 3) || !strncmp(szHTTPResponse, "499", 3)) { warn("URL %s failed: %s", m_szInfoName, szHTTPResponse); return adConnectError; } else if (!strncmp(szHTTPResponse, "404", 3)) { warn("URL %s failed: %s", m_szInfoName, szHTTPResponse); return adNotFound; } else if (!strncmp(szHTTPResponse, "301", 3) || !strncmp(szHTTPResponse, "302", 3)) { m_bRedirecting = true; return adRunning; } else if (!strncmp(szHTTPResponse, "200", 3)) { // OK return adRunning; } else { // unknown error, no special handling warn("URL %s failed: %s", m_szInfoName, szResponse); return adFailed; } } void WebDownloader::ProcessHeader(const char* szLine) { if (!strncasecmp(szLine, "Content-Length: ", 16)) { m_iContentLen = atoi(szLine + 16); m_bConfirmedLength = true; } else if (!strncasecmp(szLine, "Content-Encoding: gzip", 22)) { m_bGZip = true; } else if (!strncasecmp(szLine, "Content-Disposition: ", 21)) { ParseFilename(szLine); } else if (m_bRedirecting && !strncasecmp(szLine, "Location: ", 10)) { ParseRedirect(szLine + 10); m_bRedirected = true; } } void WebDownloader::ParseFilename(const char* szContentDisposition) { // Examples: // Content-Disposition: attachment; filename="fname.ext" // Content-Disposition: attachement;filename=fname.ext // Content-Disposition: attachement;filename=fname.ext; const char *p = strstr(szContentDisposition, "filename"); if (!p) { return; } p = strchr(p, '='); if (!p) { return; } p++; while (*p == ' ') p++; char fname[1024]; strncpy(fname, p, 1024); fname[1024-1] = '\0'; char *pe = fname + strlen(fname) - 1; while ((*pe == ' ' || *pe == '\n' || *pe == '\r' || *pe == ';') && pe > fname) { *pe = '\0'; pe--; } WebUtil::HttpUnquote(fname); free(m_szOriginalFilename); m_szOriginalFilename = strdup(Util::BaseFileName(fname)); debug("OriginalFilename: %s", m_szOriginalFilename); } void WebDownloader::ParseRedirect(const char* szLocation) { const char* szNewURL = szLocation; char szUrlBuf[1024]; URL newUrl(szNewURL); if (!newUrl.IsValid()) { // relative address URL oldUrl(m_szURL); if (oldUrl.GetPort() > 0) { snprintf(szUrlBuf, 1024, "%s://%s:%i%s", oldUrl.GetProtocol(), oldUrl.GetHost(), oldUrl.GetPort(), szNewURL); } else { snprintf(szUrlBuf, 1024, "%s://%s%s", oldUrl.GetProtocol(), oldUrl.GetHost(), szNewURL); } szUrlBuf[1024-1] = '\0'; szNewURL = szUrlBuf; } detail("URL %s redirected to %s", m_szURL, szNewURL); SetURL(szNewURL); } bool WebDownloader::Write(void* pBuffer, int iLen) { if (!m_pOutFile && !PrepareFile()) { return false; } #ifndef DISABLE_GZIP if (m_bGZip) { m_pGUnzipStream->Write(pBuffer, iLen); const void *pOutBuf; int iOutLen = 1; while (iOutLen > 0) { GUnzipStream::EStatus eGZStatus = m_pGUnzipStream->Read(&pOutBuf, &iOutLen); if (eGZStatus == GUnzipStream::zlError) { error("URL %s: GUnzip failed", m_szInfoName); return false; } if (iOutLen > 0 && fwrite(pOutBuf, 1, iOutLen, m_pOutFile) <= 0) { return false; } if (eGZStatus == GUnzipStream::zlFinished) { m_bConfirmedLength = true; return true; } } return true; } else #endif return fwrite(pBuffer, 1, iLen, m_pOutFile) > 0; } bool WebDownloader::PrepareFile() { // prepare file for writing const char* szFilename = m_szOutputFilename; m_pOutFile = fopen(szFilename, "wb"); if (!m_pOutFile) { error("Could not %s file %s", "create", szFilename); return false; } if (g_pOptions->GetWriteBufferSize() > 0) { setvbuf(m_pOutFile, (char *)NULL, _IOFBF, g_pOptions->GetWriteBufferSize()); } return true; } void WebDownloader::LogDebugInfo() { char szTime[50]; #ifdef HAVE_CTIME_R_3 ctime_r(&m_tLastUpdateTime, szTime, 50); #else ctime_r(&m_tLastUpdateTime, szTime); #endif debug(" Web-Download: status=%i, LastUpdateTime=%s, filename=%s", m_eStatus, szTime, Util::BaseFileName(m_szOutputFilename)); } void WebDownloader::Stop() { debug("Trying to stop WebDownloader"); Thread::Stop(); m_mutexConnection.Lock(); if (m_pConnection) { m_pConnection->SetSuppressErrors(true); m_pConnection->Cancel(); } m_mutexConnection.Unlock(); debug("WebDownloader stopped successfully"); } bool WebDownloader::Terminate() { Connection* pConnection = m_pConnection; bool terminated = Kill(); if (terminated && pConnection) { debug("Terminating connection"); pConnection->SetSuppressErrors(true); pConnection->Cancel(); pConnection->Disconnect(); delete pConnection; } return terminated; } void WebDownloader::FreeConnection() { if (m_pConnection) { debug("Releasing connection"); m_mutexConnection.Lock(); if (m_pConnection->GetStatus() == Connection::csCancelled) { m_pConnection->Disconnect(); } delete m_pConnection; m_pConnection = NULL; m_mutexConnection.Unlock(); } } nzbget-12.0+dfsg/WebDownloader.h000066400000000000000000000060521226450633000165550ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2012-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 881 $ * $Date: 2013-10-17 21:35:43 +0200 (Thu, 17 Oct 2013) $ * */ #ifndef WEBDOWNLOADER_H #define WEBDOWNLOADER_H #include #include "Observer.h" #include "Thread.h" #include "Connection.h" #include "Util.h" class WebDownloader : public Thread, public Subject { public: enum EStatus { adUndefined, adRunning, adFinished, adFailed, adRetry, adNotFound, adRedirect, adConnectError, adFatalError }; private: char* m_szURL; char* m_szOutputFilename; Connection* m_pConnection; Mutex m_mutexConnection; EStatus m_eStatus; time_t m_tLastUpdateTime; char* m_szInfoName; FILE* m_pOutFile; int m_iContentLen; bool m_bConfirmedLength; char* m_szOriginalFilename; bool m_bForce; bool m_bRedirecting; bool m_bRedirected; int m_iRedirects; bool m_bGZip; bool m_bRetry; #ifndef DISABLE_GZIP GUnzipStream* m_pGUnzipStream; #endif void SetStatus(EStatus eStatus); bool Write(void* pBuffer, int iLen); bool PrepareFile(); void FreeConnection(); EStatus CheckResponse(const char* szResponse); EStatus CreateConnection(URL *pUrl); void ParseFilename(const char* szContentDisposition); void SendHeaders(URL *pUrl); EStatus DownloadHeaders(); EStatus DownloadBody(); void ParseRedirect(const char* szLocation); protected: virtual void ProcessHeader(const char* szLine); public: WebDownloader(); ~WebDownloader(); EStatus GetStatus() { return m_eStatus; } virtual void Run(); virtual void Stop(); EStatus Download(); bool Terminate(); void SetInfoName(const char* v); const char* GetInfoName() { return m_szInfoName; } void SetURL(const char* szURL); const char* GetOutputFilename() { return m_szOutputFilename; } void SetOutputFilename(const char* v); time_t GetLastUpdateTime() { return m_tLastUpdateTime; } void SetLastUpdateTimeNow() { m_tLastUpdateTime = ::time(NULL); } bool GetConfirmedLength() { return m_bConfirmedLength; } const char* GetOriginalFilename() { return m_szOriginalFilename; } void SetForce(bool bForce) { m_bForce = bForce; } void SetRetry(bool bRetry) { m_bRetry = bRetry; } void LogDebugInfo(); }; #endif nzbget-12.0+dfsg/WebServer.cpp000066400000000000000000000304721226450633000162630ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2012-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 911 $ * $Date: 2013-11-24 20:29:52 +0100 (Sun, 24 Nov 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #ifndef WIN32 #include #endif #include "nzbget.h" #include "WebServer.h" #include "XmlRpc.h" #include "Log.h" #include "Options.h" #include "Util.h" extern Options* g_pOptions; static const char* ERR_HTTP_BAD_REQUEST = "400 Bad Request"; static const char* ERR_HTTP_NOT_FOUND = "404 Not Found"; static const char* ERR_HTTP_SERVICE_UNAVAILABLE = "503 Service Unavailable"; static const int MAX_UNCOMPRESSED_SIZE = 500; //***************************************************************** // WebProcessor WebProcessor::WebProcessor() { m_pConnection = NULL; m_szRequest = NULL; m_szUrl = NULL; m_szOrigin = NULL; } WebProcessor::~WebProcessor() { free(m_szRequest); free(m_szUrl); free(m_szOrigin); } void WebProcessor::SetUrl(const char* szUrl) { m_szUrl = strdup(szUrl); } void WebProcessor::Execute() { m_bGZip =false; char szAuthInfo[1024]; szAuthInfo[0] = '\0'; // reading http header char szBuffer[1024]; int iContentLen = 0; while (char* p = m_pConnection->ReadLine(szBuffer, sizeof(szBuffer), NULL)) { if (char* pe = strrchr(p, '\r')) *pe = '\0'; debug("header=%s", p); if (!strncasecmp(p, "Content-Length: ", 16)) { iContentLen = atoi(p + 16); } if (!strncasecmp(p, "Authorization: Basic ", 21)) { char* szAuthInfo64 = p + 21; if (strlen(szAuthInfo64) > sizeof(szAuthInfo)) { error("Invalid-request: auth-info too big"); return; } szAuthInfo[WebUtil::DecodeBase64(szAuthInfo64, 0, szAuthInfo)] = '\0'; } if (!strncasecmp(p, "Accept-Encoding: ", 17)) { m_bGZip = strstr(p, "gzip"); } if (!strncasecmp(p, "Origin: ", 8)) { m_szOrigin = strdup(p + 8); } if (*p == '\0') { break; } } debug("URL=%s", m_szUrl); debug("Authorization=%s", szAuthInfo); if (m_eHttpMethod == hmPost && iContentLen <= 0) { error("Invalid-request: content length is 0"); return; } if (m_eHttpMethod == hmOptions) { SendOptionsResponse(); return; } // remove subfolder "nzbget" from the path (if exists) // http://localhost:6789/nzbget/username:password/jsonrpc -> http://localhost:6789/username:password/jsonrpc if (!strncmp(m_szUrl, "/nzbget/", 8)) { char* sz_OldUrl = m_szUrl; m_szUrl = strdup(m_szUrl + 7); free(sz_OldUrl); } // http://localhost:6789/nzbget -> http://localhost:6789 if (!strcmp(m_szUrl, "/nzbget")) { char szRedirectURL[1024]; snprintf(szRedirectURL, 1024, "%s/", m_szUrl); szRedirectURL[1024-1] = '\0'; SendRedirectResponse(szRedirectURL); return; } // authorization via URL in format: // http://localhost:6789/username:password/jsonrpc char* pauth1 = strchr(m_szUrl + 1, ':'); char* pauth2 = strchr(m_szUrl + 1, '/'); if (pauth1 && pauth1 < pauth2) { char* pstart = m_szUrl + 1; int iLen = 0; char* pend = strchr(pstart + 1, '/'); if (pend) { iLen = (int)(pend - pstart < (int)sizeof(szAuthInfo) - 1 ? pend - pstart : (int)sizeof(szAuthInfo) - 1); } else { iLen = strlen(pstart); } strncpy(szAuthInfo, pstart, iLen); szAuthInfo[iLen] = '\0'; char* sz_OldUrl = m_szUrl; m_szUrl = strdup(pend); free(sz_OldUrl); } debug("Final URL=%s", m_szUrl); if (strlen(g_pOptions->GetControlPassword()) > 0 && !(strlen(g_pOptions->GetAuthorizedIP()) > 0 && IsAuthorizedIP(m_pConnection->GetRemoteAddr()))) { if (strlen(szAuthInfo) == 0) { SendAuthResponse(); return; } // Authorization char* pw = strchr(szAuthInfo, ':'); if (pw) *pw++ = '\0'; if ((strlen(g_pOptions->GetControlUsername()) > 0 && strcmp(szAuthInfo, g_pOptions->GetControlUsername())) || strcmp(pw, g_pOptions->GetControlPassword())) { warn("Request received on port %i from %s, but username or password invalid (%s:%s)", g_pOptions->GetControlPort(), m_pConnection->GetRemoteAddr(), szAuthInfo, pw); SendAuthResponse(); return; } } if (m_eHttpMethod == hmPost) { // reading http body (request content) m_szRequest = (char*)malloc(iContentLen + 1); m_szRequest[iContentLen] = '\0'; if (!m_pConnection->Recv(m_szRequest, iContentLen)) { error("Invalid-request: could not read data"); return; } debug("Request=%s", m_szRequest); } debug("request received from %s", m_pConnection->GetRemoteAddr()); Dispatch(); } bool WebProcessor::IsAuthorizedIP(const char* szRemoteAddr) { const char* szRemoteIP = m_pConnection->GetRemoteAddr(); // split option AuthorizedIP into tokens and check each token bool bAuthorized = false; char* szAuthorizedIP = strdup(g_pOptions->GetAuthorizedIP()); char* saveptr; char* szIP = strtok_r(szAuthorizedIP, ",;", &saveptr); while (szIP) { szIP = Util::Trim(szIP); if (szIP[0] != '\0' && !strcmp(szIP, szRemoteIP)) { bAuthorized = true; break; } szIP = strtok_r(NULL, ",;", &saveptr); } free(szAuthorizedIP); return bAuthorized; } void WebProcessor::Dispatch() { if (*m_szUrl != '/') { SendErrorResponse(ERR_HTTP_BAD_REQUEST); return; } if (XmlRpcProcessor::IsRpcRequest(m_szUrl)) { XmlRpcProcessor processor; processor.SetRequest(m_szRequest); processor.SetHttpMethod(m_eHttpMethod == hmGet ? XmlRpcProcessor::hmGet : XmlRpcProcessor::hmPost); processor.SetUrl(m_szUrl); processor.Execute(); SendBodyResponse(processor.GetResponse(), strlen(processor.GetResponse()), processor.GetContentType()); return; } if (!g_pOptions->GetWebDir() || strlen(g_pOptions->GetWebDir()) == 0) { SendErrorResponse(ERR_HTTP_SERVICE_UNAVAILABLE); return; } if (m_eHttpMethod != hmGet) { SendErrorResponse(ERR_HTTP_BAD_REQUEST); return; } // for security reasons we allow only characters "0..9 A..Z a..z . - _ /" in the URLs // we also don't allow ".." in the URLs for (char *p = m_szUrl; *p; p++) { if (!((*p >= '0' && *p <= '9') || (*p >= 'A' && *p <= 'Z') || (*p >= 'a' && *p <= 'z') || *p == '.' || *p == '-' || *p == '_' || *p == '/') || (*p == '.' && p[1] == '.')) { SendErrorResponse(ERR_HTTP_NOT_FOUND); return; } } const char *szDefRes = ""; if (m_szUrl[strlen(m_szUrl)-1] == '/') { // default file in directory (if not specified) is "index.html" szDefRes = "index.html"; } char disk_filename[1024]; snprintf(disk_filename, sizeof(disk_filename), "%s%s%s", g_pOptions->GetWebDir(), m_szUrl + 1, szDefRes); disk_filename[sizeof(disk_filename)-1] = '\0'; SendFileResponse(disk_filename); } void WebProcessor::SendAuthResponse() { const char* AUTH_RESPONSE_HEADER = "HTTP/1.0 401 Unauthorized\r\n" "WWW-Authenticate: Basic realm=\"NZBGet\"\r\n" "Connection: close\r\n" "Content-Type: text/plain\r\n" "Server: nzbget-%s\r\n" "\r\n"; char szResponseHeader[1024]; snprintf(szResponseHeader, 1024, AUTH_RESPONSE_HEADER, Util::VersionRevision()); // Send the response answer debug("ResponseHeader=%s", szResponseHeader); m_pConnection->Send(szResponseHeader, strlen(szResponseHeader)); } void WebProcessor::SendOptionsResponse() { const char* OPTIONS_RESPONSE_HEADER = "HTTP/1.1 200 OK\r\n" "Connection: close\r\n" //"Content-Type: plain/text\r\n" "Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n" "Access-Control-Allow-Origin: %s\r\n" "Access-Control-Allow-Credentials: true\r\n" "Access-Control-Max-Age: 86400\r\n" "Access-Control-Allow-Headers: Content-Type, Authorization\r\n" "Server: nzbget-%s\r\n" "\r\n"; char szResponseHeader[1024]; snprintf(szResponseHeader, 1024, OPTIONS_RESPONSE_HEADER, m_szOrigin ? m_szOrigin : "", Util::VersionRevision()); // Send the response answer debug("ResponseHeader=%s", szResponseHeader); m_pConnection->Send(szResponseHeader, strlen(szResponseHeader)); } void WebProcessor::SendErrorResponse(const char* szErrCode) { const char* RESPONSE_HEADER = "HTTP/1.0 %s\r\n" "Connection: close\r\n" "Content-Length: %i\r\n" "Content-Type: text/html\r\n" "Server: nzbget-%s\r\n" "\r\n"; warn("Web-Server: %s, Resource: %s", szErrCode, m_szUrl); char szResponseBody[1024]; snprintf(szResponseBody, 1024, "%sError: %s", szErrCode, szErrCode); int iPageContentLen = strlen(szResponseBody); char szResponseHeader[1024]; snprintf(szResponseHeader, 1024, RESPONSE_HEADER, szErrCode, iPageContentLen, Util::VersionRevision()); // Send the response answer m_pConnection->Send(szResponseHeader, strlen(szResponseHeader)); m_pConnection->Send(szResponseBody, iPageContentLen); } void WebProcessor::SendRedirectResponse(const char* szURL) { const char* REDIRECT_RESPONSE_HEADER = "HTTP/1.0 301 Moved Permanently\r\n" "Location: %s\r\n" "Connection: close\r\n" "Server: nzbget-%s\r\n" "\r\n"; char szResponseHeader[1024]; snprintf(szResponseHeader, 1024, REDIRECT_RESPONSE_HEADER, szURL, Util::VersionRevision()); // Send the response answer debug("ResponseHeader=%s", szResponseHeader); m_pConnection->Send(szResponseHeader, strlen(szResponseHeader)); } void WebProcessor::SendBodyResponse(const char* szBody, int iBodyLen, const char* szContentType) { const char* RESPONSE_HEADER = "HTTP/1.1 200 OK\r\n" "Connection: close\r\n" "Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n" "Access-Control-Allow-Origin: %s\r\n" "Access-Control-Allow-Credentials: true\r\n" "Access-Control-Max-Age: 86400\r\n" "Access-Control-Allow-Headers: Content-Type, Authorization\r\n" "Content-Length: %i\r\n" "%s" // Content-Type: xxx "%s" // Content-Encoding: gzip "Server: nzbget-%s\r\n" "\r\n"; #ifndef DISABLE_GZIP char *szGBuf = NULL; bool bGZip = m_bGZip && iBodyLen > MAX_UNCOMPRESSED_SIZE; if (bGZip) { unsigned int iOutLen = ZLib::GZipLen(iBodyLen); szGBuf = (char*)malloc(iOutLen); int iGZippedLen = ZLib::GZip(szBody, iBodyLen, szGBuf, iOutLen); if (iGZippedLen > 0 && iGZippedLen < iBodyLen) { szBody = szGBuf; iBodyLen = iGZippedLen; } else { free(szGBuf); szGBuf = NULL; bGZip = false; } } #else bool bGZip = false; #endif char szContentTypeHeader[1024]; if (szContentType) { snprintf(szContentTypeHeader, 1024, "Content-Type: %s\r\n", szContentType); } else { szContentTypeHeader[0] = '\0'; } char szResponseHeader[1024]; snprintf(szResponseHeader, 1024, RESPONSE_HEADER, m_szOrigin ? m_szOrigin : "", iBodyLen, szContentTypeHeader, bGZip ? "Content-Encoding: gzip\r\n" : "", Util::VersionRevision()); // Send the request answer m_pConnection->Send(szResponseHeader, strlen(szResponseHeader)); m_pConnection->Send(szBody, iBodyLen); #ifndef DISABLE_GZIP free(szGBuf); #endif } void WebProcessor::SendFileResponse(const char* szFilename) { debug("serving file: %s", szFilename); char *szBody; int iBodyLen; if (!Util::LoadFileIntoBuffer(szFilename, &szBody, &iBodyLen)) { SendErrorResponse(ERR_HTTP_NOT_FOUND); return; } // "LoadFileIntoBuffer" adds a trailing NULL, which we don't need here iBodyLen--; SendBodyResponse(szBody, iBodyLen, DetectContentType(szFilename)); free(szBody); } const char* WebProcessor::DetectContentType(const char* szFilename) { if (const char *szExt = strrchr(szFilename, '.')) { if (!strcasecmp(szExt, ".css")) { return "text/css"; } else if (!strcasecmp(szExt, ".html")) { return "text/html"; } else if (!strcasecmp(szExt, ".js")) { return "application/javascript"; } else if (!strcasecmp(szExt, ".png")) { return "image/png"; } else if (!strcasecmp(szExt, ".jpeg")) { return "image/jpeg"; } else if (!strcasecmp(szExt, ".gif")) { return "image/gif"; } } return NULL; } nzbget-12.0+dfsg/WebServer.h000066400000000000000000000036351226450633000157310ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2012 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 799 $ * $Date: 2013-08-27 00:07:43 +0200 (Tue, 27 Aug 2013) $ * */ #ifndef WEBSERVER_H #define WEBSERVER_H #include "Connection.h" class WebProcessor { public: enum EHttpMethod { hmPost, hmGet, hmOptions }; private: Connection* m_pConnection; char* m_szRequest; char* m_szUrl; EHttpMethod m_eHttpMethod; bool m_bGZip; char* m_szOrigin; void Dispatch(); void SendAuthResponse(); void SendOptionsResponse(); void SendErrorResponse(const char* szErrCode); void SendFileResponse(const char* szFilename); void SendBodyResponse(const char* szBody, int iBodyLen, const char* szContentType); void SendRedirectResponse(const char* szURL); const char* DetectContentType(const char* szFilename); bool IsAuthorizedIP(const char* szRemoteAddr); public: WebProcessor(); ~WebProcessor(); void Execute(); void SetConnection(Connection* pConnection) { m_pConnection = pConnection; } void SetUrl(const char* szUrl); void SetHttpMethod(EHttpMethod eHttpMethod) { m_eHttpMethod = eHttpMethod; } }; #endif nzbget-12.0+dfsg/XmlRpc.cpp000066400000000000000000002550641226450633000155720ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 924 $ * $Date: 2013-12-21 22:39:49 +0100 (Sat, 21 Dec 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #include #ifndef WIN32 #include #endif #include "nzbget.h" #include "XmlRpc.h" #include "Log.h" #include "Options.h" #include "QueueCoordinator.h" #include "UrlCoordinator.h" #include "QueueEditor.h" #include "PrePostProcessor.h" #include "Scanner.h" #include "FeedCoordinator.h" #include "ServerPool.h" #include "Util.h" #include "WebDownloader.h" #include "Maintenance.h" extern Options* g_pOptions; extern QueueCoordinator* g_pQueueCoordinator; extern UrlCoordinator* g_pUrlCoordinator; extern PrePostProcessor* g_pPrePostProcessor; extern Scanner* g_pScanner; extern FeedCoordinator* g_pFeedCoordinator; extern ServerPool* g_pServerPool; extern Maintenance* g_pMaintenance; extern void ExitProc(); extern void Reload(); class ErrorXmlCommand: public XmlCommand { private: int m_iErrCode; const char* m_szErrText; public: ErrorXmlCommand(int iErrCode, const char* szErrText); virtual void Execute(); }; class PauseUnpauseXmlCommand: public XmlCommand { public: enum EPauseAction { paDownload, paDownload2, paPostProcess, paScan }; private: bool m_bPause; EPauseAction m_eEPauseAction; public: PauseUnpauseXmlCommand(bool bPause, EPauseAction eEPauseAction); virtual void Execute(); }; class ScheduleResumeXmlCommand: public XmlCommand { public: virtual void Execute(); }; class ShutdownXmlCommand: public XmlCommand { public: virtual void Execute(); }; class ReloadXmlCommand: public XmlCommand { public: virtual void Execute(); }; class VersionXmlCommand: public XmlCommand { public: virtual void Execute(); }; class DumpDebugXmlCommand: public XmlCommand { public: virtual void Execute(); }; class SetDownloadRateXmlCommand: public XmlCommand { public: virtual void Execute(); }; class StatusXmlCommand: public XmlCommand { public: virtual void Execute(); }; class LogXmlCommand: public XmlCommand { protected: virtual Log::Messages* LockMessages(); virtual void UnlockMessages(); public: virtual void Execute(); }; class NzbInfoXmlCommand: public XmlCommand { protected: void AppendNZBInfoFields(NZBInfo* pNZBInfo); }; class ListFilesXmlCommand: public XmlCommand { public: virtual void Execute(); }; class ListGroupsXmlCommand: public NzbInfoXmlCommand { public: virtual void Execute(); }; class EditQueueXmlCommand: public XmlCommand { public: virtual void Execute(); }; class DownloadXmlCommand: public XmlCommand { public: virtual void Execute(); }; class PostQueueXmlCommand: public NzbInfoXmlCommand { public: virtual void Execute(); }; class WriteLogXmlCommand: public XmlCommand { public: virtual void Execute(); }; class ClearLogXmlCommand: public XmlCommand { public: virtual void Execute(); }; class ScanXmlCommand: public XmlCommand { public: virtual void Execute(); }; class HistoryXmlCommand: public NzbInfoXmlCommand { public: virtual void Execute(); }; class DownloadUrlXmlCommand: public XmlCommand { public: virtual void Execute(); }; class UrlQueueXmlCommand: public XmlCommand { public: virtual void Execute(); }; class ConfigXmlCommand: public XmlCommand { public: virtual void Execute(); }; class LoadConfigXmlCommand: public XmlCommand { public: virtual void Execute(); }; class SaveConfigXmlCommand: public XmlCommand { public: virtual void Execute(); }; class ConfigTemplatesXmlCommand: public XmlCommand { public: virtual void Execute(); }; class ViewFeedXmlCommand: public XmlCommand { private: bool m_bPreview; public: ViewFeedXmlCommand(bool bPreview); virtual void Execute(); }; class FetchFeedXmlCommand: public XmlCommand { public: virtual void Execute(); }; class EditServerXmlCommand: public XmlCommand { public: virtual void Execute(); }; class ReadUrlXmlCommand: public XmlCommand { public: virtual void Execute(); }; class CheckUpdatesXmlCommand: public XmlCommand { public: virtual void Execute(); }; class StartUpdateXmlCommand: public XmlCommand { public: virtual void Execute(); }; class LogUpdateXmlCommand: public LogXmlCommand { protected: virtual Log::Messages* LockMessages(); virtual void UnlockMessages(); }; //***************************************************************** // XmlRpcProcessor XmlRpcProcessor::XmlRpcProcessor() { m_szRequest = NULL; m_eProtocol = rpUndefined; m_eHttpMethod = hmPost; m_szUrl = NULL; m_szContentType = NULL; } XmlRpcProcessor::~XmlRpcProcessor() { free(m_szUrl); } void XmlRpcProcessor::SetUrl(const char* szUrl) { m_szUrl = strdup(szUrl); } bool XmlRpcProcessor::IsRpcRequest(const char* szUrl) { return !strcmp(szUrl, "/xmlrpc") || !strncmp(szUrl, "/xmlrpc/", 8) || !strcmp(szUrl, "/jsonrpc") || !strncmp(szUrl, "/jsonrpc/", 9) || !strcmp(szUrl, "/jsonprpc") || !strncmp(szUrl, "/jsonprpc/", 10); } void XmlRpcProcessor::Execute() { m_eProtocol = rpUndefined; if (!strcmp(m_szUrl, "/xmlrpc") || !strncmp(m_szUrl, "/xmlrpc/", 8)) { m_eProtocol = XmlRpcProcessor::rpXmlRpc; } else if (!strcmp(m_szUrl, "/jsonrpc") || !strncmp(m_szUrl, "/jsonrpc/", 9)) { m_eProtocol = rpJsonRpc; } else if (!strcmp(m_szUrl, "/jsonprpc") || !strncmp(m_szUrl, "/jsonprpc/", 10)) { m_eProtocol = rpJsonPRpc; } else { error("internal error: invalid rpc-request: %s", m_szUrl); return; } Dispatch(); } void XmlRpcProcessor::Dispatch() { char* szRequest = m_szRequest; char szMethodName[100]; szMethodName[0] = '\0'; if (m_eHttpMethod == hmGet) { szRequest = m_szUrl + 1; char* pstart = strchr(szRequest, '/'); if (pstart) { char* pend = strchr(pstart + 1, '?'); if (pend) { int iLen = (int)(pend - pstart - 1 < (int)sizeof(szMethodName) - 1 ? pend - pstart - 1 : (int)sizeof(szMethodName) - 1); strncpy(szMethodName, pstart + 1, iLen); szMethodName[iLen] = '\0'; szRequest = pend + 1; } else { strncpy(szMethodName, pstart + 1, sizeof(szMethodName)); szMethodName[sizeof(szMethodName) - 1] = '\0'; szRequest = szRequest + strlen(szRequest); } } } else if (m_eProtocol == rpXmlRpc) { WebUtil::XmlParseTagValue(m_szRequest, "methodName", szMethodName, sizeof(szMethodName), NULL); } else if (m_eProtocol == rpJsonRpc) { int iValueLen = 0; if (const char* szMethodPtr = WebUtil::JsonFindField(m_szRequest, "method", &iValueLen)) { strncpy(szMethodName, szMethodPtr + 1, iValueLen - 2); szMethodName[iValueLen - 2] = '\0'; } } debug("MethodName=%s", szMethodName); if (!strcasecmp(szMethodName, "system.multicall") && m_eProtocol == rpXmlRpc && m_eHttpMethod == hmPost) { MutliCall(); } else { XmlCommand* command = CreateCommand(szMethodName); command->SetRequest(szRequest); command->SetProtocol(m_eProtocol); command->SetHttpMethod(m_eHttpMethod); command->PrepareParams(); command->Execute(); BuildResponse(command->GetResponse(), command->GetCallbackFunc(), command->GetFault()); delete command; } } void XmlRpcProcessor::MutliCall() { bool bError = false; StringBuilder cStringBuilder; cStringBuilder.Append(""); char* szRequestPtr = m_szRequest; char* szCallEnd = strstr(szRequestPtr, ""); while (szCallEnd) { *szCallEnd = '\0'; debug("MutliCall, request=%s", szRequestPtr); char* szNameEnd = strstr(szRequestPtr, ""); if (!szNameEnd) { bError = true; break; } char szMethodName[100]; szMethodName[0] = '\0'; WebUtil::XmlParseTagValue(szNameEnd, "string", szMethodName, sizeof(szMethodName), NULL); debug("MutliCall, MethodName=%s", szMethodName); XmlCommand* command = CreateCommand(szMethodName); command->SetRequest(szRequestPtr); command->Execute(); debug("MutliCall, Response=%s", command->GetResponse()); bool bFault = !strncmp(command->GetResponse(), "", 7); bool bArray = !bFault && !strncmp(command->GetResponse(), "", 7); if (!bFault && !bArray) { cStringBuilder.Append(""); } cStringBuilder.Append(""); cStringBuilder.Append(command->GetResponse()); cStringBuilder.Append(""); if (!bFault && !bArray) { cStringBuilder.Append(""); } delete command; szRequestPtr = szCallEnd + 9; //strlen("") szCallEnd = strstr(szRequestPtr, ""); } if (bError) { XmlCommand* command = new ErrorXmlCommand(4, "Parse error"); command->SetRequest(m_szRequest); command->SetProtocol(rpXmlRpc); command->PrepareParams(); command->Execute(); BuildResponse(command->GetResponse(), "", command->GetFault()); delete command; } else { cStringBuilder.Append(""); BuildResponse(cStringBuilder.GetBuffer(), "", false); } } void XmlRpcProcessor::BuildResponse(const char* szResponse, const char* szCallbackFunc, bool bFault) { const char XML_HEADER[] = "\n\n"; const char XML_FOOTER[] = ""; const char XML_OK_OPEN[] = ""; const char XML_OK_CLOSE[] = "\n"; const char XML_FAULT_OPEN[] = ""; const char XML_FAULT_CLOSE[] = "\n"; const char JSON_HEADER[] = "{\n\"version\" : \"1.1\",\n"; const char JSON_FOOTER[] = "\n}"; const char JSON_OK_OPEN[] = "\"result\" : "; const char JSON_OK_CLOSE[] = ""; const char JSON_FAULT_OPEN[] = "\"error\" : "; const char JSON_FAULT_CLOSE[] = ""; const char JSONP_CALLBACK_HEADER[] = "("; const char JSONP_CALLBACK_FOOTER[] = ")"; bool bXmlRpc = m_eProtocol == rpXmlRpc; const char* szCallbackHeader = m_eProtocol == rpJsonPRpc ? JSONP_CALLBACK_HEADER : ""; const char* szHeader = bXmlRpc ? XML_HEADER : JSON_HEADER; const char* szFooter = bXmlRpc ? XML_FOOTER : JSON_FOOTER; const char* szOpenTag = bFault ? (bXmlRpc ? XML_FAULT_OPEN : JSON_FAULT_OPEN) : (bXmlRpc ? XML_OK_OPEN : JSON_OK_OPEN); const char* szCloseTag = bFault ? (bXmlRpc ? XML_FAULT_CLOSE : JSON_FAULT_CLOSE ) : (bXmlRpc ? XML_OK_CLOSE : JSON_OK_CLOSE); const char* szCallbackFooter = m_eProtocol == rpJsonPRpc ? JSONP_CALLBACK_FOOTER : ""; debug("Response=%s", szResponse); if (szCallbackFunc) { m_cResponse.Append(szCallbackFunc); } m_cResponse.Append(szCallbackHeader); m_cResponse.Append(szHeader); m_cResponse.Append(szOpenTag); m_cResponse.Append(szResponse); m_cResponse.Append(szCloseTag); m_cResponse.Append(szFooter); m_cResponse.Append(szCallbackFooter); m_szContentType = bXmlRpc ? "text/xml" : "application/json"; } XmlCommand* XmlRpcProcessor::CreateCommand(const char* szMethodName) { XmlCommand* command = NULL; if (!strcasecmp(szMethodName, "pause") || !strcasecmp(szMethodName, "pausedownload")) { command = new PauseUnpauseXmlCommand(true, PauseUnpauseXmlCommand::paDownload); } else if (!strcasecmp(szMethodName, "resume") || !strcasecmp(szMethodName, "resumedownload")) { command = new PauseUnpauseXmlCommand(false, PauseUnpauseXmlCommand::paDownload); } else if (!strcasecmp(szMethodName, "pausedownload2")) { command = new PauseUnpauseXmlCommand(true, PauseUnpauseXmlCommand::paDownload2); } else if (!strcasecmp(szMethodName, "resumedownload2")) { command = new PauseUnpauseXmlCommand(false, PauseUnpauseXmlCommand::paDownload2); } else if (!strcasecmp(szMethodName, "shutdown")) { command = new ShutdownXmlCommand(); } else if (!strcasecmp(szMethodName, "reload")) { command = new ReloadXmlCommand(); } else if (!strcasecmp(szMethodName, "version")) { command = new VersionXmlCommand(); } else if (!strcasecmp(szMethodName, "dump")) { command = new DumpDebugXmlCommand(); } else if (!strcasecmp(szMethodName, "rate")) { command = new SetDownloadRateXmlCommand(); } else if (!strcasecmp(szMethodName, "status")) { command = new StatusXmlCommand(); } else if (!strcasecmp(szMethodName, "log")) { command = new LogXmlCommand(); } else if (!strcasecmp(szMethodName, "listfiles")) { command = new ListFilesXmlCommand(); } else if (!strcasecmp(szMethodName, "listgroups")) { command = new ListGroupsXmlCommand(); } else if (!strcasecmp(szMethodName, "editqueue")) { command = new EditQueueXmlCommand(); } else if (!strcasecmp(szMethodName, "append")) { command = new DownloadXmlCommand(); } else if (!strcasecmp(szMethodName, "postqueue")) { command = new PostQueueXmlCommand(); } else if (!strcasecmp(szMethodName, "writelog")) { command = new WriteLogXmlCommand(); } else if (!strcasecmp(szMethodName, "clearlog")) { command = new ClearLogXmlCommand(); } else if (!strcasecmp(szMethodName, "scan")) { command = new ScanXmlCommand(); } else if (!strcasecmp(szMethodName, "pausepost")) { command = new PauseUnpauseXmlCommand(true, PauseUnpauseXmlCommand::paPostProcess); } else if (!strcasecmp(szMethodName, "resumepost")) { command = new PauseUnpauseXmlCommand(false, PauseUnpauseXmlCommand::paPostProcess); } else if (!strcasecmp(szMethodName, "pausescan")) { command = new PauseUnpauseXmlCommand(true, PauseUnpauseXmlCommand::paScan); } else if (!strcasecmp(szMethodName, "resumescan")) { command = new PauseUnpauseXmlCommand(false, PauseUnpauseXmlCommand::paScan); } else if (!strcasecmp(szMethodName, "scheduleresume")) { command = new ScheduleResumeXmlCommand(); } else if (!strcasecmp(szMethodName, "history")) { command = new HistoryXmlCommand(); } else if (!strcasecmp(szMethodName, "appendurl")) { command = new DownloadUrlXmlCommand(); } else if (!strcasecmp(szMethodName, "urlqueue")) { command = new UrlQueueXmlCommand(); } else if (!strcasecmp(szMethodName, "config")) { command = new ConfigXmlCommand(); } else if (!strcasecmp(szMethodName, "loadconfig")) { command = new LoadConfigXmlCommand(); } else if (!strcasecmp(szMethodName, "saveconfig")) { command = new SaveConfigXmlCommand(); } else if (!strcasecmp(szMethodName, "configtemplates")) { command = new ConfigTemplatesXmlCommand(); } else if (!strcasecmp(szMethodName, "viewfeed")) { command = new ViewFeedXmlCommand(false); } else if (!strcasecmp(szMethodName, "previewfeed")) { command = new ViewFeedXmlCommand(true); } else if (!strcasecmp(szMethodName, "fetchfeed")) { command = new FetchFeedXmlCommand(); } else if (!strcasecmp(szMethodName, "editserver")) { command = new EditServerXmlCommand(); } else if (!strcasecmp(szMethodName, "readurl")) { command = new ReadUrlXmlCommand(); } else if (!strcasecmp(szMethodName, "checkupdates")) { command = new CheckUpdatesXmlCommand(); } else if (!strcasecmp(szMethodName, "startupdate")) { command = new StartUpdateXmlCommand(); } else if (!strcasecmp(szMethodName, "logupdate")) { command = new LogUpdateXmlCommand(); } else { command = new ErrorXmlCommand(1, "Invalid procedure"); } return command; } //***************************************************************** // Base command XmlCommand::XmlCommand() { m_szRequest = NULL; m_szRequestPtr = NULL; m_szCallbackFunc = NULL; m_bFault = false; m_eProtocol = XmlRpcProcessor::rpUndefined; } bool XmlCommand::IsJson() { return m_eProtocol == XmlRpcProcessor::rpJsonRpc || m_eProtocol == XmlRpcProcessor::rpJsonPRpc; } void XmlCommand::AppendResponse(const char* szPart) { m_StringBuilder.Append(szPart); } void XmlCommand::BuildErrorResponse(int iErrCode, const char* szErrText, ...) { const char* XML_RESPONSE_ERROR_BODY = "\n" "faultCode%i\n" "faultString%s\n" "\n"; const char* JSON_RESPONSE_ERROR_BODY = "{\n" "\"name\" : \"JSONRPCError\",\n" "\"code\" : %i,\n" "\"message\" : \"%s\"\n" "}"; char szFullText[1024]; va_list ap; va_start(ap, szErrText); vsnprintf(szFullText, 1024, szErrText, ap); szFullText[1024-1] = '\0'; va_end(ap); char* xmlText = EncodeStr(szFullText); char szContent[1024]; snprintf(szContent, 1024, IsJson() ? JSON_RESPONSE_ERROR_BODY : XML_RESPONSE_ERROR_BODY, iErrCode, xmlText); szContent[1024-1] = '\0'; free(xmlText); AppendResponse(szContent); m_bFault = true; } void XmlCommand::BuildBoolResponse(bool bOK) { const char* XML_RESPONSE_BOOL_BODY = "%s"; const char* JSON_RESPONSE_BOOL_BODY = "%s"; char szContent[1024]; snprintf(szContent, 1024, IsJson() ? JSON_RESPONSE_BOOL_BODY : XML_RESPONSE_BOOL_BODY, BoolToStr(bOK)); szContent[1024-1] = '\0'; AppendResponse(szContent); } void XmlCommand::PrepareParams() { if (IsJson() && m_eHttpMethod == XmlRpcProcessor::hmPost) { char* szParams = strstr(m_szRequestPtr, "\"params\""); if (!szParams) { m_szRequestPtr[0] = '\0'; return; } m_szRequestPtr = szParams + 8; // strlen("\"params\"") } if (m_eProtocol == XmlRpcProcessor::rpJsonPRpc) { NextParamAsStr(&m_szCallbackFunc); } } bool XmlCommand::NextParamAsInt(int* iValue) { if (m_eHttpMethod == XmlRpcProcessor::hmGet) { char* szParam = strchr(m_szRequestPtr, '='); if (!szParam) { return false; } *iValue = atoi(szParam + 1); m_szRequestPtr = szParam + 1; return true; } else if (IsJson()) { int iLen = 0; char* szParam = (char*)WebUtil::JsonNextValue(m_szRequestPtr, &iLen); if (!szParam || !strchr("-+0123456789", *szParam)) { return false; } *iValue = atoi(szParam); m_szRequestPtr = szParam + iLen + 1; return true; } else { int iLen = 0; int iTagLen = 4; //strlen(""); char* szParam = (char*)WebUtil::XmlFindTag(m_szRequestPtr, "i4", &iLen); if (!szParam) { szParam = (char*)WebUtil::XmlFindTag(m_szRequestPtr, "int", &iLen); iTagLen = 5; //strlen(""); } if (!szParam || !strchr("-+0123456789", *szParam)) { return false; } *iValue = atoi(szParam); m_szRequestPtr = szParam + iLen + iTagLen; return true; } } bool XmlCommand::NextParamAsBool(bool* bValue) { if (m_eHttpMethod == XmlRpcProcessor::hmGet) { char* szParam; if (!NextParamAsStr(&szParam)) { return false; } if (IsJson()) { if (!strcmp(szParam, "true")) { *bValue = true; return true; } else if (!strcmp(szParam, "false")) { *bValue = false; return true; } } else { *bValue = szParam[0] == '1'; return true; } return false; } else if (IsJson()) { int iLen = 0; char* szParam = (char*)WebUtil::JsonNextValue(m_szRequestPtr, &iLen); if (!szParam) { return false; } if (iLen == 4 && !strncmp(szParam, "true", 4)) { *bValue = true; m_szRequestPtr = szParam + iLen + 1; return true; } else if (iLen == 5 && !strncmp(szParam, "false", 5)) { *bValue = false; m_szRequestPtr = szParam + iLen + 1; return true; } else { return false; } } else { int iLen = 0; char* szParam = (char*)WebUtil::XmlFindTag(m_szRequestPtr, "boolean", &iLen); if (!szParam) { return false; } *bValue = szParam[0] == '1'; m_szRequestPtr = szParam + iLen + 9; //strlen(""); return true; } } bool XmlCommand::NextParamAsStr(char** szValue) { if (m_eHttpMethod == XmlRpcProcessor::hmGet) { char* szParam = strchr(m_szRequestPtr, '='); if (!szParam) { return false; } szParam++; // skip '=' int iLen = 0; char* szParamEnd = strchr(m_szRequestPtr, '&'); if (szParamEnd) { iLen = (int)(szParamEnd - szParam); szParam[iLen] = '\0'; } else { iLen = strlen(szParam) - 1; } m_szRequestPtr = szParam + iLen + 1; *szValue = szParam; return true; } else if (IsJson()) { int iLen = 0; char* szParam = (char*)WebUtil::JsonNextValue(m_szRequestPtr, &iLen); if (!szParam || iLen < 2 || szParam[0] != '"' || szParam[iLen - 1] != '"') { return false; } szParam++; // skip first '"' szParam[iLen - 2] = '\0'; // skip last '"' m_szRequestPtr = szParam + iLen; *szValue = szParam; return true; } else { int iLen = 0; char* szParam = (char*)WebUtil::XmlFindTag(m_szRequestPtr, "string", &iLen); if (!szParam) { return false; } szParam[iLen] = '\0'; m_szRequestPtr = szParam + iLen + 8; //strlen("") *szValue = szParam; return true; } } const char* XmlCommand::BoolToStr(bool bValue) { return IsJson() ? (bValue ? "true" : "false") : (bValue ? "1" : "0"); } char* XmlCommand::EncodeStr(const char* szStr) { if (!szStr) { return strdup(""); } if (IsJson()) { return WebUtil::JsonEncode(szStr); } else { return WebUtil::XmlEncode(szStr); } } void XmlCommand::DecodeStr(char* szStr) { if (IsJson()) { WebUtil::JsonDecode(szStr); } else { WebUtil::XmlDecode(szStr); } } bool XmlCommand::CheckSafeMethod() { bool bSafe = m_eHttpMethod == XmlRpcProcessor::hmPost || m_eProtocol == XmlRpcProcessor::rpJsonPRpc; if (!bSafe) { BuildErrorResponse(4, "Not safe procedure for HTTP-Method GET. Use Method POST instead"); } return bSafe; } //***************************************************************** // Commands ErrorXmlCommand::ErrorXmlCommand(int iErrCode, const char* szErrText) { m_iErrCode = iErrCode; m_szErrText = szErrText; } void ErrorXmlCommand::Execute() { error("Received unsupported request: %s", m_szErrText); BuildErrorResponse(m_iErrCode, m_szErrText); } PauseUnpauseXmlCommand::PauseUnpauseXmlCommand(bool bPause, EPauseAction eEPauseAction) { m_bPause = bPause; m_eEPauseAction = eEPauseAction; } void PauseUnpauseXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } bool bOK = true; g_pOptions->SetResumeTime(0); switch (m_eEPauseAction) { case paDownload: g_pOptions->SetPauseDownload(m_bPause); break; case paDownload2: g_pOptions->SetPauseDownload2(m_bPause); break; case paPostProcess: g_pOptions->SetPausePostProcess(m_bPause); break; case paScan: g_pOptions->SetPauseScan(m_bPause); break; default: bOK = false; } BuildBoolResponse(bOK); } // bool scheduleresume(int Seconds) void ScheduleResumeXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } int iSeconds = 0; if (!NextParamAsInt(&iSeconds) || iSeconds < 0) { BuildErrorResponse(2, "Invalid parameter"); return; } time_t tCurTime = time(NULL); g_pOptions->SetResumeTime(tCurTime + iSeconds); BuildBoolResponse(true); } void ShutdownXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } BuildBoolResponse(true); ExitProc(); } void ReloadXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } BuildBoolResponse(true); Reload(); } void VersionXmlCommand::Execute() { const char* XML_RESPONSE_STRING_BODY = "%s"; const char* JSON_RESPONSE_STRING_BODY = "\"%s\""; char szContent[1024]; snprintf(szContent, 1024, IsJson() ? JSON_RESPONSE_STRING_BODY : XML_RESPONSE_STRING_BODY, Util::VersionRevision()); szContent[1024-1] = '\0'; AppendResponse(szContent); } void DumpDebugXmlCommand::Execute() { g_pQueueCoordinator->LogDebugInfo(); g_pUrlCoordinator->LogDebugInfo(); g_pFeedCoordinator->LogDebugInfo(); BuildBoolResponse(true); } void SetDownloadRateXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } int iRate = 0; if (!NextParamAsInt(&iRate) || iRate < 0) { BuildErrorResponse(2, "Invalid parameter"); return; } g_pOptions->SetDownloadRate(iRate * 1024); BuildBoolResponse(true); } void StatusXmlCommand::Execute() { const char* XML_STATUS_START = "\n" "RemainingSizeLo%u\n" "RemainingSizeHi%u\n" "RemainingSizeMB%i\n" "DownloadedSizeLo%u\n" "DownloadedSizeHi%u\n" "DownloadedSizeMB%i\n" "DownloadRate%i\n" "AverageDownloadRate%i\n" "DownloadLimit%i\n" "ThreadCount%i\n" "ParJobCount%i\n" // deprecated (renamed to PostJobCount) "PostJobCount%i\n" "UrlCount%i\n" "UpTimeSec%i\n" "DownloadTimeSec%i\n" "ServerPaused%s\n" // deprecated (renamed to DownloadPaused) "DownloadPaused%s\n" "Download2Paused%s\n" "ServerStandBy%s\n" "PostPaused%s\n" "ScanPaused%s\n" "FreeDiskSpaceLo%u\n" "FreeDiskSpaceHi%u\n" "FreeDiskSpaceMB%i\n" "ServerTime%i\n" "ResumeTime%i\n" "FeedActive%s\n" "NewsServers\n"; const char* XML_STATUS_END = "\n" "\n"; const char* JSON_STATUS_START = "{\n" "\"RemainingSizeLo\" : %u,\n" "\"RemainingSizeHi\" : %u,\n" "\"RemainingSizeMB\" : %i,\n" "\"DownloadedSizeLo\" : %u,\n" "\"DownloadedSizeHi\" : %u,\n" "\"DownloadedSizeMB\" : %i,\n" "\"DownloadRate\" : %i,\n" "\"AverageDownloadRate\" : %i,\n" "\"DownloadLimit\" : %i,\n" "\"ThreadCount\" : %i,\n" "\"ParJobCount\" : %i,\n" // deprecated (renamed to PostJobCount) "\"PostJobCount\" : %i,\n" "\"UrlCount\" : %i,\n" "\"UpTimeSec\" : %i,\n" "\"DownloadTimeSec\" : %i,\n" "\"ServerPaused\" : %s,\n" // deprecated (renamed to DownloadPaused) "\"DownloadPaused\" : %s,\n" "\"Download2Paused\" : %s,\n" "\"ServerStandBy\" : %s,\n" "\"PostPaused\" : %s,\n" "\"ScanPaused\" : %s,\n" "\"FreeDiskSpaceLo\" : %u,\n" "\"FreeDiskSpaceHi\" : %u,\n" "\"FreeDiskSpaceMB\" : %i,\n" "\"ServerTime\" : %i,\n" "\"ResumeTime\" : %i,\n" "\"FeedActive\" : %s,\n" "\"NewsServers\" : [\n"; const char* JSON_STATUS_END = "]\n" "}"; const char* XML_NEWSSERVER_ITEM = "\n" "ID%i\n" "Active%s\n" "\n"; const char* JSON_NEWSSERVER_ITEM = "{\n" "\"ID\" : %i,\n" "\"Active\" : %s\n" "}"; unsigned long iRemainingSizeHi, iRemainingSizeLo; int iDownloadRate = (int)(g_pQueueCoordinator->CalcCurrentDownloadSpeed()); long long iRemainingSize = g_pQueueCoordinator->CalcRemainingSize(); Util::SplitInt64(iRemainingSize, &iRemainingSizeHi, &iRemainingSizeLo); int iRemainingMBytes = (int)(iRemainingSize / 1024 / 1024); int iDownloadLimit = (int)(g_pOptions->GetDownloadRate()); bool bDownloadPaused = g_pOptions->GetPauseDownload(); bool bDownload2Paused = g_pOptions->GetPauseDownload2(); bool bPostPaused = g_pOptions->GetPausePostProcess(); bool bScanPaused = g_pOptions->GetPauseScan(); int iThreadCount = Thread::GetThreadCount() - 1; // not counting itself DownloadQueue *pDownloadQueue = g_pQueueCoordinator->LockQueue(); int iPostJobCount = pDownloadQueue->GetPostQueue()->size(); int iUrlCount = pDownloadQueue->GetUrlQueue()->size(); g_pQueueCoordinator->UnlockQueue(); unsigned long iDownloadedSizeHi, iDownloadedSizeLo; int iUpTimeSec, iDownloadTimeSec; long long iAllBytes; bool bServerStandBy; g_pQueueCoordinator->CalcStat(&iUpTimeSec, &iDownloadTimeSec, &iAllBytes, &bServerStandBy); int iDownloadedMBytes = (int)(iAllBytes / 1024 / 1024); Util::SplitInt64(iAllBytes, &iDownloadedSizeHi, &iDownloadedSizeLo); int iAverageDownloadRate = (int)(iDownloadTimeSec > 0 ? iAllBytes / iDownloadTimeSec : 0); unsigned long iFreeDiskSpaceHi, iFreeDiskSpaceLo; long long iFreeDiskSpace = Util::FreeDiskSize(g_pOptions->GetDestDir()); Util::SplitInt64(iFreeDiskSpace, &iFreeDiskSpaceHi, &iFreeDiskSpaceLo); int iFreeDiskSpaceMB = (int)(iFreeDiskSpace / 1024 / 1024); int iServerTime = time(NULL); int iResumeTime = g_pOptions->GetResumeTime(); bool bFeedActive = g_pFeedCoordinator->HasActiveDownloads(); char szContent[3072]; snprintf(szContent, 3072, IsJson() ? JSON_STATUS_START : XML_STATUS_START, iRemainingSizeLo, iRemainingSizeHi, iRemainingMBytes, iDownloadedSizeLo, iDownloadedSizeHi, iDownloadedMBytes, iDownloadRate, iAverageDownloadRate, iDownloadLimit, iThreadCount, iPostJobCount, iPostJobCount, iUrlCount, iUpTimeSec, iDownloadTimeSec, BoolToStr(bDownloadPaused), BoolToStr(bDownloadPaused), BoolToStr(bDownload2Paused), BoolToStr(bServerStandBy), BoolToStr(bPostPaused), BoolToStr(bScanPaused), iFreeDiskSpaceLo, iFreeDiskSpaceHi, iFreeDiskSpaceMB, iServerTime, iResumeTime, BoolToStr(bFeedActive)); szContent[3072-1] = '\0'; AppendResponse(szContent); int index = 0; for (Servers::iterator it = g_pServerPool->GetServers()->begin(); it != g_pServerPool->GetServers()->end(); it++) { NewsServer* pServer = *it; snprintf(szContent, sizeof(szContent), IsJson() ? JSON_NEWSSERVER_ITEM : XML_NEWSSERVER_ITEM, pServer->GetID(), BoolToStr(pServer->GetActive())); szContent[3072-1] = '\0'; if (IsJson() && index++ > 0) { AppendResponse(",\n"); } AppendResponse(szContent); } AppendResponse(IsJson() ? JSON_STATUS_END : XML_STATUS_END); } // struct[] log(idfrom, entries) void LogXmlCommand::Execute() { int iIDFrom = 0; int iNrEntries = 0; if (!NextParamAsInt(&iIDFrom) || !NextParamAsInt(&iNrEntries) || (iNrEntries > 0 && iIDFrom > 0)) { BuildErrorResponse(2, "Invalid parameter"); return; } debug("iIDFrom=%i", iIDFrom); debug("iNrEntries=%i", iNrEntries); AppendResponse(IsJson() ? "[\n" : "\n"); Log::Messages* pMessages = LockMessages(); int iStart = pMessages->size(); if (iNrEntries > 0) { if (iNrEntries > (int)pMessages->size()) { iNrEntries = pMessages->size(); } iStart = pMessages->size() - iNrEntries; } if (iIDFrom > 0 && !pMessages->empty()) { iNrEntries = pMessages->size(); iStart = iIDFrom - pMessages->front()->GetID(); if (iStart < 0) { iStart = 0; } } const char* XML_LOG_ITEM = "\n" "ID%i\n" "Kind%s\n" "Time%i\n" "Text%s\n" "\n"; const char* JSON_LOG_ITEM = "{\n" "\"ID\" : %i,\n" "\"Kind\" : \"%s\",\n" "\"Time\" : %i,\n" "\"Text\" : \"%s\"\n" "}"; const char* szMessageType[] = { "INFO", "WARNING", "ERROR", "DEBUG", "DETAIL" }; int iItemBufSize = 10240; char* szItemBuf = (char*)malloc(iItemBufSize); int index = 0; for (unsigned int i = (unsigned int)iStart; i < pMessages->size(); i++) { Message* pMessage = (*pMessages)[i]; char* xmltext = EncodeStr(pMessage->GetText()); snprintf(szItemBuf, iItemBufSize, IsJson() ? JSON_LOG_ITEM : XML_LOG_ITEM, pMessage->GetID(), szMessageType[pMessage->GetKind()], pMessage->GetTime(), xmltext); szItemBuf[iItemBufSize-1] = '\0'; free(xmltext); if (IsJson() && index++ > 0) { AppendResponse(",\n"); } AppendResponse(szItemBuf); } free(szItemBuf); UnlockMessages(); AppendResponse(IsJson() ? "\n]" : "\n"); } Log::Messages* LogXmlCommand::LockMessages() { return g_pLog->LockMessages(); } void LogXmlCommand::UnlockMessages() { g_pLog->UnlockMessages(); } // struct[] listfiles(int IDFrom, int IDTo, int NZBID) // For backward compatibility with 0.8 parameter "NZBID" is optional void ListFilesXmlCommand::Execute() { int iIDStart = 0; int iIDEnd = 0; if (NextParamAsInt(&iIDStart) && (!NextParamAsInt(&iIDEnd) || iIDEnd < iIDStart)) { BuildErrorResponse(2, "Invalid parameter"); return; } // For backward compatibility with 0.8 parameter "NZBID" is optional (error checking omitted) int iNZBID = 0; NextParamAsInt(&iNZBID); if (iNZBID > 0 && (iIDStart != 0 || iIDEnd != 0)) { BuildErrorResponse(2, "Invalid parameter"); return; } debug("iIDStart=%i", iIDStart); debug("iIDEnd=%i", iIDEnd); AppendResponse(IsJson() ? "[\n" : "\n"); DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue(); const char* XML_LIST_ITEM = "\n" "ID%i\n" "FileSizeLo%u\n" "FileSizeHi%u\n" "RemainingSizeLo%u\n" "RemainingSizeHi%u\n" "PostTime%i\n" "FilenameConfirmed%s\n" "Paused%s\n" "NZBID%i\n" "NZBName%s\n" "NZBNicename%s\n" // deprecated, use "NZBName" instead "NZBFilename%s\n" "Subject%s\n" "Filename%s\n" "DestDir%s\n" "Category%s\n" "Priority%i\n" "ActiveDownloads%i\n" "\n"; const char* JSON_LIST_ITEM = "{\n" "\"ID\" : %i,\n" "\"FileSizeLo\" : %u,\n" "\"FileSizeHi\" : %u,\n" "\"RemainingSizeLo\" : %u,\n" "\"RemainingSizeHi\" : %u,\n" "\"PostTime\" : %i,\n" "\"FilenameConfirmed\" : %s,\n" "\"Paused\" : %s,\n" "\"NZBID\" : %i,\n" "\"NZBName\" : \"%s\",\n" "\"NZBNicename\" : \"%s\",\n" // deprecated, use "NZBName" instead "\"NZBFilename\" : \"%s\",\n" "\"Subject\" : \"%s\",\n" "\"Filename\" : \"%s\",\n" "\"DestDir\" : \"%s\",\n" "\"Category\" : \"%s\",\n" "\"Priority\" : %i,\n" "\"ActiveDownloads\" : %i\n" "}"; int iItemBufSize = 10240; char* szItemBuf = (char*)malloc(iItemBufSize); int index = 0; for (FileQueue::iterator it = pDownloadQueue->GetFileQueue()->begin(); it != pDownloadQueue->GetFileQueue()->end(); it++) { FileInfo* pFileInfo = *it; if ((iNZBID > 0 && iNZBID == pFileInfo->GetNZBInfo()->GetID()) || (iNZBID == 0 && (iIDStart == 0 || (iIDStart <= pFileInfo->GetID() && pFileInfo->GetID() <= iIDEnd)))) { unsigned long iFileSizeHi, iFileSizeLo; unsigned long iRemainingSizeLo, iRemainingSizeHi; Util::SplitInt64(pFileInfo->GetSize(), &iFileSizeHi, &iFileSizeLo); Util::SplitInt64(pFileInfo->GetRemainingSize(), &iRemainingSizeHi, &iRemainingSizeLo); char* xmlNZBFilename = EncodeStr(pFileInfo->GetNZBInfo()->GetFilename()); char* xmlSubject = EncodeStr(pFileInfo->GetSubject()); char* xmlFilename = EncodeStr(pFileInfo->GetFilename()); char* xmlDestDir = EncodeStr(pFileInfo->GetNZBInfo()->GetDestDir()); char* xmlCategory = EncodeStr(pFileInfo->GetNZBInfo()->GetCategory()); char* xmlNZBNicename = EncodeStr(pFileInfo->GetNZBInfo()->GetName()); snprintf(szItemBuf, iItemBufSize, IsJson() ? JSON_LIST_ITEM : XML_LIST_ITEM, pFileInfo->GetID(), iFileSizeLo, iFileSizeHi, iRemainingSizeLo, iRemainingSizeHi, pFileInfo->GetTime(), BoolToStr(pFileInfo->GetFilenameConfirmed()), BoolToStr(pFileInfo->GetPaused()), pFileInfo->GetNZBInfo()->GetID(), xmlNZBNicename, xmlNZBNicename, xmlNZBFilename, xmlSubject, xmlFilename, xmlDestDir, xmlCategory, pFileInfo->GetPriority(), pFileInfo->GetActiveDownloads()); szItemBuf[iItemBufSize-1] = '\0'; free(xmlNZBFilename); free(xmlSubject); free(xmlFilename); free(xmlDestDir); free(xmlCategory); free(xmlNZBNicename); if (IsJson() && index++ > 0) { AppendResponse(",\n"); } AppendResponse(szItemBuf); } } free(szItemBuf); g_pQueueCoordinator->UnlockQueue(); AppendResponse(IsJson() ? "\n]" : "\n"); } void NzbInfoXmlCommand::AppendNZBInfoFields(NZBInfo* pNZBInfo) { const char* XML_HISTORY_ITEM_START = "NZBID%i\n" "NZBName%s\n" "NZBNicename%s\n" // deprecated, use "NZBName" instead "NZBFilename%s\n" "DestDir%s\n" "FinalDir%s\n" "Category%s\n" "ParStatus%s\n" "UnpackStatus%s\n" "MoveStatus%s\n" "ScriptStatus%s\n" "DeleteStatus%s\n" "MarkStatus%s\n" "FileSizeLo%u\n" "FileSizeHi%u\n" "FileSizeMB%i\n" "FileCount%i\n" "TotalArticles%i\n" "SuccessArticles%i\n" "FailedArticles%i\n" "Health%i\n" "CriticalHealth%i\n" "DupeKey%s\n" "DupeScore%i\n" "DupeMode%i\n" "Deleted%s\n" // deprecated, use "DeleteStatus" instead "Parameters\n"; const char* XML_HISTORY_ITEM_SCRIPT_START = "\n" "ScriptStatuses\n"; const char* XML_HISTORY_ITEM_STATS_START = "\n" "ServerStats\n"; const char* XML_HISTORY_ITEM_END = "\n"; const char* JSON_HISTORY_ITEM_START = "\"NZBID\" : %i,\n" "\"NZBName\" : \"%s\",\n" "\"NZBNicename\" : \"%s\",\n" // deprecated, use NZBName instead "\"NZBFilename\" : \"%s\",\n" "\"DestDir\" : \"%s\",\n" "\"FinalDir\" : \"%s\",\n" "\"Category\" : \"%s\",\n" "\"ParStatus\" : \"%s\",\n" "\"UnpackStatus\" : \"%s\",\n" "\"MoveStatus\" : \"%s\",\n" "\"ScriptStatus\" : \"%s\",\n" "\"DeleteStatus\" : \"%s\",\n" "\"MarkStatus\" : \"%s\",\n" "\"FileSizeLo\" : %u,\n" "\"FileSizeHi\" : %u,\n" "\"FileSizeMB\" : %i,\n" "\"FileCount\" : %i,\n" "\"TotalArticles\" : %i,\n" "\"SuccessArticles\" : %i,\n" "\"FailedArticles\" : %i,\n" "\"Health\" : %i,\n" "\"CriticalHealth\" : %i,\n" "\"DupeKey\" : \"%s\",\n" "\"DupeScore\" : %i,\n" "\"DupeMode\" : \"%s\",\n" "\"Deleted\" : %s,\n" // deprecated, use "DeleteStatus" instead "\"Parameters\" : [\n"; const char* JSON_HISTORY_ITEM_SCRIPT_START = "],\n" "\"ScriptStatuses\" : [\n"; const char* JSON_HISTORY_ITEM_STATS_START = "],\n" "\"ServerStats\" : [\n"; const char* JSON_HISTORY_ITEM_END = "]\n"; const char* XML_PARAMETER_ITEM = "\n" "Name%s\n" "Value%s\n" "\n"; const char* JSON_PARAMETER_ITEM = "{\n" "\"Name\" : \"%s\",\n" "\"Value\" : \"%s\"\n" "}"; const char* XML_SCRIPT_ITEM = "\n" "Name%s\n" "Status%s\n" "\n"; const char* JSON_SCRIPT_ITEM = "{\n" "\"Name\" : \"%s\",\n" "\"Status\" : \"%s\"\n" "}"; const char* XML_STAT_ITEM = "\n" "ServerID%i\n" "SuccessArticles%i\n" "FailedArticles%i\n" "\n"; const char* JSON_STAT_ITEM = "{\n" "\"ServerID\" : %i,\n" "\"SuccessArticles\" : %i,\n" "\"FailedArticles\" : %i\n" "}"; const char* szParStatusName[] = { "NONE", "NONE", "FAILURE", "SUCCESS", "REPAIR_POSSIBLE", "MANUAL" }; const char* szUnpackStatusName[] = { "NONE", "NONE", "FAILURE", "SUCCESS", "SPACE", "PASSWORD" }; const char* szMoveStatusName[] = { "NONE", "FAILURE", "SUCCESS" }; const char* szScriptStatusName[] = { "NONE", "FAILURE", "SUCCESS" }; const char* szDeleteStatusName[] = { "NONE", "MANUAL", "HEALTH", "DUPE" }; const char* szMarkStatusName[] = { "NONE", "BAD", "GOOD" }; const char* szDupeModeName[] = { "SCORE", "ALL", "FORCE" }; int iItemBufSize = 10240; char* szItemBuf = (char*)malloc(iItemBufSize); unsigned long iFileSizeHi, iFileSizeLo, iFileSizeMB; Util::SplitInt64(pNZBInfo->GetSize(), &iFileSizeHi, &iFileSizeLo); iFileSizeMB = (int)(pNZBInfo->GetSize() / 1024 / 1024); char* xmlNZBFilename = EncodeStr(pNZBInfo->GetFilename()); char* xmlNZBNicename = EncodeStr(pNZBInfo->GetName()); char* xmlDestDir = EncodeStr(pNZBInfo->GetDestDir()); char* xmlFinalDir = EncodeStr(pNZBInfo->GetFinalDir()); char* xmlCategory = EncodeStr(pNZBInfo->GetCategory()); char* xmlDupeKey = EncodeStr(pNZBInfo->GetDupeKey()); snprintf(szItemBuf, iItemBufSize, IsJson() ? JSON_HISTORY_ITEM_START : XML_HISTORY_ITEM_START, pNZBInfo->GetID(), xmlNZBNicename, xmlNZBNicename, xmlNZBFilename, xmlDestDir, xmlFinalDir, xmlCategory, szParStatusName[pNZBInfo->GetParStatus()], szUnpackStatusName[pNZBInfo->GetUnpackStatus()], szMoveStatusName[pNZBInfo->GetMoveStatus()], szScriptStatusName[pNZBInfo->GetScriptStatuses()->CalcTotalStatus()], szDeleteStatusName[pNZBInfo->GetDeleteStatus()], szMarkStatusName[pNZBInfo->GetMarkStatus()], iFileSizeLo, iFileSizeHi, iFileSizeMB, pNZBInfo->GetFileCount(), pNZBInfo->GetTotalArticles(), pNZBInfo->GetSuccessArticles(), pNZBInfo->GetFailedArticles(), pNZBInfo->CalcHealth(), pNZBInfo->CalcCriticalHealth(), xmlDupeKey, pNZBInfo->GetDupeScore(), szDupeModeName[pNZBInfo->GetDupeMode()], BoolToStr(pNZBInfo->GetDeleteStatus() != NZBInfo::dsNone)); free(xmlNZBNicename); free(xmlNZBFilename); free(xmlCategory); free(xmlDestDir); free(xmlFinalDir); free(xmlDupeKey); szItemBuf[iItemBufSize-1] = '\0'; AppendResponse(szItemBuf); // Post-processing parameters int iParamIndex = 0; for (NZBParameterList::iterator it = pNZBInfo->GetParameters()->begin(); it != pNZBInfo->GetParameters()->end(); it++) { NZBParameter* pParameter = *it; char* xmlName = EncodeStr(pParameter->GetName()); char* xmlValue = EncodeStr(pParameter->GetValue()); snprintf(szItemBuf, iItemBufSize, IsJson() ? JSON_PARAMETER_ITEM : XML_PARAMETER_ITEM, xmlName, xmlValue); szItemBuf[iItemBufSize-1] = '\0'; free(xmlName); free(xmlValue); if (IsJson() && iParamIndex++ > 0) { AppendResponse(",\n"); } AppendResponse(szItemBuf); } AppendResponse(IsJson() ? JSON_HISTORY_ITEM_SCRIPT_START : XML_HISTORY_ITEM_SCRIPT_START); // Script statuses int iScriptIndex = 0; for (ScriptStatusList::iterator it = pNZBInfo->GetScriptStatuses()->begin(); it != pNZBInfo->GetScriptStatuses()->end(); it++) { ScriptStatus* pScriptStatus = *it; char* xmlName = EncodeStr(pScriptStatus->GetName()); char* xmlStatus = EncodeStr(szScriptStatusName[pScriptStatus->GetStatus()]); snprintf(szItemBuf, iItemBufSize, IsJson() ? JSON_SCRIPT_ITEM : XML_SCRIPT_ITEM, xmlName, xmlStatus); szItemBuf[iItemBufSize-1] = '\0'; free(xmlName); free(xmlStatus); if (IsJson() && iScriptIndex++ > 0) { AppendResponse(",\n"); } AppendResponse(szItemBuf); } AppendResponse(IsJson() ? JSON_HISTORY_ITEM_STATS_START : XML_HISTORY_ITEM_STATS_START); // Server stats int iStatIndex = 0; for (ServerStatList::iterator it = pNZBInfo->GetServerStats()->begin(); it != pNZBInfo->GetServerStats()->end(); it++) { ServerStat* pServerStat = *it; snprintf(szItemBuf, iItemBufSize, IsJson() ? JSON_STAT_ITEM : XML_STAT_ITEM, pServerStat->GetServerID(), pServerStat->GetSuccessArticles(), pServerStat->GetFailedArticles()); szItemBuf[iItemBufSize-1] = '\0'; if (IsJson() && iStatIndex++ > 0) { AppendResponse(",\n"); } AppendResponse(szItemBuf); } AppendResponse(IsJson() ? JSON_HISTORY_ITEM_END : XML_HISTORY_ITEM_END); free(szItemBuf); } void ListGroupsXmlCommand::Execute() { AppendResponse(IsJson() ? "[\n" : "\n"); const char* XML_LIST_ITEM_START = "\n" "FirstID%i\n" "LastID%i\n" "RemainingSizeLo%u\n" "RemainingSizeHi%u\n" "RemainingSizeMB%i\n" "PausedSizeLo%u\n" "PausedSizeHi%u\n" "PausedSizeMB%i\n" "RemainingFileCount%i\n" "RemainingParCount%i\n" "MinPostTime%i\n" "MaxPostTime%i\n" "MinPriority%i\n" "MaxPriority%i\n" "ActiveDownloads%i\n"; const char* XML_LIST_ITEM_END = "\n"; const char* JSON_LIST_ITEM_START = "{\n" "\"FirstID\" : %i,\n" "\"LastID\" : %i,\n" "\"RemainingSizeLo\" : %u,\n" "\"RemainingSizeHi\" : %u,\n" "\"RemainingSizeMB\" : %i,\n" "\"PausedSizeLo\" : %u,\n" "\"PausedSizeHi\" : %u,\n" "\"PausedSizeMB\" : %i,\n" "\"RemainingFileCount\" : %i,\n" "\"RemainingParCount\" : %i,\n" "\"MinPostTime\" : %i,\n" "\"MaxPostTime\" : %i,\n" "\"MinPriority\" : %i,\n" "\"MaxPriority\" : %i,\n" "\"ActiveDownloads\" : %i,\n"; const char* JSON_LIST_ITEM_END = "}"; GroupQueue groupQueue; groupQueue.clear(); DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue(); pDownloadQueue->BuildGroups(&groupQueue); g_pQueueCoordinator->UnlockQueue(); int iItemBufSize = 10240; char* szItemBuf = (char*)malloc(iItemBufSize); int index = 0; for (GroupQueue::iterator it = groupQueue.begin(); it != groupQueue.end(); it++) { GroupInfo* pGroupInfo = *it; unsigned long iRemainingSizeLo, iRemainingSizeHi, iRemainingSizeMB; unsigned long iPausedSizeLo, iPausedSizeHi, iPausedSizeMB; Util::SplitInt64(pGroupInfo->GetRemainingSize(), &iRemainingSizeHi, &iRemainingSizeLo); iRemainingSizeMB = (int)(pGroupInfo->GetRemainingSize() / 1024 / 1024); Util::SplitInt64(pGroupInfo->GetPausedSize(), &iPausedSizeHi, &iPausedSizeLo); iPausedSizeMB = (int)(pGroupInfo->GetPausedSize() / 1024 / 1024); snprintf(szItemBuf, iItemBufSize, IsJson() ? JSON_LIST_ITEM_START : XML_LIST_ITEM_START, pGroupInfo->GetFirstID(), pGroupInfo->GetLastID(), iRemainingSizeLo, iRemainingSizeHi, iRemainingSizeMB, iPausedSizeLo, iPausedSizeHi, iPausedSizeMB, pGroupInfo->GetRemainingFileCount(), pGroupInfo->GetRemainingParCount(), pGroupInfo->GetMinTime(), pGroupInfo->GetMaxTime(), pGroupInfo->GetMinPriority(), pGroupInfo->GetMaxPriority(), pGroupInfo->GetActiveDownloads()); szItemBuf[iItemBufSize-1] = '\0'; if (IsJson() && index++ > 0) { AppendResponse(",\n"); } AppendResponse(szItemBuf); AppendNZBInfoFields(pGroupInfo->GetNZBInfo()); AppendResponse(IsJson() ? JSON_LIST_ITEM_END : XML_LIST_ITEM_END); } free(szItemBuf); AppendResponse(IsJson() ? "\n]" : "\n"); } typedef struct { int iActionID; const char* szActionName; } EditCommandEntry; EditCommandEntry EditCommandNameMap[] = { { QueueEditor::eaFileMoveOffset, "FileMoveOffset" }, { QueueEditor::eaFileMoveTop, "FileMoveTop" }, { QueueEditor::eaFileMoveBottom, "FileMoveBottom" }, { QueueEditor::eaFilePause, "FilePause" }, { QueueEditor::eaFileResume, "FileResume" }, { QueueEditor::eaFileDelete, "FileDelete" }, { QueueEditor::eaFilePauseAllPars, "FilePauseAllPars" }, { QueueEditor::eaFilePauseExtraPars, "FilePauseExtraPars" }, { QueueEditor::eaFileSetPriority, "FileSetPriority" }, { QueueEditor::eaFileReorder, "FileReorder" }, { QueueEditor::eaFileSplit, "FileSplit" }, { QueueEditor::eaGroupMoveOffset, "GroupMoveOffset" }, { QueueEditor::eaGroupMoveTop, "GroupMoveTop" }, { QueueEditor::eaGroupMoveBottom, "GroupMoveBottom" }, { QueueEditor::eaGroupPause, "GroupPause" }, { QueueEditor::eaGroupResume, "GroupResume" }, { QueueEditor::eaGroupDelete, "GroupDelete" }, { QueueEditor::eaGroupDupeDelete, "GroupDupeDelete" }, { QueueEditor::eaGroupFinalDelete, "GroupFinalDelete" }, { QueueEditor::eaGroupPauseAllPars, "GroupPauseAllPars" }, { QueueEditor::eaGroupPauseExtraPars, "GroupPauseExtraPars" }, { QueueEditor::eaGroupSetPriority, "GroupSetPriority" }, { QueueEditor::eaGroupSetCategory, "GroupSetCategory" }, { QueueEditor::eaGroupMerge, "GroupMerge" }, { QueueEditor::eaGroupSetParameter, "GroupSetParameter" }, { QueueEditor::eaGroupSetName, "GroupSetName" }, { QueueEditor::eaGroupSetDupeKey, "GroupSetDupeKey" }, { QueueEditor::eaGroupSetDupeScore, "GroupSetDupeScore" }, { QueueEditor::eaGroupSetDupeMode, "GroupSetDupeMode" }, { PrePostProcessor::eaPostMoveOffset, "PostMoveOffset" }, { PrePostProcessor::eaPostMoveTop, "PostMoveTop" }, { PrePostProcessor::eaPostMoveBottom, "PostMoveBottom" }, { PrePostProcessor::eaPostDelete, "PostDelete" }, { PrePostProcessor::eaHistoryDelete, "HistoryDelete" }, { PrePostProcessor::eaHistoryFinalDelete, "HistoryFinalDelete" }, { PrePostProcessor::eaHistoryReturn, "HistoryReturn" }, { PrePostProcessor::eaHistoryProcess, "HistoryProcess" }, { PrePostProcessor::eaHistoryRedownload, "HistoryRedownload" }, { PrePostProcessor::eaHistorySetParameter, "HistorySetParameter" }, { PrePostProcessor::eaHistorySetDupeKey, "HistorySetDupeKey" }, { PrePostProcessor::eaHistorySetDupeScore, "HistorySetDupeScore" }, { PrePostProcessor::eaHistorySetDupeMode, "HistorySetDupeMode" }, { PrePostProcessor::eaHistorySetDupeBackup, "HistorySetDupeBackup" }, { PrePostProcessor::eaHistoryMarkBad, "HistoryMarkBad" }, { PrePostProcessor::eaHistoryMarkGood, "HistoryMarkGood" }, { 0, NULL } }; void EditQueueXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } char* szEditCommand; if (!NextParamAsStr(&szEditCommand)) { BuildErrorResponse(2, "Invalid parameter"); return; } debug("EditCommand=%s", szEditCommand); int iAction = -1; for (int i = 0; const char* szName = EditCommandNameMap[i].szActionName; i++) { if (!strcasecmp(szEditCommand, szName)) { iAction = EditCommandNameMap[i].iActionID; break; } } if (iAction == -1) { BuildErrorResponse(3, "Invalid action"); return; } int iOffset = 0; if (!NextParamAsInt(&iOffset)) { BuildErrorResponse(2, "Invalid parameter"); return; } char* szEditText; if (!NextParamAsStr(&szEditText)) { BuildErrorResponse(2, "Invalid parameter"); return; } debug("EditText=%s", szEditText); DecodeStr(szEditText); IDList cIDList; int iID = 0; while (NextParamAsInt(&iID)) { cIDList.push_back(iID); } bool bOK = false; if (iAction < PrePostProcessor::eaPostMoveOffset) { bOK = g_pQueueCoordinator->GetQueueEditor()->EditList(&cIDList, NULL, QueueEditor::mmID, true, (QueueEditor::EEditAction)iAction, iOffset, szEditText); } else { bOK = g_pPrePostProcessor->QueueEditList(&cIDList, (PrePostProcessor::EEditAction)iAction, iOffset, szEditText); } BuildBoolResponse(bOK); } // bool append(string NZBFilename, string Category, int Priority, bool AddToTop, string Content, bool AddPaused, string DupeKey, int DupeScore, string DupeMode) // For backward compatibility with 0.8 parameter "Priority" is optional // Parameters starting from "AddPaused" are optional (added in v12) void DownloadXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } char* szFileName; if (!NextParamAsStr(&szFileName)) { BuildErrorResponse(2, "Invalid parameter (FileName)"); return; } char* szCategory; if (!NextParamAsStr(&szCategory)) { BuildErrorResponse(2, "Invalid parameter (Category)"); return; } DecodeStr(szFileName); DecodeStr(szCategory); debug("FileName=%s", szFileName); // For backward compatibility with 0.8 parameter "Priority" is optional (error checking omitted) int iPriority = 0; NextParamAsInt(&iPriority); bool bAddTop; if (!NextParamAsBool(&bAddTop)) { BuildErrorResponse(2, "Invalid parameter (AddTop)"); return; } char* szFileContent; if (!NextParamAsStr(&szFileContent)) { BuildErrorResponse(2, "Invalid parameter (FileContent)"); return; } bool bAddPaused = false; char* szDupeKey = NULL; int iDupeScore = 0; EDupeMode eDupeMode = dmScore; if (NextParamAsBool(&bAddPaused)) { if (!NextParamAsStr(&szDupeKey)) { BuildErrorResponse(2, "Invalid parameter (DupeKey)"); return; } if (!NextParamAsInt(&iDupeScore)) { BuildErrorResponse(2, "Invalid parameter (DupeScore)"); return; } char* szDupeMode = NULL; if (!NextParamAsStr(&szDupeMode) || (strcasecmp(szDupeMode, "score") && strcasecmp(szDupeMode, "all") && strcasecmp(szDupeMode, "force"))) { BuildErrorResponse(2, "Invalid parameter (DupeMode)"); return; } eDupeMode = !strcasecmp(szDupeMode, "all") ? dmAll : !strcasecmp(szDupeMode, "force") ? dmForce : dmScore; } if (IsJson()) { // JSON-string may contain '/'-character used in Base64, which must be escaped in JSON WebUtil::JsonDecode(szFileContent); } int iLen = WebUtil::DecodeBase64(szFileContent, 0, szFileContent); szFileContent[iLen] = '\0'; //debug("FileContent=%s", szFileContent); bool bOK = g_pScanner->AddExternalFile(szFileName, szCategory, iPriority, szDupeKey, iDupeScore, eDupeMode, NULL, bAddTop, bAddPaused, NULL, szFileContent, iLen) != Scanner::asFailed; BuildBoolResponse(bOK); } void PostQueueXmlCommand::Execute() { int iNrEntries = 0; NextParamAsInt(&iNrEntries); AppendResponse(IsJson() ? "[\n" : "\n"); const char* XML_POSTQUEUE_ITEM_START = "\n" "ID%i\n" "InfoName%s\n" "ParFilename\n" // deprecated, always empty "Stage%s\n" "ProgressLabel%s\n" "FileProgress%i\n" "StageProgress%i\n" "TotalTimeSec%i\n" "StageTimeSec%i\n"; const char* XML_LOG_START = "Log\n"; const char* XML_POSTQUEUE_ITEM_END = "\n" "\n"; const char* JSON_POSTQUEUE_ITEM_START = "{\n" "\"ID\" : %i,\n" "\"InfoName\" : \"%s\",\n" "\"ParFilename\" : \"\",\n" // deprecated, always empty "\"Stage\" : \"%s\",\n" "\"ProgressLabel\" : \"%s\",\n" "\"FileProgress\" : %i,\n" "\"StageProgress\" : %i,\n" "\"TotalTimeSec\" : %i,\n" "\"StageTimeSec\" : %i,\n"; const char* JSON_LOG_START = ",\n" "\"Log\" : [\n"; const char* JSON_POSTQUEUE_ITEM_END = "]\n" "}"; const char* XML_LOG_ITEM = "\n" "ID%i\n" "Kind%s\n" "Time%i\n" "Text%s\n" "\n"; const char* JSON_LOG_ITEM = "{\n" "\"ID\" : %i,\n" "\"Kind\" : \"%s\",\n" "\"Time\" : %i,\n" "\"Text\" : \"%s\"\n" "}"; const char* szMessageType[] = { "INFO", "WARNING", "ERROR", "DEBUG", "DETAIL"}; PostQueue* pPostQueue = g_pQueueCoordinator->LockQueue()->GetPostQueue(); time_t tCurTime = time(NULL); int iItemBufSize = 10240; char* szItemBuf = (char*)malloc(iItemBufSize); int index = 0; for (PostQueue::iterator it = pPostQueue->begin(); it != pPostQueue->end(); it++) { PostInfo* pPostInfo = *it; const char* szPostStageName[] = { "QUEUED", "LOADING_PARS", "VERIFYING_SOURCES", "REPAIRING", "VERIFYING_REPAIRED", "RENAMING", "UNPACKING", "MOVING", "EXECUTING_SCRIPT", "FINISHED" }; char* xmlInfoName = EncodeStr(pPostInfo->GetInfoName()); char* xmlProgressLabel = EncodeStr(pPostInfo->GetProgressLabel()); snprintf(szItemBuf, iItemBufSize, IsJson() ? JSON_POSTQUEUE_ITEM_START : XML_POSTQUEUE_ITEM_START, pPostInfo->GetID(), xmlInfoName, szPostStageName[pPostInfo->GetStage()], xmlProgressLabel, pPostInfo->GetFileProgress(), pPostInfo->GetStageProgress(), pPostInfo->GetStartTime() ? tCurTime - pPostInfo->GetStartTime() : 0, pPostInfo->GetStageTime() ? tCurTime - pPostInfo->GetStageTime() : 0); szItemBuf[iItemBufSize-1] = '\0'; free(xmlInfoName); free(xmlProgressLabel); if (IsJson() && index++ > 0) { AppendResponse(",\n"); } AppendResponse(szItemBuf); AppendNZBInfoFields(pPostInfo->GetNZBInfo()); AppendResponse(IsJson() ? JSON_LOG_START : XML_LOG_START); if (iNrEntries > 0) { PostInfo::Messages* pMessages = pPostInfo->LockMessages(); if (!pMessages->empty()) { if (iNrEntries > (int)pMessages->size()) { iNrEntries = pMessages->size(); } int iStart = pMessages->size() - iNrEntries; int index = 0; for (unsigned int i = (unsigned int)iStart; i < pMessages->size(); i++) { Message* pMessage = (*pMessages)[i]; char* xmltext = EncodeStr(pMessage->GetText()); snprintf(szItemBuf, iItemBufSize, IsJson() ? JSON_LOG_ITEM : XML_LOG_ITEM, pMessage->GetID(), szMessageType[pMessage->GetKind()], pMessage->GetTime(), xmltext); szItemBuf[iItemBufSize-1] = '\0'; free(xmltext); if (IsJson() && index++ > 0) { AppendResponse(",\n"); } AppendResponse(szItemBuf); } } pPostInfo->UnlockMessages(); } AppendResponse(IsJson() ? JSON_POSTQUEUE_ITEM_END : XML_POSTQUEUE_ITEM_END); } free(szItemBuf); g_pQueueCoordinator->UnlockQueue(); AppendResponse(IsJson() ? "\n]" : "\n"); } void WriteLogXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } char* szKind; char* szText; if (!NextParamAsStr(&szKind) || !NextParamAsStr(&szText)) { BuildErrorResponse(2, "Invalid parameter"); return; } DecodeStr(szText); debug("Kind=%s, Text=%s", szKind, szText); if (!strcmp(szKind, "INFO")) { info(szText); } else if (!strcmp(szKind, "WARNING")) { warn(szText); } else if (!strcmp(szKind, "ERROR")) { error(szText); } else if (!strcmp(szKind, "DETAIL")) { detail(szText); } else if (!strcmp(szKind, "DEBUG")) { debug(szText); } else { BuildErrorResponse(3, "Invalid Kind"); return; } BuildBoolResponse(true); } void ClearLogXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } g_pLog->Clear(); BuildBoolResponse(true); } void ScanXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } bool bSyncMode = false; // optional parameter "SyncMode" NextParamAsBool(&bSyncMode); g_pScanner->ScanNZBDir(bSyncMode); BuildBoolResponse(true); } // struct[] history(bool Dup) // Parameter "Dup" is optional (new in v12) void HistoryXmlCommand::Execute() { AppendResponse(IsJson() ? "[\n" : "\n"); const char* XML_HISTORY_ITEM_START = "\n" "ID%i\n" "Kind%s\n" "Name%s\n" "RemainingFileCount%i\n" "HistoryTime%i\n" "URL%s\n" "UrlStatus%s\n"; const char* XML_HISTORY_ITEM_LOG_START = "Log\n"; const char* XML_HISTORY_ITEM_END = "\n" "\n"; const char* JSON_HISTORY_ITEM_START = "{\n" "\"ID\" : %i,\n" "\"Kind\" : \"%s\",\n" "\"Name\" : \"%s\",\n" "\"RemainingFileCount\" : %i,\n" "\"HistoryTime\" : %i,\n" "\"URL\" : \"%s\",\n" "\"UrlStatus\" : \"%s\",\n"; const char* JSON_HISTORY_ITEM_LOG_START = "\"Log\" : [\n"; const char* JSON_HISTORY_ITEM_END = "]\n" "}"; const char* XML_LOG_ITEM = "\n" "ID%i\n" "Kind%s\n" "Time%i\n" "Text%s\n" "\n"; const char* JSON_LOG_ITEM = "{\n" "\"ID\" : %i,\n" "\"Kind\" : \"%s\",\n" "\"Time\" : %i,\n" "\"Text\" : \"%s\"\n" "}"; const char* XML_HISTORY_DUP_ITEM = "\n" "ID%i\n" "Kind%s\n" "Name%s\n" "HistoryTime%i\n" "FileSizeLo%u\n" "FileSizeHi%u\n" "FileSizeMB%i\n" "DupeKey%s\n" "DupeScore%i\n" "DupeMode%s\n" "DupStatus%s\n" "\n"; const char* JSON_HISTORY_DUP_ITEM = "{\n" "\"ID\" : %i,\n" "\"Kind\" : \"%s\",\n" "\"Name\" : \"%s\",\n" "\"HistoryTime\" : %i,\n" "\"FileSizeLo\" : %i,\n" "\"FileSizeHi\" : %i,\n" "\"FileSizeMB\" : %i,\n" "\"DupeKey\" : \"%s\",\n" "\"DupeScore\" : %i,\n" "\"DupeMode\" : \"%s\",\n" "\"DupStatus\" : \"%s\",\n"; const char* szUrlStatusName[] = { "UNKNOWN", "UNKNOWN", "SUCCESS", "FAILURE", "UNKNOWN", "SCAN_SKIPPED", "SCAN_FAILURE" }; const char* szDupStatusName[] = { "UNKNOWN", "SUCCESS", "FAILURE", "DELETED", "DUPE", "BAD", "GOOD" }; const char* szMessageType[] = { "INFO", "WARNING", "ERROR", "DEBUG", "DETAIL"}; const char* szDupeModeName[] = { "SCORE", "ALL", "FORCE" }; bool bDup = false; NextParamAsBool(&bDup); DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue(); int iItemBufSize = 10240; char* szItemBuf = (char*)malloc(iItemBufSize); NZBInfo* pUrlNZBInfo = new NZBInfo(false); // fake NZB-Info for Urls int index = 0; for (HistoryList::iterator it = pDownloadQueue->GetHistoryList()->begin(); it != pDownloadQueue->GetHistoryList()->end(); it++) { HistoryInfo* pHistoryInfo = *it; if (pHistoryInfo->GetKind() == HistoryInfo::hkDupInfo && !bDup) { continue; } NZBInfo* pNZBInfo = NULL; char szNicename[1024]; pHistoryInfo->GetName(szNicename, sizeof(szNicename)); char *xmlNicename = EncodeStr(szNicename); if (pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo) { pNZBInfo = pHistoryInfo->GetNZBInfo(); snprintf(szItemBuf, iItemBufSize, IsJson() ? JSON_HISTORY_ITEM_START : XML_HISTORY_ITEM_START, pHistoryInfo->GetID(), "NZB", xmlNicename, pNZBInfo->GetParkedFileCount(), pHistoryInfo->GetTime(), "", ""); } else if (pHistoryInfo->GetKind() == HistoryInfo::hkUrlInfo) { UrlInfo* pUrlInfo = pHistoryInfo->GetUrlInfo(); char* xmlURL = EncodeStr(pUrlInfo->GetURL()); snprintf(szItemBuf, iItemBufSize, IsJson() ? JSON_HISTORY_ITEM_START : XML_HISTORY_ITEM_START, pHistoryInfo->GetID(), "URL", xmlNicename, 0, pHistoryInfo->GetTime(), xmlURL, szUrlStatusName[pUrlInfo->GetStatus()]); free(xmlURL); pUrlNZBInfo->SetCategory(pUrlInfo->GetCategory()); pUrlNZBInfo->SetFilename(pUrlInfo->GetNZBFilename()); pNZBInfo = pUrlNZBInfo; } else if (pHistoryInfo->GetKind() == HistoryInfo::hkDupInfo) { DupInfo* pDupInfo = pHistoryInfo->GetDupInfo(); unsigned long iFileSizeHi, iFileSizeLo, iFileSizeMB; Util::SplitInt64(pDupInfo->GetSize(), &iFileSizeHi, &iFileSizeLo); iFileSizeMB = (int)(pDupInfo->GetSize() / 1024 / 1024); char* xmlDupeKey = EncodeStr(pDupInfo->GetDupeKey()); snprintf(szItemBuf, iItemBufSize, IsJson() ? JSON_HISTORY_DUP_ITEM : XML_HISTORY_DUP_ITEM, pHistoryInfo->GetID(), "DUP", xmlNicename, pHistoryInfo->GetTime(), iFileSizeLo, iFileSizeHi, iFileSizeMB, xmlDupeKey, pDupInfo->GetDupeScore(), szDupeModeName[pDupInfo->GetDupeMode()], szDupStatusName[pDupInfo->GetStatus()]); free(xmlDupeKey); } szItemBuf[iItemBufSize-1] = '\0'; free(xmlNicename); if (IsJson() && index++ > 0) { AppendResponse(",\n"); } AppendResponse(szItemBuf); if (pNZBInfo) { AppendNZBInfoFields(pNZBInfo); if (IsJson()) { AppendResponse(",\n"); } } AppendResponse(IsJson() ? JSON_HISTORY_ITEM_LOG_START : XML_HISTORY_ITEM_LOG_START); if (pNZBInfo) { // Log-Messages NZBInfo::Messages* pMessages = pNZBInfo->LockMessages(); if (!pMessages->empty()) { int iLogIndex = 0; for (NZBInfo::Messages::iterator it = pMessages->begin(); it != pMessages->end(); it++) { Message* pMessage = *it; char* xmltext = EncodeStr(pMessage->GetText()); snprintf(szItemBuf, iItemBufSize, IsJson() ? JSON_LOG_ITEM : XML_LOG_ITEM, pMessage->GetID(), szMessageType[pMessage->GetKind()], pMessage->GetTime(), xmltext); szItemBuf[iItemBufSize-1] = '\0'; free(xmltext); if (IsJson() && iLogIndex++ > 0) { AppendResponse(",\n"); } AppendResponse(szItemBuf); } } pNZBInfo->UnlockMessages(); } AppendResponse(IsJson() ? JSON_HISTORY_ITEM_END : XML_HISTORY_ITEM_END); } free(szItemBuf); delete pUrlNZBInfo; AppendResponse(IsJson() ? "\n]" : "\n"); g_pQueueCoordinator->UnlockQueue(); } // bool appendurl(string NZBFilename, string Category, int Priority, bool AddToTop, string URL, bool AddPaused, string DupeKey, int DupeScore, string DupeMode) // Parameters starting from "AddPaused" are optional (added in v12) void DownloadUrlXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } char* szNZBFileName; if (!NextParamAsStr(&szNZBFileName)) { BuildErrorResponse(2, "Invalid parameter (NZBFilename)"); return; } char* szCategory; if (!NextParamAsStr(&szCategory)) { BuildErrorResponse(2, "Invalid parameter (Category)"); return; } int iPriority = 0; if (!NextParamAsInt(&iPriority)) { BuildErrorResponse(2, "Invalid parameter (Priority)"); return; } bool bAddTop; if (!NextParamAsBool(&bAddTop)) { BuildErrorResponse(2, "Invalid parameter (AddTop)"); return; } char* szURL; if (!NextParamAsStr(&szURL)) { BuildErrorResponse(2, "Invalid parameter (URL)"); return; } bool bAddPaused = false; char* szDupeKey = NULL; int iDupeScore = 0; EDupeMode eDupeMode = dmScore; if (NextParamAsBool(&bAddPaused)) { if (!NextParamAsStr(&szDupeKey)) { BuildErrorResponse(2, "Invalid parameter (DupeKey)"); return; } if (!NextParamAsInt(&iDupeScore)) { BuildErrorResponse(2, "Invalid parameter (DupeScore)"); return; } char* szDupeMode = NULL; if (!NextParamAsStr(&szDupeMode) || (strcasecmp(szDupeMode, "score") && strcasecmp(szDupeMode, "all") && strcasecmp(szDupeMode, "force"))) { BuildErrorResponse(2, "Invalid parameter (DupeMode)"); return; } eDupeMode = !strcasecmp(szDupeMode, "all") ? dmAll : !strcasecmp(szDupeMode, "force") ? dmForce : dmScore; } DecodeStr(szNZBFileName); DecodeStr(szCategory); DecodeStr(szURL); if (szDupeKey) { DecodeStr(szDupeKey); } debug("URL=%s", szURL); UrlInfo* pUrlInfo = new UrlInfo(); pUrlInfo->SetURL(szURL); pUrlInfo->SetNZBFilename(szNZBFileName); pUrlInfo->SetCategory(szCategory); pUrlInfo->SetPriority(iPriority); pUrlInfo->SetAddTop(bAddTop); pUrlInfo->SetAddPaused(bAddPaused); pUrlInfo->SetDupeKey(szDupeKey ? szDupeKey : ""); pUrlInfo->SetDupeScore(iDupeScore); pUrlInfo->SetDupeMode(eDupeMode); char szNicename[1024]; pUrlInfo->GetName(szNicename, sizeof(szNicename)); info("Queue %s", szNicename); g_pUrlCoordinator->AddUrlToQueue(pUrlInfo, bAddTop); BuildBoolResponse(true); } void UrlQueueXmlCommand::Execute() { AppendResponse(IsJson() ? "[\n" : "\n"); const char* XML_URLQUEUE_ITEM = "\n" "ID%i\n" "NZBFilename%s\n" "URL%s\n" "Name%s\n" "Category%s\n" "Priority%i\n" "\n"; const char* JSON_URLQUEUE_ITEM = "{\n" "\"ID\" : %i,\n" "\"NZBFilename\" : \"%s\",\n" "\"URL\" : \"%s\",\n" "\"Name\" : \"%s\",\n" "\"Category\" : \"%s\",\n" "\"Priority\" : %i\n" "}"; UrlQueue* pUrlQueue = g_pQueueCoordinator->LockQueue()->GetUrlQueue(); int iItemBufSize = 10240; char* szItemBuf = (char*)malloc(iItemBufSize); int index = 0; for (UrlQueue::iterator it = pUrlQueue->begin(); it != pUrlQueue->end(); it++) { UrlInfo* pUrlInfo = *it; char szNicename[1024]; pUrlInfo->GetName(szNicename, sizeof(szNicename)); char* xmlNicename = EncodeStr(szNicename); char* xmlNZBFilename = EncodeStr(pUrlInfo->GetNZBFilename()); char* xmlURL = EncodeStr(pUrlInfo->GetURL()); char* xmlCategory = EncodeStr(pUrlInfo->GetCategory()); snprintf(szItemBuf, iItemBufSize, IsJson() ? JSON_URLQUEUE_ITEM : XML_URLQUEUE_ITEM, pUrlInfo->GetID(), xmlNZBFilename, xmlURL, xmlNicename, xmlCategory, pUrlInfo->GetPriority()); szItemBuf[iItemBufSize-1] = '\0'; free(xmlNicename); free(xmlNZBFilename); free(xmlURL); free(xmlCategory); if (IsJson() && index++ > 0) { AppendResponse(",\n"); } AppendResponse(szItemBuf); } free(szItemBuf); g_pQueueCoordinator->UnlockQueue(); AppendResponse(IsJson() ? "\n]" : "\n"); } // struct[] config() void ConfigXmlCommand::Execute() { const char* XML_CONFIG_ITEM = "\n" "Name%s\n" "Value%s\n" "\n"; const char* JSON_CONFIG_ITEM = "{\n" "\"Name\" : \"%s\",\n" "\"Value\" : \"%s\"\n" "}"; AppendResponse(IsJson() ? "[\n" : "\n"); int iItemBufSize = 1024; char* szItemBuf = (char*)malloc(iItemBufSize); int index = 0; Options::OptEntries* pOptEntries = g_pOptions->LockOptEntries(); for (Options::OptEntries::iterator it = pOptEntries->begin(); it != pOptEntries->end(); it++) { Options::OptEntry* pOptEntry = *it; char* xmlName = EncodeStr(pOptEntry->GetName()); char* xmlValue = EncodeStr(pOptEntry->GetValue()); // option values can sometimes have unlimited length int iValLen = strlen(xmlValue); if (iValLen > iItemBufSize - 500) { iItemBufSize = iValLen + 500; szItemBuf = (char*)realloc(szItemBuf, iItemBufSize); } snprintf(szItemBuf, iItemBufSize, IsJson() ? JSON_CONFIG_ITEM : XML_CONFIG_ITEM, xmlName, xmlValue); szItemBuf[iItemBufSize-1] = '\0'; free(xmlName); free(xmlValue); if (IsJson() && index++ > 0) { AppendResponse(",\n"); } AppendResponse(szItemBuf); } g_pOptions->UnlockOptEntries(); free(szItemBuf); AppendResponse(IsJson() ? "\n]" : "\n"); } // struct[] loadconfig() void LoadConfigXmlCommand::Execute() { const char* XML_CONFIG_ITEM = "\n" "Name%s\n" "Value%s\n" "\n"; const char* JSON_CONFIG_ITEM = "{\n" "\"Name\" : \"%s\",\n" "\"Value\" : \"%s\"\n" "}"; Options::OptEntries* pOptEntries = new Options::OptEntries(); if (!g_pOptions->LoadConfig(pOptEntries)) { BuildErrorResponse(3, "Could not read configuration file"); delete pOptEntries; return; } AppendResponse(IsJson() ? "[\n" : "\n"); int iItemBufSize = 1024; char* szItemBuf = (char*)malloc(iItemBufSize); int index = 0; for (Options::OptEntries::iterator it = pOptEntries->begin(); it != pOptEntries->end(); it++) { Options::OptEntry* pOptEntry = *it; char* xmlName = EncodeStr(pOptEntry->GetName()); char* xmlValue = EncodeStr(pOptEntry->GetValue()); // option values can sometimes have unlimited length int iValLen = strlen(xmlValue); if (iValLen > iItemBufSize - 500) { iItemBufSize = iValLen + 500; szItemBuf = (char*)realloc(szItemBuf, iItemBufSize); } snprintf(szItemBuf, iItemBufSize, IsJson() ? JSON_CONFIG_ITEM : XML_CONFIG_ITEM, xmlName, xmlValue); szItemBuf[iItemBufSize-1] = '\0'; free(xmlName); free(xmlValue); if (IsJson() && index++ > 0) { AppendResponse(",\n"); } AppendResponse(szItemBuf); } delete pOptEntries; free(szItemBuf); AppendResponse(IsJson() ? "\n]" : "\n"); } // bool saveconfig(struct[] data) void SaveConfigXmlCommand::Execute() { Options::OptEntries* pOptEntries = new Options::OptEntries(); char* szName; char* szValue; char* szDummy; while (NextParamAsStr(&szDummy) && NextParamAsStr(&szName) && NextParamAsStr(&szDummy) && NextParamAsStr(&szValue)) { DecodeStr(szName); DecodeStr(szValue); pOptEntries->push_back(new Options::OptEntry(szName, szValue)); } // save to config file bool bOK = g_pOptions->SaveConfig(pOptEntries); delete pOptEntries; BuildBoolResponse(bOK); } // struct[] configtemplates() void ConfigTemplatesXmlCommand::Execute() { const char* XML_CONFIG_ITEM = "\n" "Name%s\n" "DisplayName%s\n" "Template%s\n" "\n"; const char* JSON_CONFIG_ITEM = "{\n" "\"Name\" : \"%s\",\n" "\"DisplayName\" : \"%s\",\n" "\"Template\" : \"%s\"\n" "}"; Options::ConfigTemplates* pConfigTemplates = new Options::ConfigTemplates(); if (!g_pOptions->LoadConfigTemplates(pConfigTemplates)) { BuildErrorResponse(3, "Could not read configuration templates"); delete pConfigTemplates; return; } AppendResponse(IsJson() ? "[\n" : "\n"); int index = 0; for (Options::ConfigTemplates::iterator it = pConfigTemplates->begin(); it != pConfigTemplates->end(); it++) { Options::ConfigTemplate* pConfigTemplate = *it; char* xmlName = EncodeStr(pConfigTemplate->GetName()); char* xmlDisplayName = EncodeStr(pConfigTemplate->GetDisplayName()); char* xmlTemplate = EncodeStr(pConfigTemplate->GetTemplate()); int iItemBufSize = strlen(xmlName) + strlen(xmlTemplate) + 1024; char* szItemBuf = (char*)malloc(iItemBufSize); snprintf(szItemBuf, iItemBufSize, IsJson() ? JSON_CONFIG_ITEM : XML_CONFIG_ITEM, xmlName, xmlDisplayName, xmlTemplate); szItemBuf[iItemBufSize-1] = '\0'; free(xmlName); free(xmlDisplayName); free(xmlTemplate); if (IsJson() && index++ > 0) { AppendResponse(",\n"); } AppendResponse(szItemBuf); free(szItemBuf); } delete pConfigTemplates; AppendResponse(IsJson() ? "\n]" : "\n"); } ViewFeedXmlCommand::ViewFeedXmlCommand(bool bPreview) { m_bPreview = bPreview; } // struct[] viewfeed(int id) // struct[] previewfeed(string name, string url, string filter, bool includeNonMatching) // struct[] previewfeed(string name, string url, string filter, bool pauseNzb, string category, int priority, // bool includeNonMatching, int cacheTimeSec, string cacheId) void ViewFeedXmlCommand::Execute() { bool bOK = false; bool bIncludeNonMatching = false; FeedItemInfos* pFeedItemInfos = NULL; if (m_bPreview) { char* szName; char* szUrl; char* szFilter; bool bPauseNzb; char* szCategory; int iPriority; char* szCacheId; int iCacheTimeSec; if (!NextParamAsStr(&szName) || !NextParamAsStr(&szUrl) || !NextParamAsStr(&szFilter) || !NextParamAsBool(&bPauseNzb) || !NextParamAsStr(&szCategory) || !NextParamAsInt(&iPriority) || !NextParamAsBool(&bIncludeNonMatching) || !NextParamAsInt(&iCacheTimeSec) || !NextParamAsStr(&szCacheId)) { BuildErrorResponse(2, "Invalid parameter"); return; } DecodeStr(szName); DecodeStr(szUrl); DecodeStr(szFilter); DecodeStr(szCacheId); DecodeStr(szCategory); debug("Url=%s", szUrl); debug("Filter=%s", szFilter); bOK = g_pFeedCoordinator->PreviewFeed(szName, szUrl, szFilter, bPauseNzb, szCategory, iPriority, iCacheTimeSec, szCacheId, &pFeedItemInfos); } else { int iID = 0; if (!NextParamAsInt(&iID) || !NextParamAsBool(&bIncludeNonMatching)) { BuildErrorResponse(2, "Invalid parameter"); return; } debug("ID=%i", iID); bOK = g_pFeedCoordinator->ViewFeed(iID, &pFeedItemInfos); } if (!bOK) { BuildErrorResponse(3, "Could not read feed"); return; } const char* XML_FEED_ITEM = "\n" "Title%s\n" "Filename%s\n" "URL%s\n" "SizeLo%i\n" "SizeHi%i\n" "SizeMB%i\n" "Category%s\n" "AddCategory%s\n" "PauseNzb%s\n" "Priority%i\n" "Time%i\n" "Match%s\n" "Rule%i\n" "DupeKey%s\n" "DupeScore%i\n" "DupeMode%s\n" "Status%s\n" "\n"; const char* JSON_FEED_ITEM = "{\n" "\"Title\" : \"%s\",\n" "\"Filename\" : \"%s\",\n" "\"URL\" : \"%s\",\n" "\"SizeLo\" : %i,\n" "\"SizeHi\" : %i,\n" "\"SizeMB\" : %i,\n" "\"Category\" : \"%s\",\n" "\"AddCategory\" : \"%s\",\n" "\"PauseNzb\" : %s,\n" "\"Priority\" : %i,\n" "\"Time\" : %i,\n" "\"Match\" : \"%s\",\n" "\"Rule\" : %i,\n" "\"DupeKey\" : \"%s\",\n" "\"DupeScore\" : %i,\n" "\"DupeMode\" : \"%s\",\n" "\"Status\" : \"%s\"\n" "}"; const char* szStatusType[] = { "UNKNOWN", "BACKLOG", "FETCHED", "NEW" }; const char* szMatchStatusType[] = { "IGNORED", "ACCEPTED", "REJECTED" }; const char* szDupeModeType[] = { "SCORE", "ALL", "FORCE" }; int iItemBufSize = 10240; char* szItemBuf = (char*)malloc(iItemBufSize); AppendResponse(IsJson() ? "[\n" : "\n"); int index = 0; for (FeedItemInfos::iterator it = pFeedItemInfos->begin(); it != pFeedItemInfos->end(); it++) { FeedItemInfo* pFeedItemInfo = *it; if (bIncludeNonMatching || pFeedItemInfo->GetMatchStatus() == FeedItemInfo::msAccepted) { unsigned long iSizeHi, iSizeLo; Util::SplitInt64(pFeedItemInfo->GetSize(), &iSizeHi, &iSizeLo); int iSizeMB = (int)(pFeedItemInfo->GetSize() / 1024 / 1024); char* xmltitle = EncodeStr(pFeedItemInfo->GetTitle()); char* xmlfilename = EncodeStr(pFeedItemInfo->GetFilename()); char* xmlurl = EncodeStr(pFeedItemInfo->GetUrl()); char* xmlcategory = EncodeStr(pFeedItemInfo->GetCategory()); char* xmladdcategory = EncodeStr(pFeedItemInfo->GetAddCategory()); char* xmldupekey = EncodeStr(pFeedItemInfo->GetDupeKey()); snprintf(szItemBuf, iItemBufSize, IsJson() ? JSON_FEED_ITEM : XML_FEED_ITEM, xmltitle, xmlfilename, xmlurl, iSizeLo, iSizeHi, iSizeMB, xmlcategory, xmladdcategory, BoolToStr(pFeedItemInfo->GetPauseNzb()), pFeedItemInfo->GetPriority(), pFeedItemInfo->GetTime(), szMatchStatusType[pFeedItemInfo->GetMatchStatus()], pFeedItemInfo->GetMatchRule(), xmldupekey, pFeedItemInfo->GetDupeScore(), szDupeModeType[pFeedItemInfo->GetDupeMode()], szStatusType[pFeedItemInfo->GetStatus()]); szItemBuf[iItemBufSize-1] = '\0'; free(xmltitle); free(xmlfilename); free(xmlurl); free(xmlcategory); free(xmladdcategory); free(xmldupekey); if (IsJson() && index++ > 0) { AppendResponse(",\n"); } AppendResponse(szItemBuf); } } free(szItemBuf); pFeedItemInfos->Release(); AppendResponse(IsJson() ? "\n]" : "\n"); } // bool fetchfeed(int ID) void FetchFeedXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } int iID; if (!NextParamAsInt(&iID)) { BuildErrorResponse(2, "Invalid parameter (ID)"); return; } g_pFeedCoordinator->FetchFeed(iID); BuildBoolResponse(true); } // bool editserver(int ID, bool Active) void EditServerXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } bool bOK = false; int bFirst = true; int iID; while (NextParamAsInt(&iID)) { bFirst = false; bool bActive; if (!NextParamAsBool(&bActive)) { BuildErrorResponse(2, "Invalid parameter"); return; } for (Servers::iterator it = g_pServerPool->GetServers()->begin(); it != g_pServerPool->GetServers()->end(); it++) { NewsServer* pServer = *it; if (pServer->GetID() == iID) { pServer->SetActive(bActive); bOK = true; } } } if (bFirst) { BuildErrorResponse(2, "Invalid parameter"); return; } if (bOK) { g_pServerPool->Changed(); } BuildBoolResponse(bOK); } // string readurl(string url, string infoname) void ReadUrlXmlCommand::Execute() { char* szURL; if (!NextParamAsStr(&szURL)) { BuildErrorResponse(2, "Invalid parameter (URL)"); return; } DecodeStr(szURL); char* szInfoName; if (!NextParamAsStr(&szInfoName)) { BuildErrorResponse(2, "Invalid parameter (InfoName)"); return; } DecodeStr(szInfoName); // generate temp file name char szTempFileName[1024]; int iNum = 1; while (iNum == 1 || Util::FileExists(szTempFileName)) { snprintf(szTempFileName, 1024, "%sreadurl-%i.tmp", g_pOptions->GetTempDir(), iNum); szTempFileName[1024-1] = '\0'; iNum++; } WebDownloader* pDownloader = new WebDownloader(); pDownloader->SetURL(szURL); pDownloader->SetForce(true); pDownloader->SetRetry(false); pDownloader->SetOutputFilename(szTempFileName); pDownloader->SetInfoName(szInfoName); // do sync download WebDownloader::EStatus eStatus = pDownloader->Download(); bool bOK = eStatus == WebDownloader::adFinished; delete pDownloader; if (bOK) { char* szFileContent = NULL; int iFileContentLen = 0; Util::LoadFileIntoBuffer(szTempFileName, &szFileContent, &iFileContentLen); char* xmlContent = EncodeStr(szFileContent); free(szFileContent); AppendResponse(IsJson() ? "\"" : ""); AppendResponse(xmlContent); AppendResponse(IsJson() ? "\"" : ""); free(xmlContent); } else { BuildErrorResponse(3, "Could not read url"); } remove(szTempFileName); } // string checkupdates() void CheckUpdatesXmlCommand::Execute() { char* szUpdateInfo = NULL; bool bOK = g_pMaintenance->CheckUpdates(&szUpdateInfo); if (bOK) { char* xmlContent = EncodeStr(szUpdateInfo); free(szUpdateInfo); AppendResponse(IsJson() ? "\"" : ""); AppendResponse(xmlContent); AppendResponse(IsJson() ? "\"" : ""); free(xmlContent); } else { BuildErrorResponse(3, "Could not read update info from update-info-script"); } } // bool startupdate(string branch) void StartUpdateXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } char* szBranch; if (!NextParamAsStr(&szBranch)) { BuildErrorResponse(2, "Invalid parameter (Branch)"); return; } DecodeStr(szBranch); Maintenance::EBranch eBranch; if (!strcasecmp(szBranch, "stable")) { eBranch = Maintenance::brStable; } else if (!strcasecmp(szBranch, "testing")) { eBranch = Maintenance::brTesting; } else if (!strcasecmp(szBranch, "devel")) { eBranch = Maintenance::brDevel; } else { BuildErrorResponse(2, "Invalid parameter (Branch)"); return; } bool bOK = g_pMaintenance->StartUpdate(eBranch); BuildBoolResponse(bOK); } // struct[] logupdate(idfrom, entries) Log::Messages* LogUpdateXmlCommand::LockMessages() { return g_pMaintenance->LockMessages(); } void LogUpdateXmlCommand::UnlockMessages() { g_pMaintenance->UnlockMessages(); } nzbget-12.0+dfsg/XmlRpc.h000066400000000000000000000062661226450633000152350ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 881 $ * $Date: 2013-10-17 21:35:43 +0200 (Thu, 17 Oct 2013) $ * */ #ifndef XMLRPC_H #define XMLRPC_H #include "Connection.h" #include "Util.h" class XmlCommand; class XmlRpcProcessor { public: enum ERpcProtocol { rpUndefined, rpXmlRpc, rpJsonRpc, rpJsonPRpc }; enum EHttpMethod { hmPost, hmGet }; private: char* m_szRequest; const char* m_szContentType; ERpcProtocol m_eProtocol; EHttpMethod m_eHttpMethod; char* m_szUrl; StringBuilder m_cResponse; void Dispatch(); XmlCommand* CreateCommand(const char* szMethodName); void MutliCall(); void BuildResponse(const char* szResponse, const char* szCallbackFunc, bool bFault); public: XmlRpcProcessor(); ~XmlRpcProcessor(); void Execute(); void SetHttpMethod(EHttpMethod eHttpMethod) { m_eHttpMethod = eHttpMethod; } void SetUrl(const char* szUrl); void SetRequest(char* szRequest) { m_szRequest = szRequest; } const char* GetResponse() { return m_cResponse.GetBuffer(); } const char* GetContentType() { return m_szContentType; } static bool IsRpcRequest(const char* szUrl); }; class XmlCommand { protected: char* m_szRequest; char* m_szRequestPtr; char* m_szCallbackFunc; StringBuilder m_StringBuilder; bool m_bFault; XmlRpcProcessor::ERpcProtocol m_eProtocol; XmlRpcProcessor::EHttpMethod m_eHttpMethod; void BuildErrorResponse(int iErrCode, const char* szErrText, ...); void BuildBoolResponse(bool bOK); void AppendResponse(const char* szPart); bool IsJson(); bool CheckSafeMethod(); bool NextParamAsInt(int* iValue); bool NextParamAsBool(bool* bValue); bool NextParamAsStr(char** szValueBuf); const char* BoolToStr(bool bValue); char* EncodeStr(const char* szStr); void DecodeStr(char* szStr); public: XmlCommand(); virtual ~XmlCommand() {} virtual void Execute() = 0; void PrepareParams(); void SetRequest(char* szRequest) { m_szRequest = szRequest; m_szRequestPtr = m_szRequest; } void SetProtocol(XmlRpcProcessor::ERpcProtocol eProtocol) { m_eProtocol = eProtocol; } void SetHttpMethod(XmlRpcProcessor::EHttpMethod eHttpMethod) { m_eHttpMethod = eHttpMethod; } const char* GetResponse() { return m_StringBuilder.GetBuffer(); } const char* GetCallbackFunc() { return m_szCallbackFunc; } bool GetFault() { return m_bFault; } }; #endif nzbget-12.0+dfsg/aclocal.m4000066400000000000000000001067661226450633000155250ustar00rootroot00000000000000# generated automatically by aclocal 1.9.6 -*- Autoconf -*- # Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 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. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. # pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- # # Copyright © 2004 Scott James Remnant . # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # PKG_PROG_PKG_CONFIG([MIN-VERSION]) # ---------------------------------- AC_DEFUN([PKG_PROG_PKG_CONFIG], [m4_pattern_forbid([^_?PKG_[A-Z_]+$]) m4_pattern_allow([^PKG_CONFIG(_PATH)?$]) AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])dnl if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) fi if test -n "$PKG_CONFIG"; then _pkg_min_version=m4_default([$1], [0.9.0]) AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) PKG_CONFIG="" fi fi[]dnl ])# PKG_PROG_PKG_CONFIG # PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) # # Check to see whether a particular set of modules exists. Similar # to PKG_CHECK_MODULES(), but does not set variables or print errors. # # # Similar to PKG_CHECK_MODULES, make sure that the first instance of # this or PKG_CHECK_MODULES is called, or make sure to call # PKG_CHECK_EXISTS manually # -------------------------------------------------------------- AC_DEFUN([PKG_CHECK_EXISTS], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl if test -n "$PKG_CONFIG" && \ AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then m4_ifval([$2], [$2], [:]) m4_ifvaln([$3], [else $3])dnl fi]) # _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) # --------------------------------------------- m4_define([_PKG_CONFIG], [if test -n "$PKG_CONFIG"; then if test -n "$$1"; then pkg_cv_[]$1="$$1" else PKG_CHECK_EXISTS([$3], [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`], [pkg_failed=yes]) fi else pkg_failed=untried fi[]dnl ])# _PKG_CONFIG # _PKG_SHORT_ERRORS_SUPPORTED # ----------------------------- AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], [AC_REQUIRE([PKG_PROG_PKG_CONFIG]) if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi[]dnl ])# _PKG_SHORT_ERRORS_SUPPORTED # PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], # [ACTION-IF-NOT-FOUND]) # # # Note that if there is a possibility the first call to # PKG_CHECK_MODULES might not happen, you should be sure to include an # explicit call to PKG_PROG_PKG_CONFIG in your configure.ac # # # -------------------------------------------------------------- AC_DEFUN([PKG_CHECK_MODULES], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl pkg_failed=no AC_MSG_CHECKING([for $1]) _PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) _PKG_CONFIG([$1][_LIBS], [libs], [$2]) m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS and $1[]_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details.]) if test $pkg_failed = yes; then _PKG_SHORT_ERRORS_SUPPORTED if test $_pkg_short_errors_supported = yes; then $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --errors-to-stdout --print-errors "$2"` else $1[]_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "$2"` fi # Put the nasty error message in config.log where it belongs echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD ifelse([$4], , [AC_MSG_ERROR(dnl [Package requirements ($2) were not met: $$1_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. _PKG_TEXT ])], [AC_MSG_RESULT([no]) $4]) elif test $pkg_failed = untried; then ifelse([$4], , [AC_MSG_FAILURE(dnl [The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. _PKG_TEXT To get pkg-config, see .])], [$4]) else $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS $1[]_LIBS=$pkg_cv_[]$1[]_LIBS AC_MSG_RESULT([yes]) ifelse([$3], , :, [$3]) fi[]dnl ])# PKG_CHECK_MODULES # Copyright (C) 2002, 2003, 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. # AM_AUTOMAKE_VERSION(VERSION) # ---------------------------- # 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"]) # AM_SET_CURRENT_AUTOMAKE_VERSION # ------------------------------- # Call AM_AUTOMAKE_VERSION so it can be traced. # This function is AC_REQUIREd by AC_INIT_AUTOMAKE. AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION], [AM_AUTOMAKE_VERSION([1.9.6])]) # AM_AUX_DIR_EXPAND -*- Autoconf -*- # Copyright (C) 2001, 2003, 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. # For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets # $ac_aux_dir to `$srcdir/foo'. In other projects, it is set to # `$srcdir', `$srcdir/..', or `$srcdir/../..'. # # Of course, Automake must honor this variable whenever it calls a # tool from the auxiliary directory. The problem is that $srcdir (and # therefore $ac_aux_dir as well) can be either absolute or relative, # depending on how configure is run. This is pretty annoying, since # it makes $ac_aux_dir quite unusable in subdirectories: in the top # source directory, any form will work fine, but in subdirectories a # relative path needs to be adjusted first. # # $ac_aux_dir/missing # fails when called from a subdirectory if $ac_aux_dir is relative # $top_srcdir/$ac_aux_dir/missing # fails if $ac_aux_dir is absolute, # fails when called from a subdirectory in a VPATH build with # a relative $ac_aux_dir # # The reason of the latter failure is that $top_srcdir and $ac_aux_dir # are both prefixed by $srcdir. In an in-source build this is usually # harmless because $srcdir is `.', but things will broke when you # start a VPATH build or use an absolute $srcdir. # # So we could use something similar to $top_srcdir/$ac_aux_dir/missing, # iff we strip the leading $srcdir from $ac_aux_dir. That would be: # am_aux_dir='\$(top_srcdir)/'`expr "$ac_aux_dir" : "$srcdir//*\(.*\)"` # and then we would define $MISSING as # MISSING="\${SHELL} $am_aux_dir/missing" # This will work as long as MISSING is not called from configure, because # unfortunately $(top_srcdir) has no meaning in configure. # However there are other variables, like CC, which are often used in # configure, and could therefore not use this "fixed" $ac_aux_dir. # # Another solution, used here, is to always expand $ac_aux_dir to an # absolute PATH. The drawback is that using absolute paths prevent a # configured tree to be moved without reconfiguration. AC_DEFUN([AM_AUX_DIR_EXPAND], [dnl Rely on autoconf to set up CDPATH properly. AC_PREREQ([2.50])dnl # expand $ac_aux_dir to an absolute path am_aux_dir=`cd $ac_aux_dir && pwd` ]) # AM_CONDITIONAL -*- Autoconf -*- # Copyright (C) 1997, 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 7 # AM_CONDITIONAL(NAME, SHELL-CONDITION) # ------------------------------------- # Define a conditional. 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]) if $2; then $1_TRUE= $1_FALSE='#' else $1_TRUE='#' $1_FALSE= fi AC_CONFIG_COMMANDS_PRE( [if test -z "${$1_TRUE}" && test -z "${$1_FALSE}"; then AC_MSG_ERROR([[conditional "$1" was never defined. Usually this means the macro was only invoked conditionally.]]) fi])]) # Copyright (C) 1999, 2000, 2001, 2002, 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 8 # 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, # will think it sees a *use*, and therefore will trigger all it's # C support machinery. Also note that it means that autoscan, seeing # CC etc. in the Makefile, will ask for an AC_PROG_CC use... # _AM_DEPENDENCIES(NAME) # ---------------------- # See how the compiler implements dependency checking. # NAME is "CC", "CXX", "GCJ", or "OBJC". # We try a few techniques and use that to set a single cache variable. # # We don't AC_REQUIRE the corresponding AC_PROG_CC since the latter was # modified to invoke _AM_DEPENDENCIES(CC); we would have a circular # dependency, and given that the user is not expected to run this macro, # just rely on AC_PROG_CC. AC_DEFUN([_AM_DEPENDENCIES], [AC_REQUIRE([AM_SET_DEPDIR])dnl AC_REQUIRE([AM_OUTPUT_DEPENDENCY_COMMANDS])dnl AC_REQUIRE([AM_MAKE_INCLUDE])dnl 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], GCJ, [depcc="$GCJ" am_compiler_list='gcc3 gcc'], [depcc="$$1" am_compiler_list=]) AC_CACHE_CHECK([dependency style of $depcc], [am_cv_$1_dependencies_compiler_type], [if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then # We make a subdir and do the tests there. Otherwise we can end up # making bogus files that we don't know about and never remove. For # instance it was reported that on HP-UX the gcc test will end up # making a dummy file named `D' -- because `-MD' means `put the output # in D'. mkdir conftest.dir # Copy depcomp to subdir because otherwise we won't find it if we're # using a relative directory. cp "$am_depcomp" conftest.dir cd conftest.dir # We will build objects and dependencies in a subdirectory because # it helps to detect inapplicable dependency modes. For instance # both Tru64's cc and ICC support -MD to output dependencies as a # side effect of compilation, but ICC will put the dependencies in # the current directory while Tru64 will put them in the object # directory. mkdir sub am_cv_$1_dependencies_compiler_type=none if test "$am_compiler_list" = ""; then am_compiler_list=`sed -n ['s/^#*\([a-zA-Z0-9]*\))$/\1/p'] < ./depcomp` fi for depmode in $am_compiler_list; do # Setup a source with many dependencies, because some compilers # like to wrap large dependency lists on column 80 (with \), and # we should not choose a depcomp mode which is confused by this. # # We need to recreate these files for each test, as the compiler may # overwrite some of them when testing with obscure command lines. # This happens at least with the AIX C compiler. : > sub/conftest.c for i in 1 2 3 4 5 6; do echo '#include "conftst'$i'.h"' >> sub/conftest.c # Using `: > sub/conftst$i.h' creates only sub/conftst1.h with # Solaris 8's {/usr,}/bin/sh. touch sub/conftst$i.h done echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf case $depmode in nosideeffect) # after this tag, mechanisms are not by side-effect, so they'll # only be used when explicitly requested if test "x$enable_dependency_tracking" = xyes; then continue else break fi ;; none) break ;; esac # We check with `-c' and `-o' for the sake of the "dashmstdout" # mode. It turns out that the SunPro C++ compiler does not properly # handle `-M -o', and we need to detect this. if depmode=$depmode \ source=sub/conftest.c object=sub/conftest.${OBJEXT-o} \ 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/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 # icc doesn't choke on unknown options, it will just issue warnings # or remarks (even with -Werror). So we grep stderr for any message # that says an option was ignored or not supported. # When given -MP, icc 7.0 and 7.1 complain thusly: # icc: Command line warning: ignoring option '-M'; no argument required # The diagnosis changed in icc 8.0: # icc: Command line remark: option '-MP' not supported if (grep 'ignoring option' conftest.err || grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else am_cv_$1_dependencies_compiler_type=$depmode break fi fi done cd .. rm -rf conftest.dir else am_cv_$1_dependencies_compiler_type=none fi ]) AC_SUBST([$1DEPMODE], [depmode=$am_cv_$1_dependencies_compiler_type]) AM_CONDITIONAL([am__fastdep$1], [ test "x$enable_dependency_tracking" != xno \ && test "$am_cv_$1_dependencies_compiler_type" = gcc3]) ]) # AM_SET_DEPDIR # ------------- # Choose a directory name for dependency files. # This macro is AC_REQUIREd in _AM_DEPENDENCIES AC_DEFUN([AM_SET_DEPDIR], [AC_REQUIRE([AM_SET_LEADING_DOT])dnl AC_SUBST([DEPDIR], ["${am__leading_dot}deps"])dnl ]) # AM_DEP_TRACK # ------------ AC_DEFUN([AM_DEP_TRACK], [AC_ARG_ENABLE(dependency-tracking, [ --disable-dependency-tracking speeds up one-time build --enable-dependency-tracking do not reject slow dependency extractors]) if test "x$enable_dependency_tracking" != xno; then am_depcomp="$ac_aux_dir/depcomp" AMDEPBACKSLASH='\' fi AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno]) AC_SUBST([AMDEPBACKSLASH]) ]) # Generate code to set up dependency tracking. -*- Autoconf -*- # Copyright (C) 1999, 2000, 2001, 2002, 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 3 # _AM_OUTPUT_DEPENDENCY_COMMANDS # ------------------------------ AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS], [for mf in $CONFIG_FILES; do # Strip MF so we end up with the name of the file. mf=`echo "$mf" | sed -e 's/:.*$//'` # Check whether this is an Automake generated Makefile or not. # We used to match only the files named `Makefile.in', but # 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 dirpart=`AS_DIRNAME("$mf")` else continue fi # Extract the definition of DEPDIR, am__include, and am__quote # from the Makefile without running `make'. DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"` test -z "$DEPDIR" && continue am__include=`sed -n 's/^am__include = //p' < "$mf"` test -z "am__include" && continue am__quote=`sed -n 's/^am__quote = //p' < "$mf"` # When using ansi2knr, U may be empty or an underscore; expand it U=`sed -n 's/^U = //p' < "$mf"` # Find all dependency output files, they are included files with # $(DEPDIR) in their names. We invoke sed twice because it is the # simplest approach to changing $(DEPDIR) to its actual value in the # expansion. for file in `sed -n " s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \ sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g' -e 's/\$U/'"$U"'/g'`; do # Make sure the directory exists. test -f "$dirpart/$file" && continue fdir=`AS_DIRNAME(["$file"])` AS_MKDIR_P([$dirpart/$fdir]) # echo "creating $dirpart/$file" echo '# dummy' > "$dirpart/$file" done done ])# _AM_OUTPUT_DEPENDENCY_COMMANDS # AM_OUTPUT_DEPENDENCY_COMMANDS # ----------------------------- # This macro should only be invoked once -- use via AC_REQUIRE. # # This code is only required when automatic dependency tracking # is enabled. FIXME. This creates each `.P' file that we will # need in order to bootstrap the dependency handling code. AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS], [AC_CONFIG_COMMANDS([depfiles], [test x"$AMDEP_TRUE" != x"" || _AM_OUTPUT_DEPENDENCY_COMMANDS], [AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir"]) ]) # Do all the work for Automake. -*- Autoconf -*- # Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 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 12 # This macro actually does too much. Some checks are only needed if # your package does certain things. But this isn't really a big deal. # AM_INIT_AUTOMAKE(PACKAGE, VERSION, [NO-DEFINE]) # AM_INIT_AUTOMAKE([OPTIONS]) # ----------------------------------------------- # The call with PACKAGE and VERSION arguments is the old style # call (pre autoconf-2.50), which is being phased out. PACKAGE # and VERSION should now be passed to AC_INIT and removed from # the call to AM_INIT_AUTOMAKE. # We support both call styles for the transition. After # the next Automake release, Autoconf can make the AC_INIT # 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 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]) fi # test whether we have cygpath if test -z "$CYGPATH_W"; then if (cygpath --version) >/dev/null 2>/dev/null; then CYGPATH_W='cygpath -w' else CYGPATH_W=echo fi fi AC_SUBST([CYGPATH_W]) # Define the identity of the package. dnl Distinguish between old-style and new-style calls. m4_ifval([$2], [m4_ifval([$3], [_AM_SET_OPTION([no-define])])dnl AC_SUBST([PACKAGE], [$1])dnl AC_SUBST([VERSION], [$2])], [_AM_SET_OPTIONS([$1])dnl AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl AC_SUBST([VERSION], ['AC_PACKAGE_VERSION'])])dnl _AM_IF_OPTION([no-define],, [AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE", [Name of package]) AC_DEFINE_UNQUOTED(VERSION, "$VERSION", [Version number of package])])dnl # Some tools Automake needs. AC_REQUIRE([AM_SANITY_CHECK])dnl AC_REQUIRE([AC_ARG_PROGRAM])dnl AM_MISSING_PROG(ACLOCAL, aclocal-${am__api_version}) AM_MISSING_PROG(AUTOCONF, autoconf) AM_MISSING_PROG(AUTOMAKE, automake-${am__api_version}) AM_MISSING_PROG(AUTOHEADER, autoheader) AM_MISSING_PROG(MAKEINFO, makeinfo) AM_PROG_INSTALL_SH AM_PROG_INSTALL_STRIP AC_REQUIRE([AM_PROG_MKDIR_P])dnl # We need awk for the "check" target. The system "awk" is bad on # some platforms. AC_REQUIRE([AC_PROG_AWK])dnl AC_REQUIRE([AC_PROG_MAKE_SET])dnl AC_REQUIRE([AM_SET_LEADING_DOT])dnl _AM_IF_OPTION([tar-ustar], [_AM_PROG_TAR([ustar])], [_AM_IF_OPTION([tar-pax], [_AM_PROG_TAR([pax])], [_AM_PROG_TAR([v7])])]) _AM_IF_OPTION([no-dependencies],, [AC_PROVIDE_IFELSE([AC_PROG_CC], [_AM_DEPENDENCIES(CC)], [define([AC_PROG_CC], defn([AC_PROG_CC])[_AM_DEPENDENCIES(CC)])])dnl AC_PROVIDE_IFELSE([AC_PROG_CXX], [_AM_DEPENDENCIES(CXX)], [define([AC_PROG_CXX], defn([AC_PROG_CXX])[_AM_DEPENDENCIES(CXX)])])dnl ]) ]) # When config.status generates a header, we must update the stamp-h file. # This file resides in the same directory as the config header # that is generated. The stamp files are numbered to have different names. # Autoconf calls _AC_AM_CONFIG_HEADER_HOOK (when defined) in the # loop where config.status creates the headers, so we can generate # our stamp files there. AC_DEFUN([_AC_AM_CONFIG_HEADER_HOOK], [# Compute $1's index in $config_headers. _am_stamp_count=1 for _am_header in $config_headers :; do case $_am_header in $1 | $1:* ) break ;; * ) _am_stamp_count=`expr $_am_stamp_count + 1` ;; esac done echo "timestamp for $1" >`AS_DIRNAME([$1])`/stamp-h[]$_am_stamp_count]) # Copyright (C) 2001, 2003, 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. # AM_PROG_INSTALL_SH # ------------------ # 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"} AC_SUBST(install_sh)]) # Copyright (C) 2003, 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 2 # Check whether the underlying file-system supports filenames # with a leading dot. For instance MS-DOS doesn't. AC_DEFUN([AM_SET_LEADING_DOT], [rm -rf .tst 2>/dev/null mkdir .tst 2>/dev/null if test -d .tst; then am__leading_dot=. else am__leading_dot=_ fi rmdir .tst 2>/dev/null AC_SUBST([am__leading_dot])]) # Check to see how 'make' treats includes. -*- Autoconf -*- # Copyright (C) 2001, 2002, 2003, 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 3 # AM_MAKE_INCLUDE() # ----------------- # Check to see how make treats includes. AC_DEFUN([AM_MAKE_INCLUDE], [am_make=${MAKE-make} cat > confinc << 'END' am__doit: @echo done .PHONY: am__doit END # If we don't find an include directive, just comment out the code. AC_MSG_CHECKING([for style of include used by $am_make]) am__include="#" am__quote= _am_result=none # First try GNU make style include. echo "include confinc" > confmf # We grep out `Entering directory' and `Leaving directory' # messages which can occur if `w' ends up in MAKEFLAGS. # In particular we don't look at `^make:' because GNU make might # be invoked under some other name (usually "gmake"), in which # case it prints its new name instead of `make'. if test "`$am_make -s -f confmf 2> /dev/null | grep -v 'ing directory'`" = "done"; then am__include=include am__quote= _am_result=GNU fi # Now try BSD make style include. if test "$am__include" = "#"; then echo '.include "confinc"' > confmf if test "`$am_make -s -f confmf 2> /dev/null`" = "done"; then am__include=.include am__quote="\"" _am_result=BSD fi fi AC_SUBST([am__include]) AC_SUBST([am__quote]) AC_MSG_RESULT([$_am_result]) rm -f confinc confmf ]) # Fake the existence of programs that GNU maintainers use. -*- Autoconf -*- # Copyright (C) 1997, 1999, 2000, 2001, 2003, 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 # AM_MISSING_PROG(NAME, PROGRAM) # ------------------------------ AC_DEFUN([AM_MISSING_PROG], [AC_REQUIRE([AM_MISSING_HAS_RUN]) $1=${$1-"${am_missing_run}$2"} AC_SUBST($1)]) # AM_MISSING_HAS_RUN # ------------------ # Define MISSING if not defined so far and test if it supports --run. # 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 test x"${MISSING+set}" = xset || MISSING="\${SHELL} $am_aux_dir/missing" # Use eval to expand $SHELL if eval "$MISSING --run true"; then am_missing_run="$MISSING --run " else am_missing_run= AC_MSG_WARN([`missing' script is too old or missing]) fi ]) # Copyright (C) 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. # 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.) 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])]) # Helper functions for option handling. -*- Autoconf -*- # Copyright (C) 2001, 2002, 2003, 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 3 # _AM_MANGLE_OPTION(NAME) # ----------------------- AC_DEFUN([_AM_MANGLE_OPTION], [[_AM_OPTION_]m4_bpatsubst($1, [[^a-zA-Z0-9_]], [_])]) # _AM_SET_OPTION(NAME) # ------------------------------ # Set option NAME. Presently that only means defining a flag for this option. AC_DEFUN([_AM_SET_OPTION], [m4_define(_AM_MANGLE_OPTION([$1]), 1)]) # _AM_SET_OPTIONS(OPTIONS) # ---------------------------------- # OPTIONS is a space-separated list of Automake options. AC_DEFUN([_AM_SET_OPTIONS], [AC_FOREACH([_AM_Option], [$1], [_AM_SET_OPTION(_AM_Option)])]) # _AM_IF_OPTION(OPTION, IF-SET, [IF-NOT-SET]) # ------------------------------------------- # Execute IF-SET if OPTION is set, IF-NOT-SET otherwise. AC_DEFUN([_AM_IF_OPTION], [m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])]) # Check to make sure that the build environment is sane. -*- Autoconf -*- # Copyright (C) 1996, 1997, 2000, 2001, 2003, 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 # AM_SANITY_CHECK # --------------- AC_DEFUN([AM_SANITY_CHECK], [AC_MSG_CHECKING([whether build environment is sane]) # Just in case sleep 1 echo timestamp > conftest.file # Do `set' in a subshell so we don't clobber the current shell's # arguments. Must try -L first in case configure is actually a # symlink; some systems play weird games with the mod time of symlinks # (eg FreeBSD returns the mod time of the symlink's containing # directory). if ( set X `ls -Lt $srcdir/configure conftest.file 2> /dev/null` if test "$[*]" = "X"; then # -L didn't work. set X `ls -t $srcdir/configure conftest.file` fi rm -f conftest.file if test "$[*]" != "X $srcdir/configure conftest.file" \ && test "$[*]" != "X conftest.file $srcdir/configure"; then # If neither matched, then we have a broken ls. This can happen # if, for instance, CONFIG_SHELL is bash and it inherits a # broken ls alias from the environment. This has actually # happened. Such a system could not be considered "sane". AC_MSG_ERROR([ls -t appears to fail. Make sure there is not a broken alias in your environment]) fi test "$[2]" = conftest.file ) then # Ok. : else AC_MSG_ERROR([newly created file is older than distributed files! Check your system clock]) fi AC_MSG_RESULT(yes)]) # Copyright (C) 2001, 2003, 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. # AM_PROG_INSTALL_STRIP # --------------------- # One issue with vendor `install' (even GNU) is that you can't # specify the program used to strip binaries. This is especially # annoying in cross-compiling environments, where the build's strip # is unlikely to handle the host's binaries. # Fortunately install-sh will honor a STRIPPROG variable, so we # always use install-sh in `make install-strip', and initialize # STRIPPROG with the value of the STRIP variable (set by the user). AC_DEFUN([AM_PROG_INSTALL_STRIP], [AC_REQUIRE([AM_PROG_INSTALL_SH])dnl # Installed binaries are usually stripped using `strip' when the user # run `make install-strip'. However `strip' might not be the right # tool to use in cross-compilation environments, therefore Automake # will honor the `STRIP' environment variable to overrule this program. 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" AC_SUBST([INSTALL_STRIP_PROGRAM])]) # Check how to create a tarball. -*- Autoconf -*- # Copyright (C) 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 2 # _AM_PROG_TAR(FORMAT) # -------------------- # Check how to create a tarball in format FORMAT. # FORMAT should be one of `v7', `ustar', or `pax'. # # Substitute a variable $(am__tar) that is a command # writing to stdout a FORMAT-tarball containing the directory # $tardir. # tardir=directory && $(am__tar) > result.tar # # Substitute a variable $(am__untar) that extract such # a tarball read from stdin. # $(am__untar) < result.tar AC_DEFUN([_AM_PROG_TAR], [# Always define AMTAR for backward compatibility. AM_MISSING_PROG([AMTAR], [tar]) m4_if([$1], [v7], [am__tar='${AMTAR} chof - "$$tardir"'; am__untar='${AMTAR} xf -'], [m4_case([$1], [ustar],, [pax],, [m4_fatal([Unknown tar format])]) AC_MSG_CHECKING([how to create a $1 tar archive]) # Loop over all known methods to create a tar archive until one works. _am_tools='gnutar m4_if([$1], [ustar], [plaintar]) pax cpio none' _am_tools=${am_cv_prog_tar_$1-$_am_tools} # Do not fold the above two line into one, because Tru64 sh and # Solaris sh will not grok spaces in the rhs of `-'. for _am_tool in $_am_tools do case $_am_tool in gnutar) for _am_tar in tar gnutar gtar; do AM_RUN_LOG([$_am_tar --version]) && break done am__tar="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$$tardir"' am__tar_="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$tardir"' am__untar="$_am_tar -xf -" ;; plaintar) # Must skip GNU tar: if it does not support --format= it doesn't create # ustar tarball either. (tar --version) >/dev/null 2>&1 && continue am__tar='tar chf - "$$tardir"' am__tar_='tar chf - "$tardir"' am__untar='tar xf -' ;; pax) am__tar='pax -L -x $1 -w "$$tardir"' am__tar_='pax -L -x $1 -w "$tardir"' am__untar='pax -r' ;; cpio) am__tar='find "$$tardir" -print | cpio -o -H $1 -L' am__tar_='find "$tardir" -print | cpio -o -H $1 -L' am__untar='cpio -i -H $1 -d' ;; none) am__tar=false am__tar_=false am__untar=false ;; esac # If the value was cached, stop now. We just wanted to have am__tar # and am__untar set. test -n "${am_cv_prog_tar_$1}" && break # tar/untar a dummy directory, and stop if the command works rm -rf conftest.dir mkdir conftest.dir echo GrepMe > conftest.dir/file AM_RUN_LOG([tardir=conftest.dir && eval $am__tar_ >conftest.tar]) rm -rf conftest.dir if test -s conftest.tar; then AM_RUN_LOG([$am__untar /dev/null 2>&1 && break fi done rm -rf conftest.dir AC_CACHE_VAL([am_cv_prog_tar_$1], [am_cv_prog_tar_$1=$_am_tool]) AC_MSG_RESULT([$am_cv_prog_tar_$1])]) AC_SUBST([am__tar]) AC_SUBST([am__untar]) ]) # _AM_PROG_TAR nzbget-12.0+dfsg/config.guess000077500000000000000000001262601226450633000161740ustar00rootroot00000000000000#! /bin/sh # Attempt to guess a canonical system name. # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, # 2000, 2001, 2002, 2003, 2004, 2005, 2006 Free Software Foundation, # Inc. timestamp='2006-07-02' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA # 02110-1301, USA. # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # Originally written by Per Bothner . # Please send patches to . Submit a context # diff and a properly formatted ChangeLog entry. # # This script attempts to guess a canonical system name similar to # config.sub. If it succeeds, it prints the system name on stdout, and # exits with 0. Otherwise, it exits with 1. # # The plan is that this can be called by configure scripts if you # don't specify an explicit build system type. me=`echo "$0" | sed -e 's,.*/,,'` usage="\ Usage: $0 [OPTION] Output the configuration name of the system \`$me' is run on. Operation modes: -h, --help print this help, then exit -t, --time-stamp print date of last modification, then exit -v, --version print version number, then exit Report bugs and patches to ." version="\ GNU config.guess ($timestamp) Originally written by Per Bothner. Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." help=" Try \`$me --help' for more information." # Parse command line while test $# -gt 0 ; do case $1 in --time-stamp | --time* | -t ) echo "$timestamp" ; exit ;; --version | -v ) echo "$version" ; exit ;; --help | --h* | -h ) echo "$usage"; exit ;; -- ) # Stop option processing shift; break ;; - ) # Use stdin as input. break ;; -* ) echo "$me: invalid option $1$help" >&2 exit 1 ;; * ) break ;; esac done if test $# != 0; then echo "$me: too many arguments$help" >&2 exit 1 fi trap 'exit 1' 1 2 15 # CC_FOR_BUILD -- compiler used by this script. Note that the use of a # compiler to aid in system detection is discouraged as it requires # temporary files to be created and, as you can see below, it is a # headache to deal with in a portable fashion. # Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still # use `HOST_CC' if defined, but it is deprecated. # Portable tmp directory creation inspired by the Autoconf team. set_cc_for_build=' trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ; trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ; : ${TMPDIR=/tmp} ; { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } || { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } || { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ; dummy=$tmp/dummy ; tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ; case $CC_FOR_BUILD,$HOST_CC,$CC in ,,) echo "int x;" > $dummy.c ; for c in cc gcc c89 c99 ; do if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then CC_FOR_BUILD="$c"; break ; fi ; done ; if test x"$CC_FOR_BUILD" = x ; then CC_FOR_BUILD=no_compiler_found ; fi ;; ,,*) CC_FOR_BUILD=$CC ;; ,*,*) CC_FOR_BUILD=$HOST_CC ;; esac ; set_cc_for_build= ;' # This is needed to find uname on a Pyramid OSx when run in the BSD universe. # (ghazi@noc.rutgers.edu 1994-08-24) if (test -f /.attbin/uname) >/dev/null 2>&1 ; then PATH=$PATH:/.attbin ; export PATH fi UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown # Note: order is significant - the case branches are not exclusive. case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in *:NetBSD:*:*) # NetBSD (nbsd) targets should (where applicable) match one or # more of the tupples: *-*-netbsdelf*, *-*-netbsdaout*, # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently # switched to ELF, *-*-netbsd* would select the old # object file format. This provides both forward # compatibility and a consistent mechanism for selecting the # object file format. # # Note: NetBSD doesn't particularly care about the vendor # portion of the name. We always set it to "unknown". sysctl="sysctl -n hw.machine_arch" UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \ /usr/sbin/$sysctl 2>/dev/null || echo unknown)` case "${UNAME_MACHINE_ARCH}" in armeb) machine=armeb-unknown ;; arm*) machine=arm-unknown ;; sh3el) machine=shl-unknown ;; sh3eb) machine=sh-unknown ;; *) machine=${UNAME_MACHINE_ARCH}-unknown ;; esac # The Operating System including object format, if it has switched # to ELF recently, or will in the future. case "${UNAME_MACHINE_ARCH}" in arm*|i386|m68k|ns32k|sh3*|sparc|vax) eval $set_cc_for_build if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ | grep __ELF__ >/dev/null then # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). # Return netbsd for either. FIX? os=netbsd else os=netbsdelf fi ;; *) os=netbsd ;; esac # The OS release # Debian GNU/NetBSD machines have a different userland, and # thus, need a distinct triplet. However, they do not need # kernel version information, so it can be replaced with a # suitable tag, in the style of linux-gnu. case "${UNAME_VERSION}" in Debian*) release='-gnu' ;; *) release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` ;; esac # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: # contains redundant information, the shorter form: # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. echo "${machine}-${os}${release}" exit ;; *:OpenBSD:*:*) UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'` echo ${UNAME_MACHINE_ARCH}-unknown-openbsd${UNAME_RELEASE} exit ;; *:ekkoBSD:*:*) echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE} exit ;; *:SolidBSD:*:*) echo ${UNAME_MACHINE}-unknown-solidbsd${UNAME_RELEASE} exit ;; macppc:MirBSD:*:*) echo powerpc-unknown-mirbsd${UNAME_RELEASE} exit ;; *:MirBSD:*:*) echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE} exit ;; alpha:OSF1:*:*) case $UNAME_RELEASE in *4.0) UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` ;; *5.*) UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` ;; esac # According to Compaq, /usr/sbin/psrinfo has been available on # OSF/1 and Tru64 systems produced since 1995. I hope that # covers most systems running today. This code pipes the CPU # types through head -n 1, so we only detect the type of CPU 0. ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` case "$ALPHA_CPU_TYPE" in "EV4 (21064)") UNAME_MACHINE="alpha" ;; "EV4.5 (21064)") UNAME_MACHINE="alpha" ;; "LCA4 (21066/21068)") UNAME_MACHINE="alpha" ;; "EV5 (21164)") UNAME_MACHINE="alphaev5" ;; "EV5.6 (21164A)") UNAME_MACHINE="alphaev56" ;; "EV5.6 (21164PC)") UNAME_MACHINE="alphapca56" ;; "EV5.7 (21164PC)") UNAME_MACHINE="alphapca57" ;; "EV6 (21264)") UNAME_MACHINE="alphaev6" ;; "EV6.7 (21264A)") UNAME_MACHINE="alphaev67" ;; "EV6.8CB (21264C)") UNAME_MACHINE="alphaev68" ;; "EV6.8AL (21264B)") UNAME_MACHINE="alphaev68" ;; "EV6.8CX (21264D)") UNAME_MACHINE="alphaev68" ;; "EV6.9A (21264/EV69A)") UNAME_MACHINE="alphaev69" ;; "EV7 (21364)") UNAME_MACHINE="alphaev7" ;; "EV7.9 (21364A)") UNAME_MACHINE="alphaev79" ;; esac # A Pn.n version is a patched version. # A Vn.n version is a released version. # A Tn.n version is a released field test version. # A Xn.n version is an unreleased experimental baselevel. # 1.2 uses "1.2" for uname -r. echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` exit ;; Alpha\ *:Windows_NT*:*) # How do we know it's Interix rather than the generic POSIX subsystem? # Should we change UNAME_MACHINE based on the output of uname instead # of the specific Alpha model? echo alpha-pc-interix exit ;; 21064:Windows_NT:50:3) echo alpha-dec-winnt3.5 exit ;; Amiga*:UNIX_System_V:4.0:*) echo m68k-unknown-sysv4 exit ;; *:[Aa]miga[Oo][Ss]:*:*) echo ${UNAME_MACHINE}-unknown-amigaos exit ;; *:[Mm]orph[Oo][Ss]:*:*) echo ${UNAME_MACHINE}-unknown-morphos exit ;; *:OS/390:*:*) echo i370-ibm-openedition exit ;; *:z/VM:*:*) echo s390-ibm-zvmoe exit ;; *:OS400:*:*) echo powerpc-ibm-os400 exit ;; arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) echo arm-acorn-riscix${UNAME_RELEASE} exit ;; arm:riscos:*:*|arm:RISCOS:*:*) echo arm-unknown-riscos exit ;; SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) echo hppa1.1-hitachi-hiuxmpp exit ;; Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. if test "`(/bin/universe) 2>/dev/null`" = att ; then echo pyramid-pyramid-sysv3 else echo pyramid-pyramid-bsd fi exit ;; NILE*:*:*:dcosx) echo pyramid-pyramid-svr4 exit ;; DRS?6000:unix:4.0:6*) echo sparc-icl-nx6 exit ;; DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) case `/usr/bin/uname -p` in sparc) echo sparc-icl-nx7; exit ;; esac ;; sun4H:SunOS:5.*:*) echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit ;; sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit ;; i86pc:SunOS:5.*:*) echo i386-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit ;; sun4*:SunOS:6*:*) # According to config.sub, this is the proper way to canonicalize # SunOS6. Hard to guess exactly what SunOS6 will be like, but # it's likely to be more like Solaris than SunOS4. echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit ;; sun4*:SunOS:*:*) case "`/usr/bin/arch -k`" in Series*|S4*) UNAME_RELEASE=`uname -v` ;; esac # Japanese Language versions have a version number like `4.1.3-JL'. echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` exit ;; sun3*:SunOS:*:*) echo m68k-sun-sunos${UNAME_RELEASE} exit ;; sun*:*:4.2BSD:*) UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 case "`/bin/arch`" in sun3) echo m68k-sun-sunos${UNAME_RELEASE} ;; sun4) echo sparc-sun-sunos${UNAME_RELEASE} ;; esac exit ;; aushp:SunOS:*:*) echo sparc-auspex-sunos${UNAME_RELEASE} exit ;; # The situation for MiNT is a little confusing. The machine name # can be virtually everything (everything which is not # "atarist" or "atariste" at least should have a processor # > m68000). The system name ranges from "MiNT" over "FreeMiNT" # to the lowercase version "mint" (or "freemint"). Finally # the system name "TOS" denotes a system which is actually not # MiNT. But MiNT is downward compatible to TOS, so this should # be no problem. atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) echo m68k-atari-mint${UNAME_RELEASE} exit ;; atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) echo m68k-atari-mint${UNAME_RELEASE} exit ;; *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) echo m68k-atari-mint${UNAME_RELEASE} exit ;; milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) echo m68k-milan-mint${UNAME_RELEASE} exit ;; hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) echo m68k-hades-mint${UNAME_RELEASE} exit ;; *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) echo m68k-unknown-mint${UNAME_RELEASE} exit ;; m68k:machten:*:*) echo m68k-apple-machten${UNAME_RELEASE} exit ;; powerpc:machten:*:*) echo powerpc-apple-machten${UNAME_RELEASE} exit ;; RISC*:Mach:*:*) echo mips-dec-mach_bsd4.3 exit ;; RISC*:ULTRIX:*:*) echo mips-dec-ultrix${UNAME_RELEASE} exit ;; VAX*:ULTRIX*:*:*) echo vax-dec-ultrix${UNAME_RELEASE} exit ;; 2020:CLIX:*:* | 2430:CLIX:*:*) echo clipper-intergraph-clix${UNAME_RELEASE} exit ;; mips:*:*:UMIPS | mips:*:*:RISCos) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #ifdef __cplusplus #include /* for printf() prototype */ int main (int argc, char *argv[]) { #else int main (argc, argv) int argc; char *argv[]; { #endif #if defined (host_mips) && defined (MIPSEB) #if defined (SYSTYPE_SYSV) printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); #endif #if defined (SYSTYPE_SVR4) printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); #endif #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); #endif #endif exit (-1); } EOF $CC_FOR_BUILD -o $dummy $dummy.c && dummyarg=`echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` && SYSTEM_NAME=`$dummy $dummyarg` && { echo "$SYSTEM_NAME"; exit; } echo mips-mips-riscos${UNAME_RELEASE} exit ;; Motorola:PowerMAX_OS:*:*) echo powerpc-motorola-powermax exit ;; Motorola:*:4.3:PL8-*) echo powerpc-harris-powermax exit ;; Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) echo powerpc-harris-powermax exit ;; Night_Hawk:Power_UNIX:*:*) echo powerpc-harris-powerunix exit ;; m88k:CX/UX:7*:*) echo m88k-harris-cxux7 exit ;; m88k:*:4*:R4*) echo m88k-motorola-sysv4 exit ;; m88k:*:3*:R3*) echo m88k-motorola-sysv3 exit ;; AViiON:dgux:*:*) # DG/UX returns AViiON for all architectures UNAME_PROCESSOR=`/usr/bin/uname -p` if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ] then if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \ [ ${TARGET_BINARY_INTERFACE}x = x ] then echo m88k-dg-dgux${UNAME_RELEASE} else echo m88k-dg-dguxbcs${UNAME_RELEASE} fi else echo i586-dg-dgux${UNAME_RELEASE} fi exit ;; M88*:DolphinOS:*:*) # DolphinOS (SVR3) echo m88k-dolphin-sysv3 exit ;; M88*:*:R3*:*) # Delta 88k system running SVR3 echo m88k-motorola-sysv3 exit ;; XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) echo m88k-tektronix-sysv3 exit ;; Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) echo m68k-tektronix-bsd exit ;; *:IRIX*:*:*) echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` exit ;; ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id exit ;; # Note that: echo "'`uname -s`'" gives 'AIX ' i*86:AIX:*:*) echo i386-ibm-aix exit ;; ia64:AIX:*:*) if [ -x /usr/bin/oslevel ] ; then IBM_REV=`/usr/bin/oslevel` else IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} fi echo ${UNAME_MACHINE}-ibm-aix${IBM_REV} exit ;; *:AIX:2:3) if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #include main() { if (!__power_pc()) exit(1); puts("powerpc-ibm-aix3.2.5"); exit(0); } EOF if $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` then echo "$SYSTEM_NAME" else echo rs6000-ibm-aix3.2.5 fi elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then echo rs6000-ibm-aix3.2.4 else echo rs6000-ibm-aix3.2 fi exit ;; *:AIX:*:[45]) IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then IBM_ARCH=rs6000 else IBM_ARCH=powerpc fi if [ -x /usr/bin/oslevel ] ; then IBM_REV=`/usr/bin/oslevel` else IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} fi echo ${IBM_ARCH}-ibm-aix${IBM_REV} exit ;; *:AIX:*:*) echo rs6000-ibm-aix exit ;; ibmrt:4.4BSD:*|romp-ibm:BSD:*) echo romp-ibm-bsd4.4 exit ;; ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to exit ;; # report: romp-ibm BSD 4.3 *:BOSX:*:*) echo rs6000-bull-bosx exit ;; DPX/2?00:B.O.S.:*:*) echo m68k-bull-sysv3 exit ;; 9000/[34]??:4.3bsd:1.*:*) echo m68k-hp-bsd exit ;; hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) echo m68k-hp-bsd4.4 exit ;; 9000/[34678]??:HP-UX:*:*) HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` case "${UNAME_MACHINE}" in 9000/31? ) HP_ARCH=m68000 ;; 9000/[34]?? ) HP_ARCH=m68k ;; 9000/[678][0-9][0-9]) if [ -x /usr/bin/getconf ]; then sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` case "${sc_cpu_version}" in 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1 532) # CPU_PA_RISC2_0 case "${sc_kernel_bits}" in 32) HP_ARCH="hppa2.0n" ;; 64) HP_ARCH="hppa2.0w" ;; '') HP_ARCH="hppa2.0" ;; # HP-UX 10.20 esac ;; esac fi if [ "${HP_ARCH}" = "" ]; then eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #define _HPUX_SOURCE #include #include int main () { #if defined(_SC_KERNEL_BITS) long bits = sysconf(_SC_KERNEL_BITS); #endif long cpu = sysconf (_SC_CPU_VERSION); switch (cpu) { case CPU_PA_RISC1_0: puts ("hppa1.0"); break; case CPU_PA_RISC1_1: puts ("hppa1.1"); break; case CPU_PA_RISC2_0: #if defined(_SC_KERNEL_BITS) switch (bits) { case 64: puts ("hppa2.0w"); break; case 32: puts ("hppa2.0n"); break; default: puts ("hppa2.0"); break; } break; #else /* !defined(_SC_KERNEL_BITS) */ puts ("hppa2.0"); break; #endif default: puts ("hppa1.0"); break; } exit (0); } EOF (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy` test -z "$HP_ARCH" && HP_ARCH=hppa fi ;; esac if [ ${HP_ARCH} = "hppa2.0w" ] then eval $set_cc_for_build # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler # generating 64-bit code. GNU and HP use different nomenclature: # # $ CC_FOR_BUILD=cc ./config.guess # => hppa2.0w-hp-hpux11.23 # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess # => hppa64-hp-hpux11.23 if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | grep __LP64__ >/dev/null then HP_ARCH="hppa2.0w" else HP_ARCH="hppa64" fi fi echo ${HP_ARCH}-hp-hpux${HPUX_REV} exit ;; ia64:HP-UX:*:*) HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` echo ia64-hp-hpux${HPUX_REV} exit ;; 3050*:HI-UX:*:*) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #include int main () { long cpu = sysconf (_SC_CPU_VERSION); /* The order matters, because CPU_IS_HP_MC68K erroneously returns true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct results, however. */ if (CPU_IS_PA_RISC (cpu)) { switch (cpu) { case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; default: puts ("hppa-hitachi-hiuxwe2"); break; } } else if (CPU_IS_HP_MC68K (cpu)) puts ("m68k-hitachi-hiuxwe2"); else puts ("unknown-hitachi-hiuxwe2"); exit (0); } EOF $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` && { echo "$SYSTEM_NAME"; exit; } echo unknown-hitachi-hiuxwe2 exit ;; 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) echo hppa1.1-hp-bsd exit ;; 9000/8??:4.3bsd:*:*) echo hppa1.0-hp-bsd exit ;; *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) echo hppa1.0-hp-mpeix exit ;; hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) echo hppa1.1-hp-osf exit ;; hp8??:OSF1:*:*) echo hppa1.0-hp-osf exit ;; i*86:OSF1:*:*) if [ -x /usr/sbin/sysversion ] ; then echo ${UNAME_MACHINE}-unknown-osf1mk else echo ${UNAME_MACHINE}-unknown-osf1 fi exit ;; parisc*:Lites*:*:*) echo hppa1.1-hp-lites exit ;; C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) echo c1-convex-bsd exit ;; C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) if getsysinfo -f scalar_acc then echo c32-convex-bsd else echo c2-convex-bsd fi exit ;; C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) echo c34-convex-bsd exit ;; C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) echo c38-convex-bsd exit ;; C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) echo c4-convex-bsd exit ;; CRAY*Y-MP:*:*:*) echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit ;; CRAY*[A-Z]90:*:*:*) echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ -e 's/\.[^.]*$/.X/' exit ;; CRAY*TS:*:*:*) echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit ;; CRAY*T3E:*:*:*) echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit ;; CRAY*SV1:*:*:*) echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit ;; *:UNICOS/mp:*:*) echo craynv-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' exit ;; F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" exit ;; 5000:UNIX_System_V:4.*:*) FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'` echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" exit ;; i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} exit ;; sparc*:BSD/OS:*:*) echo sparc-unknown-bsdi${UNAME_RELEASE} exit ;; *:BSD/OS:*:*) echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} exit ;; *:FreeBSD:*:*) case ${UNAME_MACHINE} in pc98) echo i386-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; amd64) echo x86_64-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; *) echo ${UNAME_MACHINE}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; esac exit ;; i*:CYGWIN*:*) echo ${UNAME_MACHINE}-pc-cygwin exit ;; i*:MINGW*:*) echo ${UNAME_MACHINE}-pc-mingw32 exit ;; i*:windows32*:*) # uname -m includes "-pc" on this system. echo ${UNAME_MACHINE}-mingw32 exit ;; i*:PW*:*) echo ${UNAME_MACHINE}-pc-pw32 exit ;; x86:Interix*:[3456]*) echo i586-pc-interix${UNAME_RELEASE} exit ;; EM64T:Interix*:[3456]*) echo x86_64-unknown-interix${UNAME_RELEASE} exit ;; [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*) echo i${UNAME_MACHINE}-pc-mks exit ;; i*:Windows_NT*:* | Pentium*:Windows_NT*:*) # How do we know it's Interix rather than the generic POSIX subsystem? # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we # UNAME_MACHINE based on the output of uname instead of i386? echo i586-pc-interix exit ;; i*:UWIN*:*) echo ${UNAME_MACHINE}-pc-uwin exit ;; amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) echo x86_64-unknown-cygwin exit ;; p*:CYGWIN*:*) echo powerpcle-unknown-cygwin exit ;; prep*:SunOS:5.*:*) echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` exit ;; *:GNU:*:*) # the GNU system echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` exit ;; *:GNU/*:*:*) # other systems with GNU libc and userland echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-gnu exit ;; i*86:Minix:*:*) echo ${UNAME_MACHINE}-pc-minix exit ;; arm*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit ;; avr32*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit ;; cris:Linux:*:*) echo cris-axis-linux-gnu exit ;; crisv32:Linux:*:*) echo crisv32-axis-linux-gnu exit ;; frv:Linux:*:*) echo frv-unknown-linux-gnu exit ;; ia64:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit ;; m32r*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit ;; m68*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit ;; mips:Linux:*:*) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #undef CPU #undef mips #undef mipsel #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) CPU=mipsel #else #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) CPU=mips #else CPU= #endif #endif EOF eval "`$CC_FOR_BUILD -E $dummy.c 2>/dev/null | sed -n ' /^CPU/{ s: ::g p }'`" test x"${CPU}" != x && { echo "${CPU}-unknown-linux-gnu"; exit; } ;; mips64:Linux:*:*) eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #undef CPU #undef mips64 #undef mips64el #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) CPU=mips64el #else #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) CPU=mips64 #else CPU= #endif #endif EOF eval "`$CC_FOR_BUILD -E $dummy.c 2>/dev/null | sed -n ' /^CPU/{ s: ::g p }'`" test x"${CPU}" != x && { echo "${CPU}-unknown-linux-gnu"; exit; } ;; or32:Linux:*:*) echo or32-unknown-linux-gnu exit ;; ppc:Linux:*:*) echo powerpc-unknown-linux-gnu exit ;; ppc64:Linux:*:*) echo powerpc64-unknown-linux-gnu exit ;; alpha:Linux:*:*) case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in EV5) UNAME_MACHINE=alphaev5 ;; EV56) UNAME_MACHINE=alphaev56 ;; PCA56) UNAME_MACHINE=alphapca56 ;; PCA57) UNAME_MACHINE=alphapca56 ;; EV6) UNAME_MACHINE=alphaev6 ;; EV67) UNAME_MACHINE=alphaev67 ;; EV68*) UNAME_MACHINE=alphaev68 ;; esac objdump --private-headers /bin/sh | grep ld.so.1 >/dev/null if test "$?" = 0 ; then LIBC="libc1" ; else LIBC="" ; fi echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC} exit ;; parisc:Linux:*:* | hppa:Linux:*:*) # Look for CPU level case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in PA7*) echo hppa1.1-unknown-linux-gnu ;; PA8*) echo hppa2.0-unknown-linux-gnu ;; *) echo hppa-unknown-linux-gnu ;; esac exit ;; parisc64:Linux:*:* | hppa64:Linux:*:*) echo hppa64-unknown-linux-gnu exit ;; s390:Linux:*:* | s390x:Linux:*:*) echo ${UNAME_MACHINE}-ibm-linux exit ;; sh64*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit ;; sh*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit ;; sparc:Linux:*:* | sparc64:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu exit ;; vax:Linux:*:*) echo ${UNAME_MACHINE}-dec-linux-gnu exit ;; x86_64:Linux:*:*) echo x86_64-unknown-linux-gnu exit ;; i*86:Linux:*:*) # The BFD linker knows what the default object file format is, so # first see if it will tell us. cd to the root directory to prevent # problems with other programs or directories called `ld' in the path. # Set LC_ALL=C to ensure ld outputs messages in English. ld_supported_targets=`cd /; LC_ALL=C ld --help 2>&1 \ | sed -ne '/supported targets:/!d s/[ ][ ]*/ /g s/.*supported targets: *// s/ .*// p'` case "$ld_supported_targets" in elf32-i386) TENTATIVE="${UNAME_MACHINE}-pc-linux-gnu" ;; a.out-i386-linux) echo "${UNAME_MACHINE}-pc-linux-gnuaout" exit ;; coff-i386) echo "${UNAME_MACHINE}-pc-linux-gnucoff" exit ;; "") # Either a pre-BFD a.out linker (linux-gnuoldld) or # one that does not give us useful --help. echo "${UNAME_MACHINE}-pc-linux-gnuoldld" exit ;; esac # Determine whether the default compiler is a.out or elf eval $set_cc_for_build sed 's/^ //' << EOF >$dummy.c #include #ifdef __ELF__ # ifdef __GLIBC__ # if __GLIBC__ >= 2 LIBC=gnu # else LIBC=gnulibc1 # endif # else LIBC=gnulibc1 # endif #else #if defined(__INTEL_COMPILER) || defined(__PGI) || defined(__SUNPRO_C) || defined(__SUNPRO_CC) LIBC=gnu #else LIBC=gnuaout #endif #endif #ifdef __dietlibc__ LIBC=dietlibc #endif EOF eval "`$CC_FOR_BUILD -E $dummy.c 2>/dev/null | sed -n ' /^LIBC/{ s: ::g p }'`" test x"${LIBC}" != x && { echo "${UNAME_MACHINE}-pc-linux-${LIBC}" exit } test x"${TENTATIVE}" != x && { echo "${TENTATIVE}"; exit; } ;; i*86:DYNIX/ptx:4*:*) # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. # earlier versions are messed up and put the nodename in both # sysname and nodename. echo i386-sequent-sysv4 exit ;; i*86:UNIX_SV:4.2MP:2.*) # Unixware is an offshoot of SVR4, but it has its own version # number series starting with 2... # I am not positive that other SVR4 systems won't match this, # I just have to hope. -- rms. # Use sysv4.2uw... so that sysv4* matches it. echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} exit ;; i*86:OS/2:*:*) # If we were able to find `uname', then EMX Unix compatibility # is probably installed. echo ${UNAME_MACHINE}-pc-os2-emx exit ;; i*86:XTS-300:*:STOP) echo ${UNAME_MACHINE}-unknown-stop exit ;; i*86:atheos:*:*) echo ${UNAME_MACHINE}-unknown-atheos exit ;; i*86:syllable:*:*) echo ${UNAME_MACHINE}-pc-syllable exit ;; i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.0*:*) echo i386-unknown-lynxos${UNAME_RELEASE} exit ;; i*86:*DOS:*:*) echo ${UNAME_MACHINE}-pc-msdosdjgpp exit ;; i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*) UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'` if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL} else echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL} fi exit ;; i*86:*:5:[678]*) # UnixWare 7.x, OpenUNIX and OpenServer 6. case `/bin/uname -X | grep "^Machine"` in *486*) UNAME_MACHINE=i486 ;; *Pentium) UNAME_MACHINE=i586 ;; *Pent*|*Celeron) UNAME_MACHINE=i686 ;; esac echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} exit ;; i*86:*:3.2:*) if test -f /usr/options/cb.name; then UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ && UNAME_MACHINE=i586 (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ && UNAME_MACHINE=i686 (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ && UNAME_MACHINE=i686 echo ${UNAME_MACHINE}-pc-sco$UNAME_REL else echo ${UNAME_MACHINE}-pc-sysv32 fi exit ;; pc:*:*:*) # Left here for compatibility: # uname -m prints for DJGPP always 'pc', but it prints nothing about # the processor, so we play safe by assuming i386. echo i386-pc-msdosdjgpp exit ;; Intel:Mach:3*:*) echo i386-pc-mach3 exit ;; paragon:*:*:*) echo i860-intel-osf1 exit ;; i860:*:4.*:*) # i860-SVR4 if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 else # Add other i860-SVR4 vendors below as they are discovered. echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 fi exit ;; mini*:CTIX:SYS*5:*) # "miniframe" echo m68010-convergent-sysv exit ;; mc68k:UNIX:SYSTEM5:3.51m) echo m68k-convergent-sysv exit ;; M680?0:D-NIX:5.3:*) echo m68k-diab-dnix exit ;; M68*:*:R3V[5678]*:*) test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;; 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) OS_REL='' test -r /etc/.relid \ && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && { echo i486-ncr-sysv4.3${OS_REL}; exit; } /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;; 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ && { echo i486-ncr-sysv4; exit; } ;; m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) echo m68k-unknown-lynxos${UNAME_RELEASE} exit ;; mc68030:UNIX_System_V:4.*:*) echo m68k-atari-sysv4 exit ;; TSUNAMI:LynxOS:2.*:*) echo sparc-unknown-lynxos${UNAME_RELEASE} exit ;; rs6000:LynxOS:2.*:*) echo rs6000-unknown-lynxos${UNAME_RELEASE} exit ;; PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.0*:*) echo powerpc-unknown-lynxos${UNAME_RELEASE} exit ;; SM[BE]S:UNIX_SV:*:*) echo mips-dde-sysv${UNAME_RELEASE} exit ;; RM*:ReliantUNIX-*:*:*) echo mips-sni-sysv4 exit ;; RM*:SINIX-*:*:*) echo mips-sni-sysv4 exit ;; *:SINIX-*:*:*) if uname -p 2>/dev/null >/dev/null ; then UNAME_MACHINE=`(uname -p) 2>/dev/null` echo ${UNAME_MACHINE}-sni-sysv4 else echo ns32k-sni-sysv fi exit ;; PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort # says echo i586-unisys-sysv4 exit ;; *:UNIX_System_V:4*:FTX*) # From Gerald Hewes . # How about differentiating between stratus architectures? -djm echo hppa1.1-stratus-sysv4 exit ;; *:*:*:FTX*) # From seanf@swdc.stratus.com. echo i860-stratus-sysv4 exit ;; i*86:VOS:*:*) # From Paul.Green@stratus.com. echo ${UNAME_MACHINE}-stratus-vos exit ;; *:VOS:*:*) # From Paul.Green@stratus.com. echo hppa1.1-stratus-vos exit ;; mc68*:A/UX:*:*) echo m68k-apple-aux${UNAME_RELEASE} exit ;; news*:NEWS-OS:6*:*) echo mips-sony-newsos6 exit ;; R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) if [ -d /usr/nec ]; then echo mips-nec-sysv${UNAME_RELEASE} else echo mips-unknown-sysv${UNAME_RELEASE} fi exit ;; BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. echo powerpc-be-beos exit ;; BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. echo powerpc-apple-beos exit ;; BePC:BeOS:*:*) # BeOS running on Intel PC compatible. echo i586-pc-beos exit ;; SX-4:SUPER-UX:*:*) echo sx4-nec-superux${UNAME_RELEASE} exit ;; SX-5:SUPER-UX:*:*) echo sx5-nec-superux${UNAME_RELEASE} exit ;; SX-6:SUPER-UX:*:*) echo sx6-nec-superux${UNAME_RELEASE} exit ;; Power*:Rhapsody:*:*) echo powerpc-apple-rhapsody${UNAME_RELEASE} exit ;; *:Rhapsody:*:*) echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} exit ;; *:Darwin:*:*) UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown case $UNAME_PROCESSOR in unknown) UNAME_PROCESSOR=powerpc ;; esac echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE} exit ;; *:procnto*:*:* | *:QNX:[0123456789]*:*) UNAME_PROCESSOR=`uname -p` if test "$UNAME_PROCESSOR" = "x86"; then UNAME_PROCESSOR=i386 UNAME_MACHINE=pc fi echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE} exit ;; *:QNX:*:4*) echo i386-pc-qnx exit ;; NSE-?:NONSTOP_KERNEL:*:*) echo nse-tandem-nsk${UNAME_RELEASE} exit ;; NSR-?:NONSTOP_KERNEL:*:*) echo nsr-tandem-nsk${UNAME_RELEASE} exit ;; *:NonStop-UX:*:*) echo mips-compaq-nonstopux exit ;; BS2000:POSIX*:*:*) echo bs2000-siemens-sysv exit ;; DS/*:UNIX_System_V:*:*) echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE} exit ;; *:Plan9:*:*) # "uname -m" is not consistent, so use $cputype instead. 386 # is converted to i386 for consistency with other x86 # operating systems. if test "$cputype" = "386"; then UNAME_MACHINE=i386 else UNAME_MACHINE="$cputype" fi echo ${UNAME_MACHINE}-unknown-plan9 exit ;; *:TOPS-10:*:*) echo pdp10-unknown-tops10 exit ;; *:TENEX:*:*) echo pdp10-unknown-tenex exit ;; KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) echo pdp10-dec-tops20 exit ;; XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) echo pdp10-xkl-tops20 exit ;; *:TOPS-20:*:*) echo pdp10-unknown-tops20 exit ;; *:ITS:*:*) echo pdp10-unknown-its exit ;; SEI:*:*:SEIUX) echo mips-sei-seiux${UNAME_RELEASE} exit ;; *:DragonFly:*:*) echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` exit ;; *:*VMS:*:*) UNAME_MACHINE=`(uname -p) 2>/dev/null` case "${UNAME_MACHINE}" in A*) echo alpha-dec-vms ; exit ;; I*) echo ia64-dec-vms ; exit ;; V*) echo vax-dec-vms ; exit ;; esac ;; *:XENIX:*:SysV) echo i386-pc-xenix exit ;; i*86:skyos:*:*) echo ${UNAME_MACHINE}-pc-skyos`echo ${UNAME_RELEASE}` | sed -e 's/ .*$//' exit ;; i*86:rdos:*:*) echo ${UNAME_MACHINE}-pc-rdos exit ;; esac #echo '(No uname command or uname output not recognized.)' 1>&2 #echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2 eval $set_cc_for_build cat >$dummy.c < # include #endif main () { #if defined (sony) #if defined (MIPSEB) /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, I don't know.... */ printf ("mips-sony-bsd\n"); exit (0); #else #include printf ("m68k-sony-newsos%s\n", #ifdef NEWSOS4 "4" #else "" #endif ); exit (0); #endif #endif #if defined (__arm) && defined (__acorn) && defined (__unix) printf ("arm-acorn-riscix\n"); exit (0); #endif #if defined (hp300) && !defined (hpux) printf ("m68k-hp-bsd\n"); exit (0); #endif #if defined (NeXT) #if !defined (__ARCHITECTURE__) #define __ARCHITECTURE__ "m68k" #endif int version; version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; if (version < 4) printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); else printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); exit (0); #endif #if defined (MULTIMAX) || defined (n16) #if defined (UMAXV) printf ("ns32k-encore-sysv\n"); exit (0); #else #if defined (CMU) printf ("ns32k-encore-mach\n"); exit (0); #else printf ("ns32k-encore-bsd\n"); exit (0); #endif #endif #endif #if defined (__386BSD__) printf ("i386-pc-bsd\n"); exit (0); #endif #if defined (sequent) #if defined (i386) printf ("i386-sequent-dynix\n"); exit (0); #endif #if defined (ns32000) printf ("ns32k-sequent-dynix\n"); exit (0); #endif #endif #if defined (_SEQUENT_) struct utsname un; uname(&un); if (strncmp(un.version, "V2", 2) == 0) { printf ("i386-sequent-ptx2\n"); exit (0); } if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ printf ("i386-sequent-ptx1\n"); exit (0); } printf ("i386-sequent-ptx\n"); exit (0); #endif #if defined (vax) # if !defined (ultrix) # include # if defined (BSD) # if BSD == 43 printf ("vax-dec-bsd4.3\n"); exit (0); # else # if BSD == 199006 printf ("vax-dec-bsd4.3reno\n"); exit (0); # else printf ("vax-dec-bsd\n"); exit (0); # endif # endif # else printf ("vax-dec-bsd\n"); exit (0); # endif # else printf ("vax-dec-ultrix\n"); exit (0); # endif #endif #if defined (alliant) && defined (i860) printf ("i860-alliant-bsd\n"); exit (0); #endif exit (1); } EOF $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null && SYSTEM_NAME=`$dummy` && { echo "$SYSTEM_NAME"; exit; } # Apollos put the system type in the environment. test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit; } # Convex versions that predate uname can use getsysinfo(1) if [ -x /usr/convex/getsysinfo ] then case `getsysinfo -f cpu_type` in c1*) echo c1-convex-bsd exit ;; c2*) if getsysinfo -f scalar_acc then echo c32-convex-bsd else echo c2-convex-bsd fi exit ;; c34*) echo c34-convex-bsd exit ;; c38*) echo c38-convex-bsd exit ;; c4*) echo c4-convex-bsd exit ;; esac fi cat >&2 < in order to provide the needed information to handle your system. config.guess timestamp = $timestamp uname -m = `(uname -m) 2>/dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` /bin/uname -X = `(/bin/uname -X) 2>/dev/null` hostinfo = `(hostinfo) 2>/dev/null` /bin/universe = `(/bin/universe) 2>/dev/null` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` /bin/arch = `(/bin/arch) 2>/dev/null` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` UNAME_MACHINE = ${UNAME_MACHINE} UNAME_RELEASE = ${UNAME_RELEASE} UNAME_SYSTEM = ${UNAME_SYSTEM} UNAME_VERSION = ${UNAME_VERSION} EOF exit 1 # Local variables: # eval: (add-hook 'write-file-hooks 'time-stamp) # time-stamp-start: "timestamp='" # time-stamp-format: "%:y-%02m-%02d" # time-stamp-end: "'" # End: nzbget-12.0+dfsg/config.h.in000066400000000000000000000073071226450633000156770ustar00rootroot00000000000000/* config.h.in. Generated from configure.ac by autoheader. */ /* Define to 1 to include debug-code */ #undef DEBUG /* Define to 1 to not use curses */ #undef DISABLE_CURSES /* Define to 1 to disable gzip-support */ #undef DISABLE_GZIP /* Define to 1 to disable smart par-verification and restoration */ #undef DISABLE_PARCHECK /* Define to 1 to not use TLS/SSL */ #undef DISABLE_TLS /* Define to the name of macro which returns the name of function being compiled */ #undef FUNCTION_MACRO_NAME /* Define to 1 to create stacktrace on segmentation faults */ #undef HAVE_BACKTRACE /* Define to 1 if ctime_r takes 2 arguments */ #undef HAVE_CTIME_R_2 /* Define to 1 if ctime_r takes 3 arguments */ #undef HAVE_CTIME_R_3 /* Define to 1 if you have the header file. */ #undef HAVE_CURSES_H /* Define to 1 if getaddrinfo is supported */ #undef HAVE_GETADDRINFO /* Define to 1 if gethostbyname_r is supported */ #undef HAVE_GETHOSTBYNAME_R /* Define to 1 if gethostbyname_r takes 3 arguments */ #undef HAVE_GETHOSTBYNAME_R_3 /* Define to 1 if gethostbyname_r takes 5 arguments */ #undef HAVE_GETHOSTBYNAME_R_5 /* Define to 1 if gethostbyname_r takes 6 arguments */ #undef HAVE_GETHOSTBYNAME_R_6 /* Define to 1 if getopt_long is supported */ #undef HAVE_GETOPT_LONG /* Define to 1 if you have the header file. */ #undef HAVE_INTTYPES_H /* Define to 1 to use GnuTLS library for TLS/SSL-support. */ #undef HAVE_LIBGNUTLS /* Define to 1 if you have the header file. */ #undef HAVE_MEMORY_H /* Define to 1 if you have the header file. */ #undef HAVE_NCURSES_H /* Define to 1 if you have the header file. */ #undef HAVE_NCURSES_NCURSES_H /* Define to 1 to use OpenSSL library for TLS/SSL-support. */ #undef HAVE_OPENSSL /* Define to 1 if libpar2 has recent bugfixes-patch (version 2) */ #undef HAVE_PAR2_BUGFIXES_V2 /* Define to 1 if libpar2 supports cancelling (needs a special patch) */ #undef HAVE_PAR2_CANCEL /* Define to 1 if you have the header file. */ #undef HAVE_REGEX_H /* Define to 1 if spinlocks are supported */ #undef HAVE_SPINLOCK /* Define to 1 if you have the header file. */ #undef HAVE_STDINT_H /* Define to 1 if you have the header file. */ #undef HAVE_STDLIB_H /* Define to 1 if you have the header file. */ #undef HAVE_STRINGS_H /* Define to 1 if you have the header file. */ #undef HAVE_STRING_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_PRCTL_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_STAT_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_TYPES_H /* Define to 1 if you have the header file. */ #undef HAVE_UNISTD_H /* Define to 1 if variadic macros are supported */ #undef HAVE_VARIADIC_MACROS /* Name of package */ #undef PACKAGE /* Define to the address where bug reports for this package should be sent. */ #undef PACKAGE_BUGREPORT /* Define to the full name of this package. */ #undef PACKAGE_NAME /* Define to the full name and version of this package. */ #undef PACKAGE_STRING /* Define to the one symbol short name of this package. */ #undef PACKAGE_TARNAME /* Define to the version of this package. */ #undef PACKAGE_VERSION /* Define to 1 to install an empty signal handler for SIGCHLD */ #undef SIGCHLD_HANDLER /* Determine what socket length (socklen_t) data type is */ #undef SOCKLEN_T /* Define to 1 if you have the ANSI C header files. */ #undef STDC_HEADERS /* Version number of package */ #undef VERSION /* Number of bits in a file offset, on hosts where this is settable. */ #undef _FILE_OFFSET_BITS /* Define for large files, on AIX-style hosts. */ #undef _LARGE_FILES nzbget-12.0+dfsg/config.sub000077500000000000000000000774601226450633000156460ustar00rootroot00000000000000#! /bin/sh # Configuration validation subroutine script. # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, # 2000, 2001, 2002, 2003, 2004, 2005, 2006 Free Software Foundation, # Inc. timestamp='2006-09-20' # This file is (in principle) common to ALL GNU software. # The presence of a machine in this file suggests that SOME GNU software # can handle that machine. It does not imply ALL GNU software can. # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA # 02110-1301, USA. # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # Please send patches to . Submit a context # diff and a properly formatted ChangeLog entry. # # Configuration subroutine to validate and canonicalize a configuration type. # Supply the specified configuration type as an argument. # If it is invalid, we print an error message on stderr and exit with code 1. # Otherwise, we print the canonical config type on stdout and succeed. # This file is supposed to be the same for all GNU packages # and recognize all the CPU types, system types and aliases # that are meaningful with *any* GNU software. # Each package is responsible for reporting which valid configurations # it does not support. The user should be able to distinguish # a failure to support a valid configuration from a meaningless # configuration. # The goal of this file is to map all the various variations of a given # machine specification into a single specification in the form: # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM # or in some cases, the newer four-part form: # CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM # It is wrong to echo any other type of specification. me=`echo "$0" | sed -e 's,.*/,,'` usage="\ Usage: $0 [OPTION] CPU-MFR-OPSYS $0 [OPTION] ALIAS Canonicalize a configuration name. Operation modes: -h, --help print this help, then exit -t, --time-stamp print date of last modification, then exit -v, --version print version number, then exit Report bugs and patches to ." version="\ GNU config.sub ($timestamp) Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." help=" Try \`$me --help' for more information." # Parse command line while test $# -gt 0 ; do case $1 in --time-stamp | --time* | -t ) echo "$timestamp" ; exit ;; --version | -v ) echo "$version" ; exit ;; --help | --h* | -h ) echo "$usage"; exit ;; -- ) # Stop option processing shift; break ;; - ) # Use stdin as input. break ;; -* ) echo "$me: invalid option $1$help" exit 1 ;; *local*) # First pass through any local machine types. echo $1 exit ;; * ) break ;; esac done case $# in 0) echo "$me: missing argument$help" >&2 exit 1;; 1) ;; *) echo "$me: too many arguments$help" >&2 exit 1;; esac # Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). # Here we must recognize all the valid KERNEL-OS combinations. maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` case $maybe_os in nto-qnx* | linux-gnu* | linux-dietlibc | linux-newlib* | linux-uclibc* | \ uclinux-uclibc* | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* | \ storm-chaos* | os2-emx* | rtmk-nova*) os=-$maybe_os basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` ;; *) basic_machine=`echo $1 | sed 's/-[^-]*$//'` if [ $basic_machine != $1 ] then os=`echo $1 | sed 's/.*-/-/'` else os=; fi ;; esac ### Let's recognize common machines as not being operating systems so ### that things like config.sub decstation-3100 work. We also ### recognize some manufacturers as not being operating systems, so we ### can provide default operating systems below. case $os in -sun*os*) # Prevent following clause from handling this invalid input. ;; -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \ -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ -apple | -axis | -knuth | -cray) os= basic_machine=$1 ;; -sim | -cisco | -oki | -wec | -winbond) os= basic_machine=$1 ;; -scout) ;; -wrs) os=-vxworks basic_machine=$1 ;; -chorusos*) os=-chorusos basic_machine=$1 ;; -chorusrdb) os=-chorusrdb basic_machine=$1 ;; -hiux*) os=-hiuxwe2 ;; -sco6) os=-sco5v6 basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -sco5) os=-sco3.2v5 basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -sco4) os=-sco3.2v4 basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -sco3.2.[4-9]*) os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -sco3.2v[4-9]*) # Don't forget version if it is 3.2v4 or newer. basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -sco5v6*) # Don't forget version if it is 3.2v4 or newer. basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -sco*) os=-sco3.2v2 basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -udk*) basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -isc) os=-isc2.2 basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -clix*) basic_machine=clipper-intergraph ;; -isc*) basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; -lynx*) os=-lynxos ;; -ptx*) basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'` ;; -windowsnt*) os=`echo $os | sed -e 's/windowsnt/winnt/'` ;; -psos*) os=-psos ;; -mint | -mint[0-9]*) basic_machine=m68k-atari os=-mint ;; esac # Decode aliases for certain CPU-COMPANY combinations. case $basic_machine in # Recognize the basic CPU types without company name. # Some are omitted here because they have special meanings below. 1750a | 580 \ | a29k \ | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \ | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \ | am33_2.0 \ | arc | arm | arm[bl]e | arme[lb] | armv[2345] | armv[345][lb] | avr | avr32 \ | bfin \ | c4x | clipper \ | d10v | d30v | dlx | dsp16xx \ | fr30 | frv \ | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ | i370 | i860 | i960 | ia64 \ | ip2k | iq2000 \ | m32c | m32r | m32rle | m68000 | m68k | m88k \ | maxq | mb | microblaze | mcore \ | mips | mipsbe | mipseb | mipsel | mipsle \ | mips16 \ | mips64 | mips64el \ | mips64vr | mips64vrel \ | mips64orion | mips64orionel \ | mips64vr4100 | mips64vr4100el \ | mips64vr4300 | mips64vr4300el \ | mips64vr5000 | mips64vr5000el \ | mips64vr5900 | mips64vr5900el \ | mipsisa32 | mipsisa32el \ | mipsisa32r2 | mipsisa32r2el \ | mipsisa64 | mipsisa64el \ | mipsisa64r2 | mipsisa64r2el \ | mipsisa64sb1 | mipsisa64sb1el \ | mipsisa64sr71k | mipsisa64sr71kel \ | mipstx39 | mipstx39el \ | mn10200 | mn10300 \ | mt \ | msp430 \ | nios | nios2 \ | ns16k | ns32k \ | or32 \ | pdp10 | pdp11 | pj | pjl \ | powerpc | powerpc64 | powerpc64le | powerpcle | ppcbe \ | pyramid \ | score \ | sh | sh[1234] | sh[24]a | sh[23]e | sh[34]eb | sheb | shbe | shle | sh[1234]le | sh3ele \ | sh64 | sh64le \ | sparc | sparc64 | sparc64b | sparc64v | sparc86x | sparclet | sparclite \ | sparcv8 | sparcv9 | sparcv9b | sparcv9v \ | spu | strongarm \ | tahoe | thumb | tic4x | tic80 | tron \ | v850 | v850e \ | we32k \ | x86 | xc16x | xscale | xscalee[bl] | xstormy16 | xtensa \ | z8k) basic_machine=$basic_machine-unknown ;; m6811 | m68hc11 | m6812 | m68hc12) # Motorola 68HC11/12. basic_machine=$basic_machine-unknown os=-none ;; m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k) ;; ms1) basic_machine=mt-unknown ;; # We use `pc' rather than `unknown' # because (1) that's what they normally are, and # (2) the word "unknown" tends to confuse beginning users. i*86 | x86_64) basic_machine=$basic_machine-pc ;; # Object if more than one company name word. *-*-*) echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 exit 1 ;; # Recognize the basic CPU types with company name. 580-* \ | a29k-* \ | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \ | alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \ | alphapca5[67]-* | alpha64pca5[67]-* | arc-* \ | arm-* | armbe-* | armle-* | armeb-* | armv*-* \ | avr-* | avr32-* \ | bfin-* | bs2000-* \ | c[123]* | c30-* | [cjt]90-* | c4x-* | c54x-* | c55x-* | c6x-* \ | clipper-* | craynv-* | cydra-* \ | d10v-* | d30v-* | dlx-* \ | elxsi-* \ | f30[01]-* | f700-* | fr30-* | frv-* | fx80-* \ | h8300-* | h8500-* \ | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \ | i*86-* | i860-* | i960-* | ia64-* \ | ip2k-* | iq2000-* \ | m32c-* | m32r-* | m32rle-* \ | m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \ | m88110-* | m88k-* | maxq-* | mcore-* \ | mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \ | mips16-* \ | mips64-* | mips64el-* \ | mips64vr-* | mips64vrel-* \ | mips64orion-* | mips64orionel-* \ | mips64vr4100-* | mips64vr4100el-* \ | mips64vr4300-* | mips64vr4300el-* \ | mips64vr5000-* | mips64vr5000el-* \ | mips64vr5900-* | mips64vr5900el-* \ | mipsisa32-* | mipsisa32el-* \ | mipsisa32r2-* | mipsisa32r2el-* \ | mipsisa64-* | mipsisa64el-* \ | mipsisa64r2-* | mipsisa64r2el-* \ | mipsisa64sb1-* | mipsisa64sb1el-* \ | mipsisa64sr71k-* | mipsisa64sr71kel-* \ | mipstx39-* | mipstx39el-* \ | mmix-* \ | mt-* \ | msp430-* \ | nios-* | nios2-* \ | none-* | np1-* | ns16k-* | ns32k-* \ | orion-* \ | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \ | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* | ppcbe-* \ | pyramid-* \ | romp-* | rs6000-* \ | sh-* | sh[1234]-* | sh[24]a-* | sh[23]e-* | sh[34]eb-* | sheb-* | shbe-* \ | shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \ | sparc-* | sparc64-* | sparc64b-* | sparc64v-* | sparc86x-* | sparclet-* \ | sparclite-* \ | sparcv8-* | sparcv9-* | sparcv9b-* | sparcv9v-* | strongarm-* | sv1-* | sx?-* \ | tahoe-* | thumb-* \ | tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \ | tron-* \ | v850-* | v850e-* | vax-* \ | we32k-* \ | x86-* | x86_64-* | xc16x-* | xps100-* | xscale-* | xscalee[bl]-* \ | xstormy16-* | xtensa-* \ | ymp-* \ | z8k-*) ;; # Recognize the various machine names and aliases which stand # for a CPU type and a company and sometimes even an OS. 386bsd) basic_machine=i386-unknown os=-bsd ;; 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) basic_machine=m68000-att ;; 3b*) basic_machine=we32k-att ;; a29khif) basic_machine=a29k-amd os=-udi ;; abacus) basic_machine=abacus-unknown ;; adobe68k) basic_machine=m68010-adobe os=-scout ;; alliant | fx80) basic_machine=fx80-alliant ;; altos | altos3068) basic_machine=m68k-altos ;; am29k) basic_machine=a29k-none os=-bsd ;; amd64) basic_machine=x86_64-pc ;; amd64-*) basic_machine=x86_64-`echo $basic_machine | sed 's/^[^-]*-//'` ;; amdahl) basic_machine=580-amdahl os=-sysv ;; amiga | amiga-*) basic_machine=m68k-unknown ;; amigaos | amigados) basic_machine=m68k-unknown os=-amigaos ;; amigaunix | amix) basic_machine=m68k-unknown os=-sysv4 ;; apollo68) basic_machine=m68k-apollo os=-sysv ;; apollo68bsd) basic_machine=m68k-apollo os=-bsd ;; aux) basic_machine=m68k-apple os=-aux ;; balance) basic_machine=ns32k-sequent os=-dynix ;; c90) basic_machine=c90-cray os=-unicos ;; convex-c1) basic_machine=c1-convex os=-bsd ;; convex-c2) basic_machine=c2-convex os=-bsd ;; convex-c32) basic_machine=c32-convex os=-bsd ;; convex-c34) basic_machine=c34-convex os=-bsd ;; convex-c38) basic_machine=c38-convex os=-bsd ;; cray | j90) basic_machine=j90-cray os=-unicos ;; craynv) basic_machine=craynv-cray os=-unicosmp ;; cr16c) basic_machine=cr16c-unknown os=-elf ;; crds | unos) basic_machine=m68k-crds ;; crisv32 | crisv32-* | etraxfs*) basic_machine=crisv32-axis ;; cris | cris-* | etrax*) basic_machine=cris-axis ;; crx) basic_machine=crx-unknown os=-elf ;; da30 | da30-*) basic_machine=m68k-da30 ;; decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn) basic_machine=mips-dec ;; decsystem10* | dec10*) basic_machine=pdp10-dec os=-tops10 ;; decsystem20* | dec20*) basic_machine=pdp10-dec os=-tops20 ;; delta | 3300 | motorola-3300 | motorola-delta \ | 3300-motorola | delta-motorola) basic_machine=m68k-motorola ;; delta88) basic_machine=m88k-motorola os=-sysv3 ;; djgpp) basic_machine=i586-pc os=-msdosdjgpp ;; dpx20 | dpx20-*) basic_machine=rs6000-bull os=-bosx ;; dpx2* | dpx2*-bull) basic_machine=m68k-bull os=-sysv3 ;; ebmon29k) basic_machine=a29k-amd os=-ebmon ;; elxsi) basic_machine=elxsi-elxsi os=-bsd ;; encore | umax | mmax) basic_machine=ns32k-encore ;; es1800 | OSE68k | ose68k | ose | OSE) basic_machine=m68k-ericsson os=-ose ;; fx2800) basic_machine=i860-alliant ;; genix) basic_machine=ns32k-ns ;; gmicro) basic_machine=tron-gmicro os=-sysv ;; go32) basic_machine=i386-pc os=-go32 ;; h3050r* | hiux*) basic_machine=hppa1.1-hitachi os=-hiuxwe2 ;; h8300hms) basic_machine=h8300-hitachi os=-hms ;; h8300xray) basic_machine=h8300-hitachi os=-xray ;; h8500hms) basic_machine=h8500-hitachi os=-hms ;; harris) basic_machine=m88k-harris os=-sysv3 ;; hp300-*) basic_machine=m68k-hp ;; hp300bsd) basic_machine=m68k-hp os=-bsd ;; hp300hpux) basic_machine=m68k-hp os=-hpux ;; hp3k9[0-9][0-9] | hp9[0-9][0-9]) basic_machine=hppa1.0-hp ;; hp9k2[0-9][0-9] | hp9k31[0-9]) basic_machine=m68000-hp ;; hp9k3[2-9][0-9]) basic_machine=m68k-hp ;; hp9k6[0-9][0-9] | hp6[0-9][0-9]) basic_machine=hppa1.0-hp ;; hp9k7[0-79][0-9] | hp7[0-79][0-9]) basic_machine=hppa1.1-hp ;; hp9k78[0-9] | hp78[0-9]) # FIXME: really hppa2.0-hp basic_machine=hppa1.1-hp ;; hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) # FIXME: really hppa2.0-hp basic_machine=hppa1.1-hp ;; hp9k8[0-9][13679] | hp8[0-9][13679]) basic_machine=hppa1.1-hp ;; hp9k8[0-9][0-9] | hp8[0-9][0-9]) basic_machine=hppa1.0-hp ;; hppa-next) os=-nextstep3 ;; hppaosf) basic_machine=hppa1.1-hp os=-osf ;; hppro) basic_machine=hppa1.1-hp os=-proelf ;; i370-ibm* | ibm*) basic_machine=i370-ibm ;; # I'm not sure what "Sysv32" means. Should this be sysv3.2? i*86v32) basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` os=-sysv32 ;; i*86v4*) basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` os=-sysv4 ;; i*86v) basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` os=-sysv ;; i*86sol2) basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` os=-solaris2 ;; i386mach) basic_machine=i386-mach os=-mach ;; i386-vsta | vsta) basic_machine=i386-unknown os=-vsta ;; iris | iris4d) basic_machine=mips-sgi case $os in -irix*) ;; *) os=-irix4 ;; esac ;; isi68 | isi) basic_machine=m68k-isi os=-sysv ;; m88k-omron*) basic_machine=m88k-omron ;; magnum | m3230) basic_machine=mips-mips os=-sysv ;; merlin) basic_machine=ns32k-utek os=-sysv ;; mingw32) basic_machine=i386-pc os=-mingw32 ;; miniframe) basic_machine=m68000-convergent ;; *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*) basic_machine=m68k-atari os=-mint ;; mips3*-*) basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'` ;; mips3*) basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown ;; monitor) basic_machine=m68k-rom68k os=-coff ;; morphos) basic_machine=powerpc-unknown os=-morphos ;; msdos) basic_machine=i386-pc os=-msdos ;; ms1-*) basic_machine=`echo $basic_machine | sed -e 's/ms1-/mt-/'` ;; mvs) basic_machine=i370-ibm os=-mvs ;; ncr3000) basic_machine=i486-ncr os=-sysv4 ;; netbsd386) basic_machine=i386-unknown os=-netbsd ;; netwinder) basic_machine=armv4l-rebel os=-linux ;; news | news700 | news800 | news900) basic_machine=m68k-sony os=-newsos ;; news1000) basic_machine=m68030-sony os=-newsos ;; news-3600 | risc-news) basic_machine=mips-sony os=-newsos ;; necv70) basic_machine=v70-nec os=-sysv ;; next | m*-next ) basic_machine=m68k-next case $os in -nextstep* ) ;; -ns2*) os=-nextstep2 ;; *) os=-nextstep3 ;; esac ;; nh3000) basic_machine=m68k-harris os=-cxux ;; nh[45]000) basic_machine=m88k-harris os=-cxux ;; nindy960) basic_machine=i960-intel os=-nindy ;; mon960) basic_machine=i960-intel os=-mon960 ;; nonstopux) basic_machine=mips-compaq os=-nonstopux ;; np1) basic_machine=np1-gould ;; nsr-tandem) basic_machine=nsr-tandem ;; op50n-* | op60c-*) basic_machine=hppa1.1-oki os=-proelf ;; openrisc | openrisc-*) basic_machine=or32-unknown ;; os400) basic_machine=powerpc-ibm os=-os400 ;; OSE68000 | ose68000) basic_machine=m68000-ericsson os=-ose ;; os68k) basic_machine=m68k-none os=-os68k ;; pa-hitachi) basic_machine=hppa1.1-hitachi os=-hiuxwe2 ;; paragon) basic_machine=i860-intel os=-osf ;; pbd) basic_machine=sparc-tti ;; pbb) basic_machine=m68k-tti ;; pc532 | pc532-*) basic_machine=ns32k-pc532 ;; pc98) basic_machine=i386-pc ;; pc98-*) basic_machine=i386-`echo $basic_machine | sed 's/^[^-]*-//'` ;; pentium | p5 | k5 | k6 | nexgen | viac3) basic_machine=i586-pc ;; pentiumpro | p6 | 6x86 | athlon | athlon_*) basic_machine=i686-pc ;; pentiumii | pentium2 | pentiumiii | pentium3) basic_machine=i686-pc ;; pentium4) basic_machine=i786-pc ;; pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*) basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'` ;; pentiumpro-* | p6-* | 6x86-* | athlon-*) basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` ;; pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*) basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` ;; pentium4-*) basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'` ;; pn) basic_machine=pn-gould ;; power) basic_machine=power-ibm ;; ppc) basic_machine=powerpc-unknown ;; ppc-*) basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'` ;; ppcle | powerpclittle | ppc-le | powerpc-little) basic_machine=powerpcle-unknown ;; ppcle-* | powerpclittle-*) basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'` ;; ppc64) basic_machine=powerpc64-unknown ;; ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'` ;; ppc64le | powerpc64little | ppc64-le | powerpc64-little) basic_machine=powerpc64le-unknown ;; ppc64le-* | powerpc64little-*) basic_machine=powerpc64le-`echo $basic_machine | sed 's/^[^-]*-//'` ;; ps2) basic_machine=i386-ibm ;; pw32) basic_machine=i586-unknown os=-pw32 ;; rdos) basic_machine=i386-pc os=-rdos ;; rom68k) basic_machine=m68k-rom68k os=-coff ;; rm[46]00) basic_machine=mips-siemens ;; rtpc | rtpc-*) basic_machine=romp-ibm ;; s390 | s390-*) basic_machine=s390-ibm ;; s390x | s390x-*) basic_machine=s390x-ibm ;; sa29200) basic_machine=a29k-amd os=-udi ;; sb1) basic_machine=mipsisa64sb1-unknown ;; sb1el) basic_machine=mipsisa64sb1el-unknown ;; sde) basic_machine=mipsisa32-sde os=-elf ;; sei) basic_machine=mips-sei os=-seiux ;; sequent) basic_machine=i386-sequent ;; sh) basic_machine=sh-hitachi os=-hms ;; sh64) basic_machine=sh64-unknown ;; sparclite-wrs | simso-wrs) basic_machine=sparclite-wrs os=-vxworks ;; sps7) basic_machine=m68k-bull os=-sysv2 ;; spur) basic_machine=spur-unknown ;; st2000) basic_machine=m68k-tandem ;; stratus) basic_machine=i860-stratus os=-sysv4 ;; sun2) basic_machine=m68000-sun ;; sun2os3) basic_machine=m68000-sun os=-sunos3 ;; sun2os4) basic_machine=m68000-sun os=-sunos4 ;; sun3os3) basic_machine=m68k-sun os=-sunos3 ;; sun3os4) basic_machine=m68k-sun os=-sunos4 ;; sun4os3) basic_machine=sparc-sun os=-sunos3 ;; sun4os4) basic_machine=sparc-sun os=-sunos4 ;; sun4sol2) basic_machine=sparc-sun os=-solaris2 ;; sun3 | sun3-*) basic_machine=m68k-sun ;; sun4) basic_machine=sparc-sun ;; sun386 | sun386i | roadrunner) basic_machine=i386-sun ;; sv1) basic_machine=sv1-cray os=-unicos ;; symmetry) basic_machine=i386-sequent os=-dynix ;; t3e) basic_machine=alphaev5-cray os=-unicos ;; t90) basic_machine=t90-cray os=-unicos ;; tic54x | c54x*) basic_machine=tic54x-unknown os=-coff ;; tic55x | c55x*) basic_machine=tic55x-unknown os=-coff ;; tic6x | c6x*) basic_machine=tic6x-unknown os=-coff ;; tx39) basic_machine=mipstx39-unknown ;; tx39el) basic_machine=mipstx39el-unknown ;; toad1) basic_machine=pdp10-xkl os=-tops20 ;; tower | tower-32) basic_machine=m68k-ncr ;; tpf) basic_machine=s390x-ibm os=-tpf ;; udi29k) basic_machine=a29k-amd os=-udi ;; ultra3) basic_machine=a29k-nyu os=-sym1 ;; v810 | necv810) basic_machine=v810-nec os=-none ;; vaxv) basic_machine=vax-dec os=-sysv ;; vms) basic_machine=vax-dec os=-vms ;; vpp*|vx|vx-*) basic_machine=f301-fujitsu ;; vxworks960) basic_machine=i960-wrs os=-vxworks ;; vxworks68) basic_machine=m68k-wrs os=-vxworks ;; vxworks29k) basic_machine=a29k-wrs os=-vxworks ;; w65*) basic_machine=w65-wdc os=-none ;; w89k-*) basic_machine=hppa1.1-winbond os=-proelf ;; xbox) basic_machine=i686-pc os=-mingw32 ;; xps | xps100) basic_machine=xps100-honeywell ;; ymp) basic_machine=ymp-cray os=-unicos ;; z8k-*-coff) basic_machine=z8k-unknown os=-sim ;; none) basic_machine=none-none os=-none ;; # Here we handle the default manufacturer of certain CPU types. It is in # some cases the only manufacturer, in others, it is the most popular. w89k) basic_machine=hppa1.1-winbond ;; op50n) basic_machine=hppa1.1-oki ;; op60c) basic_machine=hppa1.1-oki ;; romp) basic_machine=romp-ibm ;; mmix) basic_machine=mmix-knuth ;; rs6000) basic_machine=rs6000-ibm ;; vax) basic_machine=vax-dec ;; pdp10) # there are many clones, so DEC is not a safe bet basic_machine=pdp10-unknown ;; pdp11) basic_machine=pdp11-dec ;; we32k) basic_machine=we32k-att ;; sh[1234] | sh[24]a | sh[34]eb | sh[1234]le | sh[23]ele) basic_machine=sh-unknown ;; sparc | sparcv8 | sparcv9 | sparcv9b | sparcv9v) basic_machine=sparc-sun ;; cydra) basic_machine=cydra-cydrome ;; orion) basic_machine=orion-highlevel ;; orion105) basic_machine=clipper-highlevel ;; mac | mpw | mac-mpw) basic_machine=m68k-apple ;; pmac | pmac-mpw) basic_machine=powerpc-apple ;; *-unknown) # Make sure to match an already-canonicalized machine name. ;; *) echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 exit 1 ;; esac # Here we canonicalize certain aliases for manufacturers. case $basic_machine in *-digital*) basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'` ;; *-commodore*) basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'` ;; *) ;; esac # Decode manufacturer-specific aliases for certain operating systems. if [ x"$os" != x"" ] then case $os in # First match some system type aliases # that might get confused with valid system types. # -solaris* is a basic system type, with this one exception. -solaris1 | -solaris1.*) os=`echo $os | sed -e 's|solaris1|sunos4|'` ;; -solaris) os=-solaris2 ;; -svr4*) os=-sysv4 ;; -unixware*) os=-sysv4.2uw ;; -gnu/linux*) os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` ;; # First accept the basic system types. # The portable systems comes first. # Each alternative MUST END IN A *, to match a version number. # -sysv* is not here because it comes later, after sysvr4. -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ | -*vms* | -sco* | -esix* | -isc* | -aix* | -sunos | -sunos[34]*\ | -hpux* | -unos* | -osf* | -luna* | -dgux* | -solaris* | -sym* \ | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ | -aos* \ | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ | -hiux* | -386bsd* | -knetbsd* | -mirbsd* | -netbsd* \ | -openbsd* | -solidbsd* \ | -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \ | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \ | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ | -chorusos* | -chorusrdb* \ | -cygwin* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ | -mingw32* | -linux-gnu* | -linux-newlib* | -linux-uclibc* \ | -uxpv* | -beos* | -mpeix* | -udk* \ | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* | -opened* \ | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \ | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \ | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \ | -morphos* | -superux* | -rtmk* | -rtmk-nova* | -windiss* \ | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly* \ | -skyos* | -haiku* | -rdos* | -toppers*) # Remember, each alternative MUST END IN *, to match a version number. ;; -qnx*) case $basic_machine in x86-* | i*86-*) ;; *) os=-nto$os ;; esac ;; -nto-qnx*) ;; -nto*) os=`echo $os | sed -e 's|nto|nto-qnx|'` ;; -sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \ | -windows* | -osx | -abug | -netware* | -os9* | -beos* | -haiku* \ | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*) ;; -mac*) os=`echo $os | sed -e 's|mac|macos|'` ;; -linux-dietlibc) os=-linux-dietlibc ;; -linux*) os=`echo $os | sed -e 's|linux|linux-gnu|'` ;; -sunos5*) os=`echo $os | sed -e 's|sunos5|solaris2|'` ;; -sunos6*) os=`echo $os | sed -e 's|sunos6|solaris3|'` ;; -opened*) os=-openedition ;; -os400*) os=-os400 ;; -wince*) os=-wince ;; -osfrose*) os=-osfrose ;; -osf*) os=-osf ;; -utek*) os=-bsd ;; -dynix*) os=-bsd ;; -acis*) os=-aos ;; -atheos*) os=-atheos ;; -syllable*) os=-syllable ;; -386bsd) os=-bsd ;; -ctix* | -uts*) os=-sysv ;; -nova*) os=-rtmk-nova ;; -ns2 ) os=-nextstep2 ;; -nsk*) os=-nsk ;; # Preserve the version number of sinix5. -sinix5.*) os=`echo $os | sed -e 's|sinix|sysv|'` ;; -sinix*) os=-sysv4 ;; -tpf*) os=-tpf ;; -triton*) os=-sysv3 ;; -oss*) os=-sysv3 ;; -svr4) os=-sysv4 ;; -svr3) os=-sysv3 ;; -sysvr4) os=-sysv4 ;; # This must come after -sysvr4. -sysv*) ;; -ose*) os=-ose ;; -es1800*) os=-ose ;; -xenix) os=-xenix ;; -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) os=-mint ;; -aros*) os=-aros ;; -kaos*) os=-kaos ;; -zvmoe) os=-zvmoe ;; -none) ;; *) # Get rid of the `-' at the beginning of $os. os=`echo $os | sed 's/[^-]*-//'` echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2 exit 1 ;; esac else # Here we handle the default operating systems that come with various machines. # The value should be what the vendor currently ships out the door with their # machine or put another way, the most popular os provided with the machine. # Note that if you're going to try to match "-MANUFACTURER" here (say, # "-sun"), then you have to tell the case statement up towards the top # that MANUFACTURER isn't an operating system. Otherwise, code above # will signal an error saying that MANUFACTURER isn't an operating # system, and we'll never get to this point. case $basic_machine in score-*) os=-elf ;; spu-*) os=-elf ;; *-acorn) os=-riscix1.2 ;; arm*-rebel) os=-linux ;; arm*-semi) os=-aout ;; c4x-* | tic4x-*) os=-coff ;; # This must come before the *-dec entry. pdp10-*) os=-tops20 ;; pdp11-*) os=-none ;; *-dec | vax-*) os=-ultrix4.2 ;; m68*-apollo) os=-domain ;; i386-sun) os=-sunos4.0.2 ;; m68000-sun) os=-sunos3 # This also exists in the configure program, but was not the # default. # os=-sunos4 ;; m68*-cisco) os=-aout ;; mips*-cisco) os=-elf ;; mips*-*) os=-elf ;; or32-*) os=-coff ;; *-tti) # must be before sparc entry or we get the wrong os. os=-sysv3 ;; sparc-* | *-sun) os=-sunos4.1.1 ;; *-be) os=-beos ;; *-haiku) os=-haiku ;; *-ibm) os=-aix ;; *-knuth) os=-mmixware ;; *-wec) os=-proelf ;; *-winbond) os=-proelf ;; *-oki) os=-proelf ;; *-hp) os=-hpux ;; *-hitachi) os=-hiux ;; i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) os=-sysv ;; *-cbm) os=-amigaos ;; *-dg) os=-dgux ;; *-dolphin) os=-sysv3 ;; m68k-ccur) os=-rtu ;; m88k-omron*) os=-luna ;; *-next ) os=-nextstep ;; *-sequent) os=-ptx ;; *-crds) os=-unos ;; *-ns) os=-genix ;; i370-*) os=-mvs ;; *-next) os=-nextstep3 ;; *-gould) os=-sysv ;; *-highlevel) os=-bsd ;; *-encore) os=-bsd ;; *-sgi) os=-irix ;; *-siemens) os=-sysv4 ;; *-masscomp) os=-rtu ;; f30[01]-fujitsu | f700-fujitsu) os=-uxpv ;; *-rom68k) os=-coff ;; *-*bug) os=-coff ;; *-apple) os=-macos ;; *-atari*) os=-mint ;; *) os=-none ;; esac fi # Here we handle the case where we know the os, and the CPU type, but not the # manufacturer. We pick the logical manufacturer. vendor=unknown case $basic_machine in *-unknown) case $os in -riscix*) vendor=acorn ;; -sunos*) vendor=sun ;; -aix*) vendor=ibm ;; -beos*) vendor=be ;; -hpux*) vendor=hp ;; -mpeix*) vendor=hp ;; -hiux*) vendor=hitachi ;; -unos*) vendor=crds ;; -dgux*) vendor=dg ;; -luna*) vendor=omron ;; -genix*) vendor=ns ;; -mvs* | -opened*) vendor=ibm ;; -os400*) vendor=ibm ;; -ptx*) vendor=sequent ;; -tpf*) vendor=ibm ;; -vxsim* | -vxworks* | -windiss*) vendor=wrs ;; -aux*) vendor=apple ;; -hms*) vendor=hitachi ;; -mpw* | -macos*) vendor=apple ;; -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) vendor=atari ;; -vos*) vendor=stratus ;; esac basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"` ;; esac echo $basic_machine$os exit # Local variables: # eval: (add-hook 'write-file-hooks 'time-stamp) # time-stamp-start: "timestamp='" # time-stamp-format: "%:y-%02m-%02d" # time-stamp-end: "'" # End: nzbget-12.0+dfsg/configure000077500000000000000000011311631226450633000155620ustar00rootroot00000000000000#! /bin/sh # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.61 for nzbget 12.0. # # Report bugs to . # # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, # 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. # This configure script is free software; the Free Software Foundation # gives unlimited permission to copy, distribute and modify it. ## --------------------- ## ## M4sh Initialization. ## ## --------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then emulate sh NULLCMD=: # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in *posix*) set -o posix ;; esac fi # PATH needs CR # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits # The user is always right. if test "${PATH_SEPARATOR+set}" != set; then echo "#! /bin/sh" >conf$$.sh echo "exit 0" >>conf$$.sh chmod +x conf$$.sh if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then PATH_SEPARATOR=';' else PATH_SEPARATOR=: fi rm -f conf$$.sh fi # Support unset when possible. if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then as_unset=unset else as_unset=false fi # IFS # We need space, tab and new line, in precisely that order. Quoting is # there to prevent editors from complaining about space-tab. # (If _AS_PATH_WALK were called with IFS unset, it would disable word # splitting by setting IFS to empty value.) as_nl=' ' IFS=" "" $as_nl" # Find who we are. Look in the path if we contain no directory separator. case $0 in *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 { (exit 1); exit 1; } fi # Work around bugs in pre-3.0 UWIN ksh. for as_var in ENV MAIL MAILPATH do ($as_unset $as_var) >/dev/null 2>&1 && $as_unset $as_var done PS1='$ ' PS2='> ' PS4='+ ' # NLS nuisances. for as_var in \ LANG LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_CTYPE LC_IDENTIFICATION \ LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER \ LC_TELEPHONE LC_TIME do if (set +x; test -z "`(eval $as_var=C; export $as_var) 2>&1`"); then eval $as_var=C; export $as_var else ($as_unset $as_var) >/dev/null 2>&1 && $as_unset $as_var fi done # Required to use basename. if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi # Name of the executable. as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # CDPATH. $as_unset CDPATH if test "x$CONFIG_SHELL" = x; then if (eval ":") 2>/dev/null; then as_have_required=yes else as_have_required=no fi if test $as_have_required = yes && (eval ": (as_func_return () { (exit \$1) } as_func_success () { as_func_return 0 } as_func_failure () { as_func_return 1 } as_func_ret_success () { return 0 } as_func_ret_failure () { return 1 } exitcode=0 if as_func_success; then : else exitcode=1 echo as_func_success failed. fi if as_func_failure; then exitcode=1 echo as_func_failure succeeded. fi if as_func_ret_success; then : else exitcode=1 echo as_func_ret_success failed. fi if as_func_ret_failure; then exitcode=1 echo as_func_ret_failure succeeded. fi if ( set x; as_func_ret_success y && test x = \"\$1\" ); then : else exitcode=1 echo positional parameters were not saved. fi test \$exitcode = 0) || { (exit 1); exit 1; } ( as_lineno_1=\$LINENO as_lineno_2=\$LINENO test \"x\$as_lineno_1\" != \"x\$as_lineno_2\" && test \"x\`expr \$as_lineno_1 + 1\`\" = \"x\$as_lineno_2\") || { (exit 1); exit 1; } ") 2> /dev/null; then : else as_candidate_shells= as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. case $as_dir in /*) for as_base in sh bash ksh sh5; do as_candidate_shells="$as_candidate_shells $as_dir/$as_base" done;; esac done IFS=$as_save_IFS for as_shell in $as_candidate_shells $SHELL; do # Try only shells that exist, to save several forks. if { test -f "$as_shell" || test -f "$as_shell.exe"; } && { ("$as_shell") 2> /dev/null <<\_ASEOF if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then emulate sh NULLCMD=: # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in *posix*) set -o posix ;; esac fi : _ASEOF }; then CONFIG_SHELL=$as_shell as_have_required=yes if { "$as_shell" 2> /dev/null <<\_ASEOF if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then emulate sh NULLCMD=: # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in *posix*) set -o posix ;; esac fi : (as_func_return () { (exit $1) } as_func_success () { as_func_return 0 } as_func_failure () { as_func_return 1 } as_func_ret_success () { return 0 } as_func_ret_failure () { return 1 } exitcode=0 if as_func_success; then : else exitcode=1 echo as_func_success failed. fi if as_func_failure; then exitcode=1 echo as_func_failure succeeded. fi if as_func_ret_success; then : else exitcode=1 echo as_func_ret_success failed. fi if as_func_ret_failure; then exitcode=1 echo as_func_ret_failure succeeded. fi if ( set x; as_func_ret_success y && test x = "$1" ); then : else exitcode=1 echo positional parameters were not saved. fi test $exitcode = 0) || { (exit 1); exit 1; } ( as_lineno_1=$LINENO as_lineno_2=$LINENO test "x$as_lineno_1" != "x$as_lineno_2" && test "x`expr $as_lineno_1 + 1`" = "x$as_lineno_2") || { (exit 1); exit 1; } _ASEOF }; then break fi fi done if test "x$CONFIG_SHELL" != x; then for as_var in BASH_ENV ENV do ($as_unset $as_var) >/dev/null 2>&1 && $as_unset $as_var done export CONFIG_SHELL exec "$CONFIG_SHELL" "$as_myself" ${1+"$@"} fi if test $as_have_required = no; then echo This script requires a shell more modern than all the echo shells that I found on your system. Please install a echo modern shell, or manually run the script under such a echo shell if you do have one. { (exit 1); exit 1; } fi fi fi (eval "as_func_return () { (exit \$1) } as_func_success () { as_func_return 0 } as_func_failure () { as_func_return 1 } as_func_ret_success () { return 0 } as_func_ret_failure () { return 1 } exitcode=0 if as_func_success; then : else exitcode=1 echo as_func_success failed. fi if as_func_failure; then exitcode=1 echo as_func_failure succeeded. fi if as_func_ret_success; then : else exitcode=1 echo as_func_ret_success failed. fi if as_func_ret_failure; then exitcode=1 echo as_func_ret_failure succeeded. fi if ( set x; as_func_ret_success y && test x = \"\$1\" ); then : else exitcode=1 echo positional parameters were not saved. fi test \$exitcode = 0") || { echo No shell found that supports shell functions. echo Please tell autoconf@gnu.org about your system, echo including any error possibly output before this echo message } as_lineno_1=$LINENO as_lineno_2=$LINENO test "x$as_lineno_1" != "x$as_lineno_2" && test "x`expr $as_lineno_1 + 1`" = "x$as_lineno_2" || { # Create $as_me.lineno as a copy of $as_myself, but with $LINENO # uniformly replaced by the line number. The first 'sed' inserts a # line-number line after each line using $LINENO; the second 'sed' # does the real work. The second script uses 'N' to pair each # line-number line with the line containing $LINENO, and appends # trailing '-' during substitution so that $LINENO is not a special # case at line end. # (Raja R Harinath suggested sed '=', and Paul Eggert wrote the # scripts with optimization help from Paolo Bonzini. Blame Lee # E. McMahon (1931-1989) for sed's syntax. :-) sed -n ' p /[$]LINENO/= ' <$as_myself | sed ' s/[$]LINENO.*/&-/ t lineno b :lineno N :loop s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ t loop s/-\n.*// ' >$as_me.lineno && chmod +x "$as_me.lineno" || { echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2 { (exit 1); exit 1; }; } # Don't try to exec as it changes $[0], causing all sort of problems # (the dirname of $[0] is not the place where we might find the # original and so on. Autoconf is especially sensitive to this). . "./$as_me.lineno" # Exit status is that of the last command. exit } if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in -n*) case `echo 'x\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. *) ECHO_C='\c';; esac;; *) ECHO_N='-n';; esac if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir fi echo >conf$$.file if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -p'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -p' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -p' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null if mkdir -p . 2>/dev/null; then as_mkdir_p=: else test -d ./-p && rmdir ./-p as_mkdir_p=false fi if test -x / >/dev/null 2>&1; then as_test_x='test -x' else if ls -dL / >/dev/null 2>&1; then as_ls_L_option=L else as_ls_L_option= fi as_test_x=' eval sh -c '\'' if test -d "$1"; then test -d "$1/."; else case $1 in -*)set "./$1";; esac; case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in ???[sx]*):;;*)false;;esac;fi '\'' sh ' fi as_executable_p=$as_test_x # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" exec 7<&0 &1 # Name of the host. # hostname on some systems (SVR3.2, Linux) returns a bogus exit status, # so uname gets run too. ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` # # Initializations. # ac_default_prefix=/usr/local ac_clean_files= ac_config_libobj_dir=. LIBOBJS= cross_compiling=no subdirs= MFLAGS= MAKEFLAGS= SHELL=${CONFIG_SHELL-/bin/sh} # Identity of this package. PACKAGE_NAME='nzbget' PACKAGE_TARNAME='nzbget' PACKAGE_VERSION='12.0' PACKAGE_STRING='nzbget 12.0' PACKAGE_BUGREPORT='hugbug@users.sourceforge.net' ac_unique_file="nzbget.cpp" # Factoring default headers for most tests. ac_includes_default="\ #include #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_SYS_STAT_H # include #endif #ifdef STDC_HEADERS # include # include #else # ifdef HAVE_STDLIB_H # include # endif #endif #ifdef HAVE_STRING_H # if !defined STDC_HEADERS && defined HAVE_MEMORY_H # include # endif # include #endif #ifdef HAVE_STRINGS_H # include #endif #ifdef HAVE_INTTYPES_H # include #endif #ifdef HAVE_STDINT_H # include #endif #ifdef HAVE_UNISTD_H # include #endif" ac_subst_vars='SHELL PATH_SEPARATOR PACKAGE_NAME PACKAGE_TARNAME PACKAGE_VERSION PACKAGE_STRING PACKAGE_BUGREPORT exec_prefix prefix program_transform_name bindir sbindir libexecdir datarootdir datadir sysconfdir sharedstatedir localstatedir includedir oldincludedir docdir infodir htmldir dvidir pdfdir psdir libdir localedir mandir DEFS ECHO_C ECHO_N ECHO_T LIBS build_alias host_alias target_alias build build_cpu build_vendor build_os host host_cpu host_vendor host_os target target_cpu target_vendor target_os INSTALL_PROGRAM INSTALL_SCRIPT INSTALL_DATA CYGPATH_W PACKAGE VERSION ACLOCAL AUTOCONF AUTOMAKE AUTOHEADER MAKEINFO install_sh STRIP INSTALL_STRIP_PROGRAM mkdir_p AWK SET_MAKE am__leading_dot AMTAR am__tar am__untar CXX CXXFLAGS LDFLAGS CPPFLAGS ac_ct_CXX EXEEXT OBJEXT DEPDIR am__include am__quote AMDEP_TRUE AMDEP_FALSE AMDEPBACKSLASH CXXDEPMODE am__fastdepCXX_TRUE am__fastdepCXX_FALSE TAR MAKE CXXCPP GREP EGREP PKG_CONFIG libxml2_CFLAGS libxml2_LIBS libsigc_CFLAGS libsigc_LIBS openssl_CFLAGS openssl_LIBS LIBOBJS LTLIBOBJS' ac_subst_files='' ac_precious_vars='build_alias host_alias target_alias CXX CXXFLAGS LDFLAGS LIBS CPPFLAGS CCC CXXCPP PKG_CONFIG libxml2_CFLAGS libxml2_LIBS libsigc_CFLAGS libsigc_LIBS openssl_CFLAGS openssl_LIBS' # Initialize some variables set by options. ac_init_help= ac_init_version=false # The variables have the same names as the options, with # dashes changed to underlines. cache_file=/dev/null exec_prefix=NONE no_create= no_recursion= prefix=NONE program_prefix=NONE program_suffix=NONE program_transform_name=s,x,x, silent= site= srcdir= verbose= x_includes=NONE x_libraries=NONE # Installation directory options. # These are left unexpanded so users can "make install exec_prefix=/foo" # and all the variables that are supposed to be based on exec_prefix # by default will actually change. # Use braces instead of parens because sh, perl, etc. also accept them. # (The list follows the same order as the GNU Coding Standards.) bindir='${exec_prefix}/bin' sbindir='${exec_prefix}/sbin' libexecdir='${exec_prefix}/libexec' datarootdir='${prefix}/share' datadir='${datarootdir}' sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' infodir='${datarootdir}/info' htmldir='${docdir}' dvidir='${docdir}' pdfdir='${docdir}' psdir='${docdir}' libdir='${exec_prefix}/lib' localedir='${datarootdir}/locale' mandir='${datarootdir}/man' ac_prev= ac_dashdash= for ac_option do # If the previous option needs an argument, assign it. if test -n "$ac_prev"; then eval $ac_prev=\$ac_option ac_prev= continue fi case $ac_option in *=*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; *) ac_optarg=yes ;; esac # Accept the important Cygnus configure options, so we can diagnose typos. case $ac_dashdash$ac_option in --) ac_dashdash=yes ;; -bindir | --bindir | --bindi | --bind | --bin | --bi) ac_prev=bindir ;; -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) bindir=$ac_optarg ;; -build | --build | --buil | --bui | --bu) ac_prev=build_alias ;; -build=* | --build=* | --buil=* | --bui=* | --bu=*) build_alias=$ac_optarg ;; -cache-file | --cache-file | --cache-fil | --cache-fi \ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) ac_prev=cache_file ;; -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) cache_file=$ac_optarg ;; --config-cache | -C) cache_file=config.cache ;; -datadir | --datadir | --datadi | --datad) ac_prev=datadir ;; -datadir=* | --datadir=* | --datadi=* | --datad=*) datadir=$ac_optarg ;; -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ | --dataroo | --dataro | --datar) ac_prev=datarootdir ;; -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) datarootdir=$ac_optarg ;; -disable-* | --disable-*) ac_feature=`expr "x$ac_option" : 'x-*disable-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_feature" : ".*[^-._$as_cr_alnum]" >/dev/null && { echo "$as_me: error: invalid feature name: $ac_feature" >&2 { (exit 1); exit 1; }; } ac_feature=`echo $ac_feature | sed 's/[-.]/_/g'` eval enable_$ac_feature=no ;; -docdir | --docdir | --docdi | --doc | --do) ac_prev=docdir ;; -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) docdir=$ac_optarg ;; -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) ac_prev=dvidir ;; -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) dvidir=$ac_optarg ;; -enable-* | --enable-*) ac_feature=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_feature" : ".*[^-._$as_cr_alnum]" >/dev/null && { echo "$as_me: error: invalid feature name: $ac_feature" >&2 { (exit 1); exit 1; }; } ac_feature=`echo $ac_feature | sed 's/[-.]/_/g'` eval enable_$ac_feature=\$ac_optarg ;; -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ | --exec | --exe | --ex) ac_prev=exec_prefix ;; -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ | --exec=* | --exe=* | --ex=*) exec_prefix=$ac_optarg ;; -gas | --gas | --ga | --g) # Obsolete; use --with-gas. with_gas=yes ;; -help | --help | --hel | --he | -h) ac_init_help=long ;; -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) ac_init_help=recursive ;; -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) ac_init_help=short ;; -host | --host | --hos | --ho) ac_prev=host_alias ;; -host=* | --host=* | --hos=* | --ho=*) host_alias=$ac_optarg ;; -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) ac_prev=htmldir ;; -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ | --ht=*) htmldir=$ac_optarg ;; -includedir | --includedir | --includedi | --included | --include \ | --includ | --inclu | --incl | --inc) ac_prev=includedir ;; -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ | --includ=* | --inclu=* | --incl=* | --inc=*) includedir=$ac_optarg ;; -infodir | --infodir | --infodi | --infod | --info | --inf) ac_prev=infodir ;; -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) infodir=$ac_optarg ;; -libdir | --libdir | --libdi | --libd) ac_prev=libdir ;; -libdir=* | --libdir=* | --libdi=* | --libd=*) libdir=$ac_optarg ;; -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ | --libexe | --libex | --libe) ac_prev=libexecdir ;; -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ | --libexe=* | --libex=* | --libe=*) libexecdir=$ac_optarg ;; -localedir | --localedir | --localedi | --localed | --locale) ac_prev=localedir ;; -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) localedir=$ac_optarg ;; -localstatedir | --localstatedir | --localstatedi | --localstated \ | --localstate | --localstat | --localsta | --localst | --locals) ac_prev=localstatedir ;; -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) localstatedir=$ac_optarg ;; -mandir | --mandir | --mandi | --mand | --man | --ma | --m) ac_prev=mandir ;; -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) mandir=$ac_optarg ;; -nfp | --nfp | --nf) # Obsolete; use --without-fp. with_fp=no ;; -no-create | --no-create | --no-creat | --no-crea | --no-cre \ | --no-cr | --no-c | -n) no_create=yes ;; -no-recursion | --no-recursion | --no-recursio | --no-recursi \ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) no_recursion=yes ;; -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ | --oldin | --oldi | --old | --ol | --o) ac_prev=oldincludedir ;; -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) oldincludedir=$ac_optarg ;; -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) ac_prev=prefix ;; -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) prefix=$ac_optarg ;; -program-prefix | --program-prefix | --program-prefi | --program-pref \ | --program-pre | --program-pr | --program-p) ac_prev=program_prefix ;; -program-prefix=* | --program-prefix=* | --program-prefi=* \ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) program_prefix=$ac_optarg ;; -program-suffix | --program-suffix | --program-suffi | --program-suff \ | --program-suf | --program-su | --program-s) ac_prev=program_suffix ;; -program-suffix=* | --program-suffix=* | --program-suffi=* \ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) program_suffix=$ac_optarg ;; -program-transform-name | --program-transform-name \ | --program-transform-nam | --program-transform-na \ | --program-transform-n | --program-transform- \ | --program-transform | --program-transfor \ | --program-transfo | --program-transf \ | --program-trans | --program-tran \ | --progr-tra | --program-tr | --program-t) ac_prev=program_transform_name ;; -program-transform-name=* | --program-transform-name=* \ | --program-transform-nam=* | --program-transform-na=* \ | --program-transform-n=* | --program-transform-=* \ | --program-transform=* | --program-transfor=* \ | --program-transfo=* | --program-transf=* \ | --program-trans=* | --program-tran=* \ | --progr-tra=* | --program-tr=* | --program-t=*) program_transform_name=$ac_optarg ;; -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) ac_prev=pdfdir ;; -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) pdfdir=$ac_optarg ;; -psdir | --psdir | --psdi | --psd | --ps) ac_prev=psdir ;; -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) psdir=$ac_optarg ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) silent=yes ;; -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ | --sbi=* | --sb=*) sbindir=$ac_optarg ;; -sharedstatedir | --sharedstatedir | --sharedstatedi \ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ | --sharedst | --shareds | --shared | --share | --shar \ | --sha | --sh) ac_prev=sharedstatedir ;; -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ | --sha=* | --sh=*) sharedstatedir=$ac_optarg ;; -site | --site | --sit) ac_prev=site ;; -site=* | --site=* | --sit=*) site=$ac_optarg ;; -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) ac_prev=srcdir ;; -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) srcdir=$ac_optarg ;; -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ | --syscon | --sysco | --sysc | --sys | --sy) ac_prev=sysconfdir ;; -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) sysconfdir=$ac_optarg ;; -target | --target | --targe | --targ | --tar | --ta | --t) ac_prev=target_alias ;; -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) target_alias=$ac_optarg ;; -v | -verbose | --verbose | --verbos | --verbo | --verb) verbose=yes ;; -version | --version | --versio | --versi | --vers | -V) ac_init_version=: ;; -with-* | --with-*) ac_package=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_package" : ".*[^-._$as_cr_alnum]" >/dev/null && { echo "$as_me: error: invalid package name: $ac_package" >&2 { (exit 1); exit 1; }; } ac_package=`echo $ac_package | sed 's/[-.]/_/g'` eval with_$ac_package=\$ac_optarg ;; -without-* | --without-*) ac_package=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_package" : ".*[^-._$as_cr_alnum]" >/dev/null && { echo "$as_me: error: invalid package name: $ac_package" >&2 { (exit 1); exit 1; }; } ac_package=`echo $ac_package | sed 's/[-.]/_/g'` eval with_$ac_package=no ;; --x) # Obsolete; use --with-x. with_x=yes ;; -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ | --x-incl | --x-inc | --x-in | --x-i) ac_prev=x_includes ;; -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) x_includes=$ac_optarg ;; -x-libraries | --x-libraries | --x-librarie | --x-librari \ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) ac_prev=x_libraries ;; -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; -*) { echo "$as_me: error: unrecognized option: $ac_option Try \`$0 --help' for more information." >&2 { (exit 1); exit 1; }; } ;; *=*) ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` # Reject names that are not valid shell variable names. expr "x$ac_envvar" : ".*[^_$as_cr_alnum]" >/dev/null && { echo "$as_me: error: invalid variable name: $ac_envvar" >&2 { (exit 1); exit 1; }; } eval $ac_envvar=\$ac_optarg export $ac_envvar ;; *) # FIXME: should be removed in autoconf 3.0. echo "$as_me: WARNING: you should use --build, --host, --target" >&2 expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && echo "$as_me: WARNING: invalid host type: $ac_option" >&2 : ${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option} ;; esac done if test -n "$ac_prev"; then ac_option=--`echo $ac_prev | sed 's/_/-/g'` { echo "$as_me: error: missing argument to $ac_option" >&2 { (exit 1); exit 1; }; } fi # Be sure to have absolute directory names. for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ libdir localedir mandir do eval ac_val=\$$ac_var case $ac_val in [\\/$]* | ?:[\\/]* ) continue;; NONE | '' ) case $ac_var in *prefix ) continue;; esac;; esac { echo "$as_me: error: expected an absolute directory name for --$ac_var: $ac_val" >&2 { (exit 1); exit 1; }; } done # There might be people who depend on the old broken behavior: `$host' # used to hold the argument of --host etc. # FIXME: To remove some day. build=$build_alias host=$host_alias target=$target_alias # FIXME: To remove some day. if test "x$host_alias" != x; then if test "x$build_alias" = x; then cross_compiling=maybe echo "$as_me: WARNING: If you wanted to set the --build type, don't use --host. If a cross compiler is detected then cross compile mode will be used." >&2 elif test "x$build_alias" != "x$host_alias"; then cross_compiling=yes fi fi ac_tool_prefix= test -n "$host_alias" && ac_tool_prefix=$host_alias- test "$silent" = yes && exec 6>/dev/null ac_pwd=`pwd` && test -n "$ac_pwd" && ac_ls_di=`ls -di .` && ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || { echo "$as_me: error: Working directory cannot be determined" >&2 { (exit 1); exit 1; }; } test "X$ac_ls_di" = "X$ac_pwd_ls_di" || { echo "$as_me: error: pwd does not report name of working directory" >&2 { (exit 1); exit 1; }; } # Find the source files, if location was not specified. if test -z "$srcdir"; then ac_srcdir_defaulted=yes # Try the directory containing this script, then the parent directory. ac_confdir=`$as_dirname -- "$0" || $as_expr X"$0" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$0" : 'X\(//\)[^/]' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || echo X"$0" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` srcdir=$ac_confdir if test ! -r "$srcdir/$ac_unique_file"; then srcdir=.. fi else ac_srcdir_defaulted=no fi if test ! -r "$srcdir/$ac_unique_file"; then test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." { echo "$as_me: error: cannot find sources ($ac_unique_file) in $srcdir" >&2 { (exit 1); exit 1; }; } fi ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" ac_abs_confdir=`( cd "$srcdir" && test -r "./$ac_unique_file" || { echo "$as_me: error: $ac_msg" >&2 { (exit 1); exit 1; }; } pwd)` # When building in place, set srcdir=. if test "$ac_abs_confdir" = "$ac_pwd"; then srcdir=. fi # Remove unnecessary trailing slashes from srcdir. # Double slashes in file names in object file debugging info # mess up M-x gdb in Emacs. case $srcdir in */) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; esac for ac_var in $ac_precious_vars; do eval ac_env_${ac_var}_set=\${${ac_var}+set} eval ac_env_${ac_var}_value=\$${ac_var} eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} eval ac_cv_env_${ac_var}_value=\$${ac_var} done # # Report the --help message. # if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF \`configure' configures nzbget 12.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... To assign environment variables (e.g., CC, CFLAGS...), specify them as VAR=VALUE. See below for descriptions of some of the useful variables. Defaults for the options are specified in brackets. Configuration: -h, --help display this help and exit --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit -q, --quiet, --silent do not print \`checking...' messages --cache-file=FILE cache test results in FILE [disabled] -C, --config-cache alias for \`--cache-file=config.cache' -n, --no-create do not create output files --srcdir=DIR find the sources in DIR [configure dir or \`..'] Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX [$ac_default_prefix] --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] By default, \`make install' will install all the files in \`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify an installation prefix other than \`$ac_default_prefix' using \`--prefix', for instance \`--prefix=\$HOME'. For better control, use the options below. Fine tuning of the installation directories: --bindir=DIR user executables [EPREFIX/bin] --sbindir=DIR system admin executables [EPREFIX/sbin] --libexecdir=DIR program executables [EPREFIX/libexec] --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] --datadir=DIR read-only architecture-independent data [DATAROOTDIR] --infodir=DIR info documentation [DATAROOTDIR/info] --localedir=DIR locale-dependent data [DATAROOTDIR/locale] --mandir=DIR man documentation [DATAROOTDIR/man] --docdir=DIR documentation root [DATAROOTDIR/doc/nzbget] --htmldir=DIR html documentation [DOCDIR] --dvidir=DIR dvi documentation [DOCDIR] --pdfdir=DIR pdf documentation [DOCDIR] --psdir=DIR ps documentation [DOCDIR] _ACEOF cat <<\_ACEOF Program names: --program-prefix=PREFIX prepend PREFIX to installed program names --program-suffix=SUFFIX append SUFFIX to installed program names --program-transform-name=PROGRAM run sed PROGRAM on installed program names System types: --build=BUILD configure for building on BUILD [guessed] --host=HOST cross-compile to build programs to run on HOST [BUILD] --target=TARGET configure for building compilers for TARGET [HOST] _ACEOF fi if test -n "$ac_init_help"; then case $ac_init_help in short | recursive ) echo "Configuration of nzbget 12.0:";; esac cat <<\_ACEOF Optional Features: --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) --enable-FEATURE[=ARG] include FEATURE [ARG=yes] --disable-dependency-tracking speeds up one-time build --enable-dependency-tracking do not reject slow dependency extractors --disable-largefile omit support for large files --disable-curses do not use curses (removes dependency from curses-library) --disable-parcheck do not include par-check/-repair-support (removes dependency from libpar2- and libsigc-libraries) --disable-libpar2-bugfixes-check do not check if libpar2 has recent bugfixes-patch applied --disable-tls do not use TLS/SSL (removes dependency from TLS/SSL-libraries) --disable-gzip disable gzip-compression/decompression (removes dependency from zlib-library) --disable-sigchld-handler do not use sigchld-handler (the disabling may be neccessary on 32-Bit BSD) --enable-debug enable debugging Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-libxml2-includes=DIR libxml2 include directory --with-libxml2-libraries=DIR libxml2 library directory --with-libcurses-includes=DIR libcurses include directory --with-libcurses-libraries=DIR libcurses library directory --with-libsigc-includes=DIR libsigc++-2.0 include directory --with-libsigc-libraries=DIR libsigc++-2.0 library directory --with-libpar2-includes=DIR libpar2 include directory --with-libpar2-libraries=DIR libpar2 library directory --with-tlslib=(GnuTLS, OpenSSL) TLS/SSL library to use --with-libgnutls-includes=DIR GnuTLS include directory --with-libgnutls-libraries=DIR GnuTLS library directory --with-openssl-includes=DIR OpenSSL include directory --with-openssl-libraries=DIR OpenSSL library directory --with-zlib-includes=DIR zlib include directory --with-zlib-libraries=DIR zlib library directory Some influential environment variables: CXX C++ compiler command CXXFLAGS C++ compiler flags LDFLAGS linker flags, e.g. -L if you have libraries in a nonstandard directory LIBS libraries to pass to the linker, e.g. -l CPPFLAGS C/C++/Objective C preprocessor flags, e.g. -I if you have headers in a nonstandard directory CXXCPP C++ preprocessor PKG_CONFIG path to pkg-config utility libxml2_CFLAGS C compiler flags for libxml2, overriding pkg-config libxml2_LIBS linker flags for libxml2, overriding pkg-config libsigc_CFLAGS C compiler flags for libsigc, overriding pkg-config libsigc_LIBS linker flags for libsigc, overriding pkg-config openssl_CFLAGS C compiler flags for openssl, overriding pkg-config openssl_LIBS linker flags for openssl, overriding pkg-config Use these variables to override the choices made by `configure' or to help it to find libraries and programs with nonstandard names/locations. Report bugs to . _ACEOF ac_status=$? fi if test "$ac_init_help" = "recursive"; then # If there are subdirs, report their specific --help. for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue test -d "$ac_dir" || continue ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`echo "$ac_dir" | sed 's,^\.[\\/],,'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`echo "$ac_dir_suffix" | sed 's,/[^\\/]*,/..,g;s,/,,'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix cd "$ac_dir" || { ac_status=$?; continue; } # Check for guested configure. if test -f "$ac_srcdir/configure.gnu"; then echo && $SHELL "$ac_srcdir/configure.gnu" --help=recursive elif test -f "$ac_srcdir/configure"; then echo && $SHELL "$ac_srcdir/configure" --help=recursive else echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 fi || ac_status=$? cd "$ac_pwd" || { ac_status=$?; break; } done fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF nzbget configure 12.0 generated by GNU Autoconf 2.61 Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. _ACEOF exit fi cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by nzbget $as_me 12.0, which was generated by GNU Autoconf 2.61. Invocation command line was $ $0 $@ _ACEOF exec 5>>config.log { cat <<_ASUNAME ## --------- ## ## Platform. ## ## --------- ## hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` uname -m = `(uname -m) 2>/dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` /bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` /bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` /usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` /bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` /bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` _ASUNAME as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. echo "PATH: $as_dir" done IFS=$as_save_IFS } >&5 cat >&5 <<_ACEOF ## ----------- ## ## Core tests. ## ## ----------- ## _ACEOF # Keep a trace of the command line. # Strip out --no-create and --no-recursion so they do not pile up. # Strip out --silent because we don't want to record it for future runs. # Also quote any args containing shell meta-characters. # Make two passes to allow for proper duplicate-argument suppression. ac_configure_args= ac_configure_args0= ac_configure_args1= ac_must_keep_next=false for ac_pass in 1 2 do for ac_arg do case $ac_arg in -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) continue ;; *\'*) ac_arg=`echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac case $ac_pass in 1) ac_configure_args0="$ac_configure_args0 '$ac_arg'" ;; 2) ac_configure_args1="$ac_configure_args1 '$ac_arg'" if test $ac_must_keep_next = true; then ac_must_keep_next=false # Got value, back to normal. else case $ac_arg in *=* | --config-cache | -C | -disable-* | --disable-* \ | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ | -with-* | --with-* | -without-* | --without-* | --x) case "$ac_configure_args0 " in "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; esac ;; -* ) ac_must_keep_next=true ;; esac fi ac_configure_args="$ac_configure_args '$ac_arg'" ;; esac done done $as_unset ac_configure_args0 || test "${ac_configure_args0+set}" != set || { ac_configure_args0=; export ac_configure_args0; } $as_unset ac_configure_args1 || test "${ac_configure_args1+set}" != set || { ac_configure_args1=; export ac_configure_args1; } # When interrupted or exit'd, cleanup temporary files, and complete # config.log. We remove comments because anyway the quotes in there # would cause problems or look ugly. # WARNING: Use '\'' to represent an apostrophe within the trap. # WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. trap 'exit_status=$? # Save into config.log some information that might help in debugging. { echo cat <<\_ASBOX ## ---------------- ## ## Cache variables. ## ## ---------------- ## _ASBOX echo # The following way of writing the cache mishandles newlines in values, ( for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { echo "$as_me:$LINENO: WARNING: Cache variable $ac_var contains a newline." >&5 echo "$as_me: WARNING: Cache variable $ac_var contains a newline." >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( *) $as_unset $ac_var ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( *${as_nl}ac_space=\ *) sed -n \ "s/'\''/'\''\\\\'\'''\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" ;; #( *) sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) echo cat <<\_ASBOX ## ----------------- ## ## Output variables. ## ## ----------------- ## _ASBOX echo for ac_var in $ac_subst_vars do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac echo "$ac_var='\''$ac_val'\''" done | sort echo if test -n "$ac_subst_files"; then cat <<\_ASBOX ## ------------------- ## ## File substitutions. ## ## ------------------- ## _ASBOX echo for ac_var in $ac_subst_files do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac echo "$ac_var='\''$ac_val'\''" done | sort echo fi if test -s confdefs.h; then cat <<\_ASBOX ## ----------- ## ## confdefs.h. ## ## ----------- ## _ASBOX echo cat confdefs.h echo fi test "$ac_signal" != 0 && echo "$as_me: caught signal $ac_signal" echo "$as_me: exit $exit_status" } >&5 rm -f core *.core core.conftest.* && rm -f -r conftest* confdefs* conf$$* $ac_clean_files && exit $exit_status ' 0 for ac_signal in 1 2 13 15; do trap 'ac_signal='$ac_signal'; { (exit 1); exit 1; }' $ac_signal done ac_signal=0 # confdefs.h avoids OS command line length limits that DEFS can exceed. rm -f -r conftest* confdefs.h # Predefined preprocessor variables. cat >>confdefs.h <<_ACEOF #define PACKAGE_NAME "$PACKAGE_NAME" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_TARNAME "$PACKAGE_TARNAME" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_VERSION "$PACKAGE_VERSION" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_STRING "$PACKAGE_STRING" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" _ACEOF # Let the site file select an alternate cache file if it wants to. # Prefer explicitly selected file to automatically selected ones. if test -n "$CONFIG_SITE"; then set x "$CONFIG_SITE" elif test "x$prefix" != xNONE; then set x "$prefix/share/config.site" "$prefix/etc/config.site" else set x "$ac_default_prefix/share/config.site" \ "$ac_default_prefix/etc/config.site" fi shift for ac_site_file do if test -r "$ac_site_file"; then { echo "$as_me:$LINENO: loading site script $ac_site_file" >&5 echo "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" fi done if test -r "$cache_file"; then # Some versions of bash will fail to source /dev/null (special # files actually), so we avoid doing that. if test -f "$cache_file"; then { echo "$as_me:$LINENO: loading cache $cache_file" >&5 echo "$as_me: loading cache $cache_file" >&6;} case $cache_file in [\\/]* | ?:[\\/]* ) . "$cache_file";; *) . "./$cache_file";; esac fi else { echo "$as_me:$LINENO: creating cache $cache_file" >&5 echo "$as_me: creating cache $cache_file" >&6;} >$cache_file fi # Check that the precious variables saved in the cache have kept the same # value. ac_cache_corrupted=false for ac_var in $ac_precious_vars; do eval ac_old_set=\$ac_cv_env_${ac_var}_set eval ac_new_set=\$ac_env_${ac_var}_set eval ac_old_val=\$ac_cv_env_${ac_var}_value eval ac_new_val=\$ac_env_${ac_var}_value case $ac_old_set,$ac_new_set in set,) { echo "$as_me:$LINENO: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set) { echo "$as_me:$LINENO: error: \`$ac_var' was not set in the previous run" >&5 echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *) if test "x$ac_old_val" != "x$ac_new_val"; then { echo "$as_me:$LINENO: error: \`$ac_var' has changed since the previous run:" >&5 echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} { echo "$as_me:$LINENO: former value: $ac_old_val" >&5 echo "$as_me: former value: $ac_old_val" >&2;} { echo "$as_me:$LINENO: current value: $ac_new_val" >&5 echo "$as_me: current value: $ac_new_val" >&2;} ac_cache_corrupted=: fi;; esac # Pass precious variables to config.status. if test "$ac_new_set" = set; then case $ac_new_val in *\'*) ac_arg=$ac_var=`echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; *) ac_arg=$ac_var=$ac_new_val ;; esac case " $ac_configure_args " in *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. *) ac_configure_args="$ac_configure_args '$ac_arg'" ;; esac fi done if $ac_cache_corrupted; then { echo "$as_me:$LINENO: error: changes in the environment can compromise the build" >&5 echo "$as_me: error: changes in the environment can compromise the build" >&2;} { { echo "$as_me:$LINENO: error: run \`make distclean' and/or \`rm $cache_file' and start over" >&5 echo "$as_me: error: run \`make distclean' and/or \`rm $cache_file' and start over" >&2;} { (exit 1); exit 1; }; } fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_aux_dir= for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do if test -f "$ac_dir/install-sh"; then ac_aux_dir=$ac_dir ac_install_sh="$ac_aux_dir/install-sh -c" break elif test -f "$ac_dir/install.sh"; then ac_aux_dir=$ac_dir ac_install_sh="$ac_aux_dir/install.sh -c" break elif test -f "$ac_dir/shtool"; then ac_aux_dir=$ac_dir ac_install_sh="$ac_aux_dir/shtool install -c" break fi done if test -z "$ac_aux_dir"; then { { echo "$as_me:$LINENO: error: cannot find install-sh or install.sh in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" >&5 echo "$as_me: error: cannot find install-sh or install.sh in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" >&2;} { (exit 1); exit 1; }; } fi # These three variables are undocumented and unsupported, # and are intended to be withdrawn in a future Autoconf release. # They can cause serious problems if a builder's source tree is in a directory # whose full name contains unusual characters. ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var. ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var. ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var. # Make sure we can run config.sub. $SHELL "$ac_aux_dir/config.sub" sun4 >/dev/null 2>&1 || { { echo "$as_me:$LINENO: error: cannot run $SHELL $ac_aux_dir/config.sub" >&5 echo "$as_me: error: cannot run $SHELL $ac_aux_dir/config.sub" >&2;} { (exit 1); exit 1; }; } { echo "$as_me:$LINENO: checking build system type" >&5 echo $ECHO_N "checking build system type... $ECHO_C" >&6; } if test "${ac_cv_build+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_build_alias=$build_alias test "x$ac_build_alias" = x && ac_build_alias=`$SHELL "$ac_aux_dir/config.guess"` test "x$ac_build_alias" = x && { { echo "$as_me:$LINENO: error: cannot guess build type; you must specify one" >&5 echo "$as_me: error: cannot guess build type; you must specify one" >&2;} { (exit 1); exit 1; }; } ac_cv_build=`$SHELL "$ac_aux_dir/config.sub" $ac_build_alias` || { { echo "$as_me:$LINENO: error: $SHELL $ac_aux_dir/config.sub $ac_build_alias failed" >&5 echo "$as_me: error: $SHELL $ac_aux_dir/config.sub $ac_build_alias failed" >&2;} { (exit 1); exit 1; }; } fi { echo "$as_me:$LINENO: result: $ac_cv_build" >&5 echo "${ECHO_T}$ac_cv_build" >&6; } case $ac_cv_build in *-*-*) ;; *) { { echo "$as_me:$LINENO: error: invalid value of canonical build" >&5 echo "$as_me: error: invalid value of canonical build" >&2;} { (exit 1); exit 1; }; };; esac build=$ac_cv_build ac_save_IFS=$IFS; IFS='-' set x $ac_cv_build shift build_cpu=$1 build_vendor=$2 shift; shift # Remember, the first character of IFS is used to create $*, # except with old shells: build_os=$* IFS=$ac_save_IFS case $build_os in *\ *) build_os=`echo "$build_os" | sed 's/ /-/g'`;; esac { echo "$as_me:$LINENO: checking host system type" >&5 echo $ECHO_N "checking host system type... $ECHO_C" >&6; } if test "${ac_cv_host+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test "x$host_alias" = x; then ac_cv_host=$ac_cv_build else ac_cv_host=`$SHELL "$ac_aux_dir/config.sub" $host_alias` || { { echo "$as_me:$LINENO: error: $SHELL $ac_aux_dir/config.sub $host_alias failed" >&5 echo "$as_me: error: $SHELL $ac_aux_dir/config.sub $host_alias failed" >&2;} { (exit 1); exit 1; }; } fi fi { echo "$as_me:$LINENO: result: $ac_cv_host" >&5 echo "${ECHO_T}$ac_cv_host" >&6; } case $ac_cv_host in *-*-*) ;; *) { { echo "$as_me:$LINENO: error: invalid value of canonical host" >&5 echo "$as_me: error: invalid value of canonical host" >&2;} { (exit 1); exit 1; }; };; esac host=$ac_cv_host ac_save_IFS=$IFS; IFS='-' set x $ac_cv_host shift host_cpu=$1 host_vendor=$2 shift; shift # Remember, the first character of IFS is used to create $*, # except with old shells: host_os=$* IFS=$ac_save_IFS case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac { echo "$as_me:$LINENO: checking target system type" >&5 echo $ECHO_N "checking target system type... $ECHO_C" >&6; } if test "${ac_cv_target+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test "x$target_alias" = x; then ac_cv_target=$ac_cv_host else ac_cv_target=`$SHELL "$ac_aux_dir/config.sub" $target_alias` || { { echo "$as_me:$LINENO: error: $SHELL $ac_aux_dir/config.sub $target_alias failed" >&5 echo "$as_me: error: $SHELL $ac_aux_dir/config.sub $target_alias failed" >&2;} { (exit 1); exit 1; }; } fi fi { echo "$as_me:$LINENO: result: $ac_cv_target" >&5 echo "${ECHO_T}$ac_cv_target" >&6; } case $ac_cv_target in *-*-*) ;; *) { { echo "$as_me:$LINENO: error: invalid value of canonical target" >&5 echo "$as_me: error: invalid value of canonical target" >&2;} { (exit 1); exit 1; }; };; esac target=$ac_cv_target ac_save_IFS=$IFS; IFS='-' set x $ac_cv_target shift target_cpu=$1 target_vendor=$2 shift; shift # Remember, the first character of IFS is used to create $*, # except with old shells: target_os=$* IFS=$ac_save_IFS case $target_os in *\ *) target_os=`echo "$target_os" | sed 's/ /-/g'`;; esac # The aliases save the names the user supplied, while $host etc. # will get canonicalized. test -n "$target_alias" && test "$program_prefix$program_suffix$program_transform_name" = \ NONENONEs,x,x, && program_prefix=${target_alias}- am__api_version="1.9" # Find a good install program. We prefer a C program (faster), # so one script is as good as another. But avoid the broken or # incompatible versions: # SysV /etc/install, /usr/sbin/install # SunOS /usr/etc/install # IRIX /sbin/install # AIX /bin/install # AmigaOS /C/install, which installs bootblocks on floppy discs # AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag # AFS /usr/afsws/bin/install, which mishandles nonexistent args # SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" # OS/2's system install, which has a completely different semantic # ./install, which can be erroneously created by make from ./install.sh. { echo "$as_me:$LINENO: checking for a BSD-compatible install" >&5 echo $ECHO_N "checking for a BSD-compatible install... $ECHO_C" >&6; } if test -z "$INSTALL"; then if test "${ac_cv_path_install+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. # Account for people who put trailing slashes in PATH elements. case $as_dir/ in ./ | .// | /cC/* | \ /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ ?:\\/os2\\/install\\/* | ?:\\/OS2\\/INSTALL\\/* | \ /usr/ucb/* ) ;; *) # OSF1 and SCO ODT 3.0 have their own names for install. # Don't use installbsd from OSF since it installs stuff as root # by default. for ac_prog in ginstall scoinst install; do for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_prog$ac_exec_ext" && $as_test_x "$as_dir/$ac_prog$ac_exec_ext"; }; then if test $ac_prog = install && grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # AIX install. It has an incompatible calling convention. : elif test $ac_prog = install && grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # program-specific install script used by HP pwplus--don't use. : else ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c" break 3 fi fi done done ;; esac done IFS=$as_save_IFS fi if test "${ac_cv_path_install+set}" = set; then INSTALL=$ac_cv_path_install else # As a last resort, use the slow shell script. Don't cache a # value for INSTALL within a source directory, because that will # break other packages using the cache if that directory is # removed, or if the value is a relative name. INSTALL=$ac_install_sh fi fi { echo "$as_me:$LINENO: result: $INSTALL" >&5 echo "${ECHO_T}$INSTALL" >&6; } # Use test -z because SunOS4 sh mishandles braces in ${var-val}. # It thinks the first close brace ends the variable substitution. test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' { echo "$as_me:$LINENO: checking whether build environment is sane" >&5 echo $ECHO_N "checking whether build environment is sane... $ECHO_C" >&6; } # Just in case sleep 1 echo timestamp > conftest.file # Do `set' in a subshell so we don't clobber the current shell's # arguments. Must try -L first in case configure is actually a # symlink; some systems play weird games with the mod time of symlinks # (eg FreeBSD returns the mod time of the symlink's containing # directory). if ( set X `ls -Lt $srcdir/configure conftest.file 2> /dev/null` if test "$*" = "X"; then # -L didn't work. set X `ls -t $srcdir/configure conftest.file` fi rm -f conftest.file if test "$*" != "X $srcdir/configure conftest.file" \ && test "$*" != "X conftest.file $srcdir/configure"; then # If neither matched, then we have a broken ls. This can happen # if, for instance, CONFIG_SHELL is bash and it inherits a # broken ls alias from the environment. This has actually # happened. Such a system could not be considered "sane". { { echo "$as_me:$LINENO: error: ls -t appears to fail. Make sure there is not a broken alias in your environment" >&5 echo "$as_me: error: ls -t appears to fail. Make sure there is not a broken alias in your environment" >&2;} { (exit 1); exit 1; }; } fi test "$2" = conftest.file ) then # Ok. : else { { echo "$as_me:$LINENO: error: newly created file is older than distributed files! Check your system clock" >&5 echo "$as_me: error: newly created file is older than distributed files! Check your system clock" >&2;} { (exit 1); exit 1; }; } fi { echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6; } test "$program_prefix" != NONE && program_transform_name="s&^&$program_prefix&;$program_transform_name" # Use a double $ so make ignores it. test "$program_suffix" != NONE && program_transform_name="s&\$&$program_suffix&;$program_transform_name" # Double any \ or $. echo might interpret backslashes. # By default was `s,x,x', remove it if useless. cat <<\_ACEOF >conftest.sed s/[\\$]/&&/g;s/;s,x,x,$// _ACEOF program_transform_name=`echo $program_transform_name | sed -f conftest.sed` rm -f conftest.sed # expand $ac_aux_dir to an absolute path am_aux_dir=`cd $ac_aux_dir && pwd` test x"${MISSING+set}" = xset || MISSING="\${SHELL} $am_aux_dir/missing" # Use eval to expand $SHELL if eval "$MISSING --run true"; then am_missing_run="$MISSING --run " else am_missing_run= { echo "$as_me:$LINENO: WARNING: \`missing' script is too old or missing" >&5 echo "$as_me: WARNING: \`missing' script is too old or missing" >&2;} fi 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 for ac_prog in gawk mawk nawk awk do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; } if test "${ac_cv_prog_AWK+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test -n "$AWK"; then ac_cv_prog_AWK="$AWK" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_prog_AWK="$ac_prog" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi AWK=$ac_cv_prog_AWK if test -n "$AWK"; then { echo "$as_me:$LINENO: result: $AWK" >&5 echo "${ECHO_T}$AWK" >&6; } else { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } fi test -n "$AWK" && break done { echo "$as_me:$LINENO: checking whether ${MAKE-make} sets \$(MAKE)" >&5 echo $ECHO_N "checking whether ${MAKE-make} sets \$(MAKE)... $ECHO_C" >&6; } set x ${MAKE-make}; ac_make=`echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` if { as_var=ac_cv_prog_make_${ac_make}_set; eval "test \"\${$as_var+set}\" = set"; }; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.make <<\_ACEOF SHELL = /bin/sh all: @echo '@@@%%%=$(MAKE)=@@@%%%' _ACEOF # GNU make sometimes prints "make[1]: Entering...", which would confuse us. case `${MAKE-make} -f conftest.make 2>/dev/null` in *@@@%%%=?*=@@@%%%*) eval ac_cv_prog_make_${ac_make}_set=yes;; *) eval ac_cv_prog_make_${ac_make}_set=no;; esac rm -f conftest.make fi if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then { echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6; } SET_MAKE= else { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } SET_MAKE="MAKE=${MAKE-make}" fi rm -rf .tst 2>/dev/null mkdir .tst 2>/dev/null if test -d .tst; then am__leading_dot=. else am__leading_dot=_ fi rmdir .tst 2>/dev/null # test to see if srcdir already configured if test "`cd $srcdir && pwd`" != "`pwd`" && test -f $srcdir/config.status; then { { echo "$as_me:$LINENO: error: source directory already configured; run \"make distclean\" there first" >&5 echo "$as_me: error: source directory already configured; run \"make distclean\" there first" >&2;} { (exit 1); exit 1; }; } fi # test whether we have cygpath if test -z "$CYGPATH_W"; then if (cygpath --version) >/dev/null 2>/dev/null; then CYGPATH_W='cygpath -w' else CYGPATH_W=echo fi fi # Define the identity of the package. PACKAGE=nzbget VERSION=12.0 cat >>confdefs.h <<_ACEOF #define PACKAGE "$PACKAGE" _ACEOF cat >>confdefs.h <<_ACEOF #define VERSION "$VERSION" _ACEOF # Some tools Automake needs. ACLOCAL=${ACLOCAL-"${am_missing_run}aclocal-${am__api_version}"} AUTOCONF=${AUTOCONF-"${am_missing_run}autoconf"} AUTOMAKE=${AUTOMAKE-"${am_missing_run}automake-${am__api_version}"} AUTOHEADER=${AUTOHEADER-"${am_missing_run}autoheader"} MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"} install_sh=${install_sh-"$am_aux_dir/install-sh"} # Installed binaries are usually stripped using `strip' when the user # run `make install-strip'. However `strip' might not be the right # tool to use in cross-compilation environments, therefore Automake # will honor the `STRIP' environment variable to overrule this program. if test "$cross_compiling" != no; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args. set dummy ${ac_tool_prefix}strip; ac_word=$2 { echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; } if test "${ac_cv_prog_STRIP+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test -n "$STRIP"; then ac_cv_prog_STRIP="$STRIP" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_prog_STRIP="${ac_tool_prefix}strip" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi STRIP=$ac_cv_prog_STRIP if test -n "$STRIP"; then { echo "$as_me:$LINENO: result: $STRIP" >&5 echo "${ECHO_T}$STRIP" >&6; } else { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } fi fi if test -z "$ac_cv_prog_STRIP"; then ac_ct_STRIP=$STRIP # Extract the first word of "strip", so it can be a program name with args. set dummy strip; ac_word=$2 { echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; } if test "${ac_cv_prog_ac_ct_STRIP+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test -n "$ac_ct_STRIP"; then ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_prog_ac_ct_STRIP="strip" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP if test -n "$ac_ct_STRIP"; then { echo "$as_me:$LINENO: result: $ac_ct_STRIP" >&5 echo "${ECHO_T}$ac_ct_STRIP" >&6; } else { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } fi if test "x$ac_ct_STRIP" = x; then STRIP=":" else case $cross_compiling:$ac_tool_warned in yes:) { echo "$as_me:$LINENO: WARNING: In the future, Autoconf will not detect cross-tools whose name does not start with the host triplet. If you think this configuration is useful to you, please write to autoconf@gnu.org." >&5 echo "$as_me: WARNING: In the future, Autoconf will not detect cross-tools whose name does not start with the host triplet. If you think this configuration is useful to you, please write to autoconf@gnu.org." >&2;} ac_tool_warned=yes ;; esac STRIP=$ac_ct_STRIP fi else STRIP="$ac_cv_prog_STRIP" fi fi INSTALL_STRIP_PROGRAM="\${SHELL} \$(install_sh) -c -s" # We need awk for the "check" target. The system "awk" is bad on # some platforms. # Always define AMTAR for backward compatibility. AMTAR=${AMTAR-"${am_missing_run}tar"} am__tar='${AMTAR} chof - "$$tardir"'; am__untar='${AMTAR} xf -' ac_config_headers="$ac_config_headers config.h" if test "$LIBPREF" = ""; then LIBPREF="/usr" fi ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu if test -z "$CXX"; then if test -n "$CCC"; then CXX=$CCC else if test -n "$ac_tool_prefix"; then for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; } if test "${ac_cv_prog_CXX+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test -n "$CXX"; then ac_cv_prog_CXX="$CXX" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_prog_CXX="$ac_tool_prefix$ac_prog" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CXX=$ac_cv_prog_CXX if test -n "$CXX"; then { echo "$as_me:$LINENO: result: $CXX" >&5 echo "${ECHO_T}$CXX" >&6; } else { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } fi test -n "$CXX" && break done fi if test -z "$CXX"; then ac_ct_CXX=$CXX for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; } if test "${ac_cv_prog_ac_ct_CXX+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test -n "$ac_ct_CXX"; then ac_cv_prog_ac_ct_CXX="$ac_ct_CXX" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_prog_ac_ct_CXX="$ac_prog" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_CXX=$ac_cv_prog_ac_ct_CXX if test -n "$ac_ct_CXX"; then { echo "$as_me:$LINENO: result: $ac_ct_CXX" >&5 echo "${ECHO_T}$ac_ct_CXX" >&6; } else { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } fi test -n "$ac_ct_CXX" && break done if test "x$ac_ct_CXX" = x; then CXX="g++" else case $cross_compiling:$ac_tool_warned in yes:) { echo "$as_me:$LINENO: WARNING: In the future, Autoconf will not detect cross-tools whose name does not start with the host triplet. If you think this configuration is useful to you, please write to autoconf@gnu.org." >&5 echo "$as_me: WARNING: In the future, Autoconf will not detect cross-tools whose name does not start with the host triplet. If you think this configuration is useful to you, please write to autoconf@gnu.org." >&2;} ac_tool_warned=yes ;; esac CXX=$ac_ct_CXX fi fi fi fi # Provide some information about the compiler. echo "$as_me:$LINENO: checking for C++ compiler version" >&5 ac_compiler=`set X $ac_compile; echo $2` { (ac_try="$ac_compiler --version >&5" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compiler --version >&5") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } { (ac_try="$ac_compiler -v >&5" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compiler -v >&5") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } { (ac_try="$ac_compiler -V >&5" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compiler -V >&5") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ int main () { ; return 0; } _ACEOF ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files a.out a.exe b.out" # Try to create an executable without -o first, disregard a.out. # It will help us diagnose broken compilers, and finding out an intuition # of exeext. { echo "$as_me:$LINENO: checking for C++ compiler default output file name" >&5 echo $ECHO_N "checking for C++ compiler default output file name... $ECHO_C" >&6; } ac_link_default=`echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` # # List of possible output files, starting from the most likely. # The algorithm is not robust to junk in `.', hence go to wildcards (a.*) # only as a last resort. b.out is created by i960 compilers. ac_files='a_out.exe a.exe conftest.exe a.out conftest a.* conftest.* b.out' # # The IRIX 6 linker writes into existing files which may not be # executable, retaining their permissions. Remove them first so a # subsequent execution test works. ac_rmfiles= for ac_file in $ac_files do case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.o | *.obj ) ;; * ) ac_rmfiles="$ac_rmfiles $ac_file";; esac done rm -f $ac_rmfiles if { (ac_try="$ac_link_default" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link_default") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; then # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. # So ignore a value of `no', otherwise this would lead to `EXEEXT = no' # in a Makefile. We should not override ac_cv_exeext if it was cached, # so that the user can short-circuit this test for compilers unknown to # Autoconf. for ac_file in $ac_files '' do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.o | *.obj ) ;; [ab].out ) # We found the default executable, but exeext='' is most # certainly right. break;; *.* ) if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no; then :; else ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` fi # We set ac_cv_exeext here because the later test for it is not # safe: cross compilers may not add the suffix if given an `-o' # argument, so we may need to know it at that point already. # Even if this section looks crufty: it has the advantage of # actually working. break;; * ) break;; esac done test "$ac_cv_exeext" = no && ac_cv_exeext= else ac_file='' fi { echo "$as_me:$LINENO: result: $ac_file" >&5 echo "${ECHO_T}$ac_file" >&6; } if test -z "$ac_file"; then echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { echo "$as_me:$LINENO: error: C++ compiler cannot create executables See \`config.log' for more details." >&5 echo "$as_me: error: C++ compiler cannot create executables See \`config.log' for more details." >&2;} { (exit 77); exit 77; }; } fi ac_exeext=$ac_cv_exeext # Check that the compiler produces executables we can run. If not, either # the compiler is broken, or we cross compile. { echo "$as_me:$LINENO: checking whether the C++ compiler works" >&5 echo $ECHO_N "checking whether the C++ compiler works... $ECHO_C" >&6; } # FIXME: These cross compiler hacks should be removed for Autoconf 3.0 # If not cross compiling, check that we can run a simple program. if test "$cross_compiling" != yes; then if { ac_try='./$ac_file' { (case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_try") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then cross_compiling=no else if test "$cross_compiling" = maybe; then cross_compiling=yes else { { echo "$as_me:$LINENO: error: cannot run C++ compiled programs. If you meant to cross compile, use \`--host'. See \`config.log' for more details." >&5 echo "$as_me: error: cannot run C++ compiled programs. If you meant to cross compile, use \`--host'. See \`config.log' for more details." >&2;} { (exit 1); exit 1; }; } fi fi fi { echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6; } rm -f a.out a.exe conftest$ac_cv_exeext b.out ac_clean_files=$ac_clean_files_save # Check that the compiler produces executables we can run. If not, either # the compiler is broken, or we cross compile. { echo "$as_me:$LINENO: checking whether we are cross compiling" >&5 echo $ECHO_N "checking whether we are cross compiling... $ECHO_C" >&6; } { echo "$as_me:$LINENO: result: $cross_compiling" >&5 echo "${ECHO_T}$cross_compiling" >&6; } { echo "$as_me:$LINENO: checking for suffix of executables" >&5 echo $ECHO_N "checking for suffix of executables... $ECHO_C" >&6; } if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; then # If both `conftest.exe' and `conftest' are `present' (well, observable) # catch `conftest.exe'. For instance with Cygwin, `ls conftest' will # work properly (i.e., refer to `conftest.exe'), while it won't with # `rm'. for ac_file in conftest.exe conftest conftest.*; do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.o | *.obj ) ;; *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` break;; * ) break;; esac done else { { echo "$as_me:$LINENO: error: cannot compute suffix of executables: cannot compile and link See \`config.log' for more details." >&5 echo "$as_me: error: cannot compute suffix of executables: cannot compile and link See \`config.log' for more details." >&2;} { (exit 1); exit 1; }; } fi rm -f conftest$ac_cv_exeext { echo "$as_me:$LINENO: result: $ac_cv_exeext" >&5 echo "${ECHO_T}$ac_cv_exeext" >&6; } rm -f conftest.$ac_ext EXEEXT=$ac_cv_exeext ac_exeext=$EXEEXT { echo "$as_me:$LINENO: checking for suffix of object files" >&5 echo $ECHO_N "checking for suffix of object files... $ECHO_C" >&6; } if test "${ac_cv_objext+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ int main () { ; return 0; } _ACEOF rm -f conftest.o conftest.obj if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; then for ac_file in conftest.o conftest.obj conftest.*; do test -f "$ac_file" || continue; case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf ) ;; *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` break;; esac done else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { echo "$as_me:$LINENO: error: cannot compute suffix of object files: cannot compile See \`config.log' for more details." >&5 echo "$as_me: error: cannot compute suffix of object files: cannot compile See \`config.log' for more details." >&2;} { (exit 1); exit 1; }; } fi rm -f conftest.$ac_cv_objext conftest.$ac_ext fi { echo "$as_me:$LINENO: result: $ac_cv_objext" >&5 echo "${ECHO_T}$ac_cv_objext" >&6; } OBJEXT=$ac_cv_objext ac_objext=$OBJEXT { echo "$as_me:$LINENO: checking whether we are using the GNU C++ compiler" >&5 echo $ECHO_N "checking whether we are using the GNU C++ compiler... $ECHO_C" >&6; } if test "${ac_cv_cxx_compiler_gnu+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ int main () { #ifndef __GNUC__ choke me #endif ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then ac_compiler_gnu=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_compiler_gnu=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext ac_cv_cxx_compiler_gnu=$ac_compiler_gnu fi { echo "$as_me:$LINENO: result: $ac_cv_cxx_compiler_gnu" >&5 echo "${ECHO_T}$ac_cv_cxx_compiler_gnu" >&6; } GXX=`test $ac_compiler_gnu = yes && echo yes` ac_test_CXXFLAGS=${CXXFLAGS+set} ac_save_CXXFLAGS=$CXXFLAGS { echo "$as_me:$LINENO: checking whether $CXX accepts -g" >&5 echo $ECHO_N "checking whether $CXX accepts -g... $ECHO_C" >&6; } if test "${ac_cv_prog_cxx_g+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_save_cxx_werror_flag=$ac_cxx_werror_flag ac_cxx_werror_flag=yes ac_cv_prog_cxx_g=no CXXFLAGS="-g" cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ int main () { ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then ac_cv_prog_cxx_g=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 CXXFLAGS="" cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ int main () { ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then : else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_cxx_werror_flag=$ac_save_cxx_werror_flag CXXFLAGS="-g" cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ int main () { ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then ac_cv_prog_cxx_g=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext ac_cxx_werror_flag=$ac_save_cxx_werror_flag fi { echo "$as_me:$LINENO: result: $ac_cv_prog_cxx_g" >&5 echo "${ECHO_T}$ac_cv_prog_cxx_g" >&6; } if test "$ac_test_CXXFLAGS" = set; then CXXFLAGS=$ac_save_CXXFLAGS elif test $ac_cv_prog_cxx_g = yes; then if test "$GXX" = yes; then CXXFLAGS="-g -O2" else CXXFLAGS="-g" fi else if test "$GXX" = yes; then CXXFLAGS="-O2" else CXXFLAGS= fi fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu DEPDIR="${am__leading_dot}deps" ac_config_commands="$ac_config_commands depfiles" am_make=${MAKE-make} cat > confinc << 'END' am__doit: @echo done .PHONY: am__doit END # If we don't find an include directive, just comment out the code. { echo "$as_me:$LINENO: checking for style of include used by $am_make" >&5 echo $ECHO_N "checking for style of include used by $am_make... $ECHO_C" >&6; } am__include="#" am__quote= _am_result=none # First try GNU make style include. echo "include confinc" > confmf # We grep out `Entering directory' and `Leaving directory' # messages which can occur if `w' ends up in MAKEFLAGS. # In particular we don't look at `^make:' because GNU make might # be invoked under some other name (usually "gmake"), in which # case it prints its new name instead of `make'. if test "`$am_make -s -f confmf 2> /dev/null | grep -v 'ing directory'`" = "done"; then am__include=include am__quote= _am_result=GNU fi # Now try BSD make style include. if test "$am__include" = "#"; then echo '.include "confinc"' > confmf if test "`$am_make -s -f confmf 2> /dev/null`" = "done"; then am__include=.include am__quote="\"" _am_result=BSD fi fi { echo "$as_me:$LINENO: result: $_am_result" >&5 echo "${ECHO_T}$_am_result" >&6; } rm -f confinc confmf # Check whether --enable-dependency-tracking was given. if test "${enable_dependency_tracking+set}" = set; then enableval=$enable_dependency_tracking; fi if test "x$enable_dependency_tracking" != xno; then am_depcomp="$ac_aux_dir/depcomp" AMDEPBACKSLASH='\' fi if test "x$enable_dependency_tracking" != xno; then AMDEP_TRUE= AMDEP_FALSE='#' else AMDEP_TRUE='#' AMDEP_FALSE= fi depcc="$CXX" am_compiler_list= { echo "$as_me:$LINENO: checking dependency style of $depcc" >&5 echo $ECHO_N "checking dependency style of $depcc... $ECHO_C" >&6; } if test "${am_cv_CXX_dependencies_compiler_type+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then # We make a subdir and do the tests there. Otherwise we can end up # making bogus files that we don't know about and never remove. For # instance it was reported that on HP-UX the gcc test will end up # making a dummy file named `D' -- because `-MD' means `put the output # in D'. mkdir conftest.dir # Copy depcomp to subdir because otherwise we won't find it if we're # using a relative directory. cp "$am_depcomp" conftest.dir cd conftest.dir # We will build objects and dependencies in a subdirectory because # it helps to detect inapplicable dependency modes. For instance # both Tru64's cc and ICC support -MD to output dependencies as a # side effect of compilation, but ICC will put the dependencies in # the current directory while Tru64 will put them in the object # directory. mkdir sub am_cv_CXX_dependencies_compiler_type=none if test "$am_compiler_list" = ""; then am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp` fi for depmode in $am_compiler_list; do # Setup a source with many dependencies, because some compilers # like to wrap large dependency lists on column 80 (with \), and # we should not choose a depcomp mode which is confused by this. # # We need to recreate these files for each test, as the compiler may # overwrite some of them when testing with obscure command lines. # This happens at least with the AIX C compiler. : > sub/conftest.c for i in 1 2 3 4 5 6; do echo '#include "conftst'$i'.h"' >> sub/conftest.c # Using `: > sub/conftst$i.h' creates only sub/conftst1.h with # Solaris 8's {/usr,}/bin/sh. touch sub/conftst$i.h done echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf case $depmode in nosideeffect) # after this tag, mechanisms are not by side-effect, so they'll # only be used when explicitly requested if test "x$enable_dependency_tracking" = xyes; then continue else break fi ;; none) break ;; esac # We check with `-c' and `-o' for the sake of the "dashmstdout" # mode. It turns out that the SunPro C++ compiler does not properly # handle `-M -o', and we need to detect this. if depmode=$depmode \ source=sub/conftest.c object=sub/conftest.${OBJEXT-o} \ 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/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 # icc doesn't choke on unknown options, it will just issue warnings # or remarks (even with -Werror). So we grep stderr for any message # that says an option was ignored or not supported. # When given -MP, icc 7.0 and 7.1 complain thusly: # icc: Command line warning: ignoring option '-M'; no argument required # The diagnosis changed in icc 8.0: # icc: Command line remark: option '-MP' not supported if (grep 'ignoring option' conftest.err || grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else am_cv_CXX_dependencies_compiler_type=$depmode break fi fi done cd .. rm -rf conftest.dir else am_cv_CXX_dependencies_compiler_type=none fi fi { echo "$as_me:$LINENO: result: $am_cv_CXX_dependencies_compiler_type" >&5 echo "${ECHO_T}$am_cv_CXX_dependencies_compiler_type" >&6; } CXXDEPMODE=depmode=$am_cv_CXX_dependencies_compiler_type if test "x$enable_dependency_tracking" != xno \ && test "$am_cv_CXX_dependencies_compiler_type" = gcc3; then am__fastdepCXX_TRUE= am__fastdepCXX_FALSE='#' else am__fastdepCXX_TRUE='#' am__fastdepCXX_FALSE= fi # Extract the first word of "tar", so it can be a program name with args. set dummy tar; ac_word=$2 { echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; } if test "${ac_cv_path_TAR+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else case $TAR in [\\/]* | ?:[\\/]*) ac_cv_path_TAR="$TAR" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_path_TAR="$as_dir/$ac_word$ac_exec_ext" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_TAR" && ac_cv_path_TAR="$FALSE" ;; esac fi TAR=$ac_cv_path_TAR if test -n "$TAR"; then { echo "$as_me:$LINENO: result: $TAR" >&5 echo "${ECHO_T}$TAR" >&6; } else { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } fi # Extract the first word of "make", so it can be a program name with args. set dummy make; ac_word=$2 { echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; } if test "${ac_cv_path_MAKE+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else case $MAKE in [\\/]* | ?:[\\/]*) ac_cv_path_MAKE="$MAKE" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_path_MAKE="$as_dir/$ac_word$ac_exec_ext" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_MAKE" && ac_cv_path_MAKE="$FALSE" ;; esac fi MAKE=$ac_cv_path_MAKE if test -n "$MAKE"; then { echo "$as_me:$LINENO: result: $MAKE" >&5 echo "${ECHO_T}$MAKE" >&6; } else { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } fi # Find a good install program. We prefer a C program (faster), # so one script is as good as another. But avoid the broken or # incompatible versions: # SysV /etc/install, /usr/sbin/install # SunOS /usr/etc/install # IRIX /sbin/install # AIX /bin/install # AmigaOS /C/install, which installs bootblocks on floppy discs # AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag # AFS /usr/afsws/bin/install, which mishandles nonexistent args # SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" # OS/2's system install, which has a completely different semantic # ./install, which can be erroneously created by make from ./install.sh. { echo "$as_me:$LINENO: checking for a BSD-compatible install" >&5 echo $ECHO_N "checking for a BSD-compatible install... $ECHO_C" >&6; } if test -z "$INSTALL"; then if test "${ac_cv_path_install+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. # Account for people who put trailing slashes in PATH elements. case $as_dir/ in ./ | .// | /cC/* | \ /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ ?:\\/os2\\/install\\/* | ?:\\/OS2\\/INSTALL\\/* | \ /usr/ucb/* ) ;; *) # OSF1 and SCO ODT 3.0 have their own names for install. # Don't use installbsd from OSF since it installs stuff as root # by default. for ac_prog in ginstall scoinst install; do for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_prog$ac_exec_ext" && $as_test_x "$as_dir/$ac_prog$ac_exec_ext"; }; then if test $ac_prog = install && grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # AIX install. It has an incompatible calling convention. : elif test $ac_prog = install && grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # program-specific install script used by HP pwplus--don't use. : else ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c" break 3 fi fi done done ;; esac done IFS=$as_save_IFS fi if test "${ac_cv_path_install+set}" = set; then INSTALL=$ac_cv_path_install else # As a last resort, use the slow shell script. Don't cache a # value for INSTALL within a source directory, because that will # break other packages using the cache if that directory is # removed, or if the value is a relative name. INSTALL=$ac_install_sh fi fi { echo "$as_me:$LINENO: result: $INSTALL" >&5 echo "${ECHO_T}$INSTALL" >&6; } # Use test -z because SunOS4 sh mishandles braces in ${var-val}. # It thinks the first close brace ends the variable substitution. test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu { echo "$as_me:$LINENO: checking how to run the C++ preprocessor" >&5 echo $ECHO_N "checking how to run the C++ preprocessor... $ECHO_C" >&6; } if test -z "$CXXCPP"; then if test "${ac_cv_prog_CXXCPP+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else # Double quotes because CXXCPP needs to be expanded for CXXCPP in "$CXX -E" "/lib/cpp" do ac_preproc_ok=false for ac_cxx_preproc_warn_flag in '' yes do # Use a header file that comes with gcc, so configuring glibc # with a fresh cross-compiler works. # Prefer to if __STDC__ is defined, since # exists even on freestanding compilers. # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. "Syntax error" is here to catch this case. cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #ifdef __STDC__ # include #else # include #endif Syntax error _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then : else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 # Broken: fails on valid input. continue fi rm -f conftest.err conftest.$ac_ext # OK, works on sane cases. Now check whether nonexistent headers # can be detected and how. cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then # Broken: success on invalid input. continue else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 # Passes both tests. ac_preproc_ok=: break fi rm -f conftest.err conftest.$ac_ext done # Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.err conftest.$ac_ext if $ac_preproc_ok; then break fi done ac_cv_prog_CXXCPP=$CXXCPP fi CXXCPP=$ac_cv_prog_CXXCPP else ac_cv_prog_CXXCPP=$CXXCPP fi { echo "$as_me:$LINENO: result: $CXXCPP" >&5 echo "${ECHO_T}$CXXCPP" >&6; } ac_preproc_ok=false for ac_cxx_preproc_warn_flag in '' yes do # Use a header file that comes with gcc, so configuring glibc # with a fresh cross-compiler works. # Prefer to if __STDC__ is defined, since # exists even on freestanding compilers. # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. "Syntax error" is here to catch this case. cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #ifdef __STDC__ # include #else # include #endif Syntax error _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then : else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 # Broken: fails on valid input. continue fi rm -f conftest.err conftest.$ac_ext # OK, works on sane cases. Now check whether nonexistent headers # can be detected and how. cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then # Broken: success on invalid input. continue else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 # Passes both tests. ac_preproc_ok=: break fi rm -f conftest.err conftest.$ac_ext done # Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.err conftest.$ac_ext if $ac_preproc_ok; then : else { { echo "$as_me:$LINENO: error: C++ preprocessor \"$CXXCPP\" fails sanity check See \`config.log' for more details." >&5 echo "$as_me: error: C++ preprocessor \"$CXXCPP\" fails sanity check See \`config.log' for more details." >&2;} { (exit 1); exit 1; }; } fi ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu { echo "$as_me:$LINENO: checking for grep that handles long lines and -e" >&5 echo $ECHO_N "checking for grep that handles long lines and -e... $ECHO_C" >&6; } if test "${ac_cv_path_GREP+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else # Extract the first word of "grep ggrep" to use in msg output if test -z "$GREP"; then set dummy grep ggrep; ac_prog_name=$2 if test "${ac_cv_path_GREP+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_path_GREP_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_prog in grep ggrep; do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext" { test -f "$ac_path_GREP" && $as_test_x "$ac_path_GREP"; } || continue # Check for GNU ac_path_GREP and select it if it is found. # Check for GNU $ac_path_GREP case `"$ac_path_GREP" --version 2>&1` in *GNU*) ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; *) ac_count=0 echo $ECHO_N "0123456789$ECHO_C" >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" echo 'GREP' >> "conftest.nl" "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break ac_count=`expr $ac_count + 1` if test $ac_count -gt ${ac_path_GREP_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_GREP_found && break 3 done done done IFS=$as_save_IFS fi GREP="$ac_cv_path_GREP" if test -z "$GREP"; then { { echo "$as_me:$LINENO: error: no acceptable $ac_prog_name could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" >&5 echo "$as_me: error: no acceptable $ac_prog_name could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" >&2;} { (exit 1); exit 1; }; } fi else ac_cv_path_GREP=$GREP fi fi { echo "$as_me:$LINENO: result: $ac_cv_path_GREP" >&5 echo "${ECHO_T}$ac_cv_path_GREP" >&6; } GREP="$ac_cv_path_GREP" { echo "$as_me:$LINENO: checking for egrep" >&5 echo $ECHO_N "checking for egrep... $ECHO_C" >&6; } if test "${ac_cv_path_EGREP+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 then ac_cv_path_EGREP="$GREP -E" else # Extract the first word of "egrep" to use in msg output if test -z "$EGREP"; then set dummy egrep; ac_prog_name=$2 if test "${ac_cv_path_EGREP+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_path_EGREP_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_prog in egrep; do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext" { test -f "$ac_path_EGREP" && $as_test_x "$ac_path_EGREP"; } || continue # Check for GNU ac_path_EGREP and select it if it is found. # Check for GNU $ac_path_EGREP case `"$ac_path_EGREP" --version 2>&1` in *GNU*) ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; *) ac_count=0 echo $ECHO_N "0123456789$ECHO_C" >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" echo 'EGREP' >> "conftest.nl" "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break ac_count=`expr $ac_count + 1` if test $ac_count -gt ${ac_path_EGREP_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_EGREP_found && break 3 done done done IFS=$as_save_IFS fi EGREP="$ac_cv_path_EGREP" if test -z "$EGREP"; then { { echo "$as_me:$LINENO: error: no acceptable $ac_prog_name could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" >&5 echo "$as_me: error: no acceptable $ac_prog_name could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" >&2;} { (exit 1); exit 1; }; } fi else ac_cv_path_EGREP=$EGREP fi fi fi { echo "$as_me:$LINENO: result: $ac_cv_path_EGREP" >&5 echo "${ECHO_T}$ac_cv_path_EGREP" >&6; } EGREP="$ac_cv_path_EGREP" { echo "$as_me:$LINENO: checking for ANSI C header files" >&5 echo $ECHO_N "checking for ANSI C header files... $ECHO_C" >&6; } if test "${ac_cv_header_stdc+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include #include #include int main () { ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then ac_cv_header_stdc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_cv_header_stdc=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test $ac_cv_header_stdc = yes; then # SunOS 4.x string.h does not declare mem*, contrary to ANSI. cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP "memchr" >/dev/null 2>&1; then : else ac_cv_header_stdc=no fi rm -f conftest* fi if test $ac_cv_header_stdc = yes; then # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP "free" >/dev/null 2>&1; then : else ac_cv_header_stdc=no fi rm -f conftest* fi if test $ac_cv_header_stdc = yes; then # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. if test "$cross_compiling" = yes; then : else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include #if ((' ' & 0x0FF) == 0x020) # define ISLOWER(c) ('a' <= (c) && (c) <= 'z') # define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) #else # define ISLOWER(c) \ (('a' <= (c) && (c) <= 'i') \ || ('j' <= (c) && (c) <= 'r') \ || ('s' <= (c) && (c) <= 'z')) # define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) #endif #define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) int main () { int i; for (i = 0; i < 256; i++) if (XOR (islower (i), ISLOWER (i)) || toupper (i) != TOUPPER (i)) return 2; return 0; } _ACEOF rm -f conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { ac_try='./conftest$ac_exeext' { (case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_try") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then : else echo "$as_me: program exited with status $ac_status" >&5 echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ( exit $ac_status ) ac_cv_header_stdc=no fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext conftest.$ac_objext conftest.$ac_ext fi fi fi { echo "$as_me:$LINENO: result: $ac_cv_header_stdc" >&5 echo "${ECHO_T}$ac_cv_header_stdc" >&6; } if test $ac_cv_header_stdc = yes; then cat >>confdefs.h <<\_ACEOF #define STDC_HEADERS 1 _ACEOF fi # On IRIX 5.3, sys/types and inttypes.h are conflicting. for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \ inttypes.h stdint.h unistd.h do as_ac_Header=`echo "ac_cv_header_$ac_header" | $as_tr_sh` { echo "$as_me:$LINENO: checking for $ac_header" >&5 echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6; } if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include <$ac_header> _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then eval "$as_ac_Header=yes" else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 eval "$as_ac_Header=no" fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi ac_res=`eval echo '${'$as_ac_Header'}'` { echo "$as_me:$LINENO: result: $ac_res" >&5 echo "${ECHO_T}$ac_res" >&6; } if test `eval echo '${'$as_ac_Header'}'` = yes; then cat >>confdefs.h <<_ACEOF #define `echo "HAVE_$ac_header" | $as_tr_cpp` 1 _ACEOF fi done for ac_header in sys/prctl.h do as_ac_Header=`echo "ac_cv_header_$ac_header" | $as_tr_sh` if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then { echo "$as_me:$LINENO: checking for $ac_header" >&5 echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6; } if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then echo $ECHO_N "(cached) $ECHO_C" >&6 fi ac_res=`eval echo '${'$as_ac_Header'}'` { echo "$as_me:$LINENO: result: $ac_res" >&5 echo "${ECHO_T}$ac_res" >&6; } else # Is the header compilable? { echo "$as_me:$LINENO: checking $ac_header usability" >&5 echo $ECHO_N "checking $ac_header usability... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include <$ac_header> _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then ac_header_compiler=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_compiler=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 echo "${ECHO_T}$ac_header_compiler" >&6; } # Is the header present? { echo "$as_me:$LINENO: checking $ac_header presence" >&5 echo $ECHO_N "checking $ac_header presence... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include <$ac_header> _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then ac_header_preproc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_preproc=no fi rm -f conftest.err conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 echo "${ECHO_T}$ac_header_preproc" >&6; } # So? What about this header? case $ac_header_compiler:$ac_header_preproc:$ac_cxx_preproc_warn_flag in yes:no: ) { echo "$as_me:$LINENO: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&5 echo "$as_me: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the compiler's result" >&5 echo "$as_me: WARNING: $ac_header: proceeding with the compiler's result" >&2;} ac_header_preproc=yes ;; no:yes:* ) { echo "$as_me:$LINENO: WARNING: $ac_header: present but cannot be compiled" >&5 echo "$as_me: WARNING: $ac_header: present but cannot be compiled" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: check for missing prerequisite headers?" >&5 echo "$as_me: WARNING: $ac_header: check for missing prerequisite headers?" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: see the Autoconf documentation" >&5 echo "$as_me: WARNING: $ac_header: see the Autoconf documentation" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: section \"Present But Cannot Be Compiled\"" >&5 echo "$as_me: WARNING: $ac_header: section \"Present But Cannot Be Compiled\"" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: $ac_header: proceeding with the preprocessor's result" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: in the future, the compiler will take precedence" >&5 echo "$as_me: WARNING: $ac_header: in the future, the compiler will take precedence" >&2;} ( cat <<\_ASBOX ## ------------------------------------------- ## ## Report this to hugbug@users.sourceforge.net ## ## ------------------------------------------- ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac { echo "$as_me:$LINENO: checking for $ac_header" >&5 echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6; } if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then echo $ECHO_N "(cached) $ECHO_C" >&6 else eval "$as_ac_Header=\$ac_header_preproc" fi ac_res=`eval echo '${'$as_ac_Header'}'` { echo "$as_me:$LINENO: result: $ac_res" >&5 echo "${ECHO_T}$ac_res" >&6; } fi if test `eval echo '${'$as_ac_Header'}'` = yes; then cat >>confdefs.h <<_ACEOF #define `echo "HAVE_$ac_header" | $as_tr_cpp` 1 _ACEOF fi done for ac_header in regex.h do as_ac_Header=`echo "ac_cv_header_$ac_header" | $as_tr_sh` if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then { echo "$as_me:$LINENO: checking for $ac_header" >&5 echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6; } if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then echo $ECHO_N "(cached) $ECHO_C" >&6 fi ac_res=`eval echo '${'$as_ac_Header'}'` { echo "$as_me:$LINENO: result: $ac_res" >&5 echo "${ECHO_T}$ac_res" >&6; } else # Is the header compilable? { echo "$as_me:$LINENO: checking $ac_header usability" >&5 echo $ECHO_N "checking $ac_header usability... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include <$ac_header> _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then ac_header_compiler=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_compiler=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 echo "${ECHO_T}$ac_header_compiler" >&6; } # Is the header present? { echo "$as_me:$LINENO: checking $ac_header presence" >&5 echo $ECHO_N "checking $ac_header presence... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include <$ac_header> _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then ac_header_preproc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_preproc=no fi rm -f conftest.err conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 echo "${ECHO_T}$ac_header_preproc" >&6; } # So? What about this header? case $ac_header_compiler:$ac_header_preproc:$ac_cxx_preproc_warn_flag in yes:no: ) { echo "$as_me:$LINENO: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&5 echo "$as_me: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the compiler's result" >&5 echo "$as_me: WARNING: $ac_header: proceeding with the compiler's result" >&2;} ac_header_preproc=yes ;; no:yes:* ) { echo "$as_me:$LINENO: WARNING: $ac_header: present but cannot be compiled" >&5 echo "$as_me: WARNING: $ac_header: present but cannot be compiled" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: check for missing prerequisite headers?" >&5 echo "$as_me: WARNING: $ac_header: check for missing prerequisite headers?" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: see the Autoconf documentation" >&5 echo "$as_me: WARNING: $ac_header: see the Autoconf documentation" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: section \"Present But Cannot Be Compiled\"" >&5 echo "$as_me: WARNING: $ac_header: section \"Present But Cannot Be Compiled\"" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: $ac_header: proceeding with the preprocessor's result" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: in the future, the compiler will take precedence" >&5 echo "$as_me: WARNING: $ac_header: in the future, the compiler will take precedence" >&2;} ( cat <<\_ASBOX ## ------------------------------------------- ## ## Report this to hugbug@users.sourceforge.net ## ## ------------------------------------------- ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac { echo "$as_me:$LINENO: checking for $ac_header" >&5 echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6; } if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then echo $ECHO_N "(cached) $ECHO_C" >&6 else eval "$as_ac_Header=\$ac_header_preproc" fi ac_res=`eval echo '${'$as_ac_Header'}'` { echo "$as_me:$LINENO: result: $ac_res" >&5 echo "${ECHO_T}$ac_res" >&6; } fi if test `eval echo '${'$as_ac_Header'}'` = yes; then cat >>confdefs.h <<_ACEOF #define `echo "HAVE_$ac_header" | $as_tr_cpp` 1 _ACEOF fi done { echo "$as_me:$LINENO: checking for library containing pthread_create" >&5 echo $ECHO_N "checking for library containing pthread_create... $ECHO_C" >&6; } if test "${ac_cv_search_pthread_create+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char pthread_create (); int main () { return pthread_create (); ; return 0; } _ACEOF for ac_lib in '' pthread; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_pthread_create=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_pthread_create+set}" = set; then break fi done if test "${ac_cv_search_pthread_create+set}" = set; then : else ac_cv_search_pthread_create=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_pthread_create" >&5 echo "${ECHO_T}$ac_cv_search_pthread_create" >&6; } ac_res=$ac_cv_search_pthread_create if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" fi { echo "$as_me:$LINENO: checking for library containing socket" >&5 echo $ECHO_N "checking for library containing socket... $ECHO_C" >&6; } if test "${ac_cv_search_socket+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char socket (); int main () { return socket (); ; return 0; } _ACEOF for ac_lib in '' socket; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_socket=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_socket+set}" = set; then break fi done if test "${ac_cv_search_socket+set}" = set; then : else ac_cv_search_socket=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_socket" >&5 echo "${ECHO_T}$ac_cv_search_socket" >&6; } ac_res=$ac_cv_search_socket if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" fi { echo "$as_me:$LINENO: checking for library containing inet_addr" >&5 echo $ECHO_N "checking for library containing inet_addr... $ECHO_C" >&6; } if test "${ac_cv_search_inet_addr+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char inet_addr (); int main () { return inet_addr (); ; return 0; } _ACEOF for ac_lib in '' nsl; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_inet_addr=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_inet_addr+set}" = set; then break fi done if test "${ac_cv_search_inet_addr+set}" = set; then : else ac_cv_search_inet_addr=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_inet_addr" >&5 echo "${ECHO_T}$ac_cv_search_inet_addr" >&6; } ac_res=$ac_cv_search_inet_addr if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" fi { echo "$as_me:$LINENO: checking for library containing hstrerror" >&5 echo $ECHO_N "checking for library containing hstrerror... $ECHO_C" >&6; } if test "${ac_cv_search_hstrerror+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char hstrerror (); int main () { return hstrerror (); ; return 0; } _ACEOF for ac_lib in '' resolv; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_hstrerror=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_hstrerror+set}" = set; then break fi done if test "${ac_cv_search_hstrerror+set}" = set; then : else ac_cv_search_hstrerror=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_hstrerror" >&5 echo "${ECHO_T}$ac_cv_search_hstrerror" >&6; } ac_res=$ac_cv_search_hstrerror if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" fi { echo "$as_me:$LINENO: checking for getopt_long" >&5 echo $ECHO_N "checking for getopt_long... $ECHO_C" >&6; } if test "${ac_cv_func_getopt_long+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Define getopt_long to an innocuous variant, in case declares getopt_long. For example, HP-UX 11i declares gettimeofday. */ #define getopt_long innocuous_getopt_long /* System header to define __stub macros and hopefully few prototypes, which can conflict with char getopt_long (); below. Prefer to if __STDC__ is defined, since exists even on freestanding compilers. */ #ifdef __STDC__ # include #else # include #endif #undef getopt_long /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char getopt_long (); /* The GNU C library defines this for functions which it implements to always fail with ENOSYS. Some functions are actually named something starting with __ and the normal name is an alias. */ #if defined __stub_getopt_long || defined __stub___getopt_long choke me #endif int main () { return getopt_long (); ; return 0; } _ACEOF rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_func_getopt_long=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_cv_func_getopt_long=no fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext conftest.$ac_ext fi { echo "$as_me:$LINENO: result: $ac_cv_func_getopt_long" >&5 echo "${ECHO_T}$ac_cv_func_getopt_long" >&6; } if test $ac_cv_func_getopt_long = yes; then cat >>confdefs.h <<\_ACEOF #define HAVE_GETOPT_LONG 1 _ACEOF fi # Check whether --enable-largefile was given. if test "${enable_largefile+set}" = set; then enableval=$enable_largefile; fi if test "$enable_largefile" != no; then { echo "$as_me:$LINENO: checking for special C compiler options needed for large files" >&5 echo $ECHO_N "checking for special C compiler options needed for large files... $ECHO_C" >&6; } if test "${ac_cv_sys_largefile_CC+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_cv_sys_largefile_CC=no if test "$GCC" != yes; then ac_save_CC=$CC while :; do # IRIX 6.2 and later do not support large files by default, # so use the C compiler's -n32 option if that helps. cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include /* Check that off_t can represent 2**63 - 1 correctly. We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ #define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; int main () { ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then break else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext CC="$CC -n32" rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then ac_cv_sys_largefile_CC=' -n32'; break else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext break done CC=$ac_save_CC rm -f conftest.$ac_ext fi fi { echo "$as_me:$LINENO: result: $ac_cv_sys_largefile_CC" >&5 echo "${ECHO_T}$ac_cv_sys_largefile_CC" >&6; } if test "$ac_cv_sys_largefile_CC" != no; then CC=$CC$ac_cv_sys_largefile_CC fi { echo "$as_me:$LINENO: checking for _FILE_OFFSET_BITS value needed for large files" >&5 echo $ECHO_N "checking for _FILE_OFFSET_BITS value needed for large files... $ECHO_C" >&6; } if test "${ac_cv_sys_file_offset_bits+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else while :; do cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include /* Check that off_t can represent 2**63 - 1 correctly. We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ #define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; int main () { ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then ac_cv_sys_file_offset_bits=no; break else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #define _FILE_OFFSET_BITS 64 #include /* Check that off_t can represent 2**63 - 1 correctly. We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ #define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; int main () { ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then ac_cv_sys_file_offset_bits=64; break else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext ac_cv_sys_file_offset_bits=unknown break done fi { echo "$as_me:$LINENO: result: $ac_cv_sys_file_offset_bits" >&5 echo "${ECHO_T}$ac_cv_sys_file_offset_bits" >&6; } case $ac_cv_sys_file_offset_bits in #( no | unknown) ;; *) cat >>confdefs.h <<_ACEOF #define _FILE_OFFSET_BITS $ac_cv_sys_file_offset_bits _ACEOF ;; esac rm -f conftest* if test $ac_cv_sys_file_offset_bits = unknown; then { echo "$as_me:$LINENO: checking for _LARGE_FILES value needed for large files" >&5 echo $ECHO_N "checking for _LARGE_FILES value needed for large files... $ECHO_C" >&6; } if test "${ac_cv_sys_large_files+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else while :; do cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include /* Check that off_t can represent 2**63 - 1 correctly. We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ #define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; int main () { ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then ac_cv_sys_large_files=no; break else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #define _LARGE_FILES 1 #include /* Check that off_t can represent 2**63 - 1 correctly. We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ #define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; int main () { ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then ac_cv_sys_large_files=1; break else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext ac_cv_sys_large_files=unknown break done fi { echo "$as_me:$LINENO: result: $ac_cv_sys_large_files" >&5 echo "${ECHO_T}$ac_cv_sys_large_files" >&6; } case $ac_cv_sys_large_files in #( no | unknown) ;; *) cat >>confdefs.h <<_ACEOF #define _LARGE_FILES $ac_cv_sys_large_files _ACEOF ;; esac rm -f conftest* fi fi { echo "$as_me:$LINENO: checking for ctime_r" >&5 echo $ECHO_N "checking for ctime_r... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include int main () { time_t clock; char buf[26]; ctime_r(&clock, buf, 26); ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then { echo "$as_me:$LINENO: result: yes, and it takes 3 arguments" >&5 echo "${ECHO_T}yes, and it takes 3 arguments" >&6; } FOUND="yes" cat >>confdefs.h <<\_ACEOF #define HAVE_CTIME_R_3 1 _ACEOF else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 FOUND="no" fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test "$FOUND" = "no"; then cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include int main () { time_t clock; char buf[26]; ctime_r(&clock, buf); ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then { echo "$as_me:$LINENO: result: yes, and it takes 2 arguments" >&5 echo "${ECHO_T}yes, and it takes 2 arguments" >&6; } FOUND="yes" cat >>confdefs.h <<\_ACEOF #define HAVE_CTIME_R_2 1 _ACEOF else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 FOUND="no" fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi if test "$FOUND" = "no"; then { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } { { echo "$as_me:$LINENO: error: \"function ctime_r not found\"" >&5 echo "$as_me: error: \"function ctime_r not found\"" >&2;} { (exit 1); exit 1; }; } fi { echo "$as_me:$LINENO: checking for getaddrinfo" >&5 echo $ECHO_N "checking for getaddrinfo... $ECHO_C" >&6; } if test "${ac_cv_func_getaddrinfo+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Define getaddrinfo to an innocuous variant, in case declares getaddrinfo. For example, HP-UX 11i declares gettimeofday. */ #define getaddrinfo innocuous_getaddrinfo /* System header to define __stub macros and hopefully few prototypes, which can conflict with char getaddrinfo (); below. Prefer to if __STDC__ is defined, since exists even on freestanding compilers. */ #ifdef __STDC__ # include #else # include #endif #undef getaddrinfo /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char getaddrinfo (); /* The GNU C library defines this for functions which it implements to always fail with ENOSYS. Some functions are actually named something starting with __ and the normal name is an alias. */ #if defined __stub_getaddrinfo || defined __stub___getaddrinfo choke me #endif int main () { return getaddrinfo (); ; return 0; } _ACEOF rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_func_getaddrinfo=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_cv_func_getaddrinfo=no fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext conftest.$ac_ext fi { echo "$as_me:$LINENO: result: $ac_cv_func_getaddrinfo" >&5 echo "${ECHO_T}$ac_cv_func_getaddrinfo" >&6; } if test $ac_cv_func_getaddrinfo = yes; then FOUND="yes" cat >>confdefs.h <<\_ACEOF #define HAVE_GETADDRINFO 1 _ACEOF { echo "$as_me:$LINENO: checking for library containing getaddrinfo" >&5 echo $ECHO_N "checking for library containing getaddrinfo... $ECHO_C" >&6; } if test "${ac_cv_search_getaddrinfo+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char getaddrinfo (); int main () { return getaddrinfo (); ; return 0; } _ACEOF for ac_lib in '' nsl; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_getaddrinfo=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_getaddrinfo+set}" = set; then break fi done if test "${ac_cv_search_getaddrinfo+set}" = set; then : else ac_cv_search_getaddrinfo=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_getaddrinfo" >&5 echo "${ECHO_T}$ac_cv_search_getaddrinfo" >&6; } ac_res=$ac_cv_search_getaddrinfo if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" fi else FOUND="no" fi if test "$FOUND" = "no"; then { echo "$as_me:$LINENO: checking for gethostbyname_r" >&5 echo $ECHO_N "checking for gethostbyname_r... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include int main () { char* szHost; struct hostent hinfobuf; char* strbuf; int h_errnop; struct hostent* hinfo = gethostbyname_r(szHost, &hinfobuf, strbuf, 1024, &h_errnop); ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then { echo "$as_me:$LINENO: result: yes, and it takes 5 arguments" >&5 echo "${ECHO_T}yes, and it takes 5 arguments" >&6; } FOUND="yes" cat >>confdefs.h <<\_ACEOF #define HAVE_GETHOSTBYNAME_R_5 1 _ACEOF else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 FOUND="no" fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test "$FOUND" = "no"; then cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include int main () { char* szHost; struct hostent* hinfo; struct hostent hinfobuf; char* strbuf; int h_errnop; int err = gethostbyname_r(szHost, &hinfobuf, strbuf, 1024, &hinfo, &h_errnop); ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then { echo "$as_me:$LINENO: result: yes, and it takes 6 arguments" >&5 echo "${ECHO_T}yes, and it takes 6 arguments" >&6; } FOUND="yes" cat >>confdefs.h <<\_ACEOF #define HAVE_GETHOSTBYNAME_R_6 1 _ACEOF else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 FOUND="no" fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi if test "$FOUND" = "no"; then cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include int main () { char* szHost; struct hostent hinfo; struct hostent_data hinfobuf; int err = gethostbyname_r(szHost, &hinfo, &hinfobuf); ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then { echo "$as_me:$LINENO: result: yes, and it takes 3 arguments" >&5 echo "${ECHO_T}yes, and it takes 3 arguments" >&6; } FOUND="yes" cat >>confdefs.h <<\_ACEOF #define HAVE_GETHOSTBYNAME_R_3 1 _ACEOF else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } FOUND="no" fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi if test "$FOUND" = "yes"; then cat >>confdefs.h <<\_ACEOF #define HAVE_GETHOSTBYNAME_R 1 _ACEOF { echo "$as_me:$LINENO: checking for library containing gethostbyname_r" >&5 echo $ECHO_N "checking for library containing gethostbyname_r... $ECHO_C" >&6; } if test "${ac_cv_search_gethostbyname_r+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char gethostbyname_r (); int main () { return gethostbyname_r (); ; return 0; } _ACEOF for ac_lib in '' nsl; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_gethostbyname_r=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_gethostbyname_r+set}" = set; then break fi done if test "${ac_cv_search_gethostbyname_r+set}" = set; then : else ac_cv_search_gethostbyname_r=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_gethostbyname_r" >&5 echo "${ECHO_T}$ac_cv_search_gethostbyname_r" >&6; } ac_res=$ac_cv_search_gethostbyname_r if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" fi fi fi { echo "$as_me:$LINENO: checking for pthread_spin_init" >&5 echo $ECHO_N "checking for pthread_spin_init... $ECHO_C" >&6; } if test "${ac_cv_func_pthread_spin_init+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Define pthread_spin_init to an innocuous variant, in case declares pthread_spin_init. For example, HP-UX 11i declares gettimeofday. */ #define pthread_spin_init innocuous_pthread_spin_init /* System header to define __stub macros and hopefully few prototypes, which can conflict with char pthread_spin_init (); below. Prefer to if __STDC__ is defined, since exists even on freestanding compilers. */ #ifdef __STDC__ # include #else # include #endif #undef pthread_spin_init /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char pthread_spin_init (); /* The GNU C library defines this for functions which it implements to always fail with ENOSYS. Some functions are actually named something starting with __ and the normal name is an alias. */ #if defined __stub_pthread_spin_init || defined __stub___pthread_spin_init choke me #endif int main () { return pthread_spin_init (); ; return 0; } _ACEOF rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_func_pthread_spin_init=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_cv_func_pthread_spin_init=no fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext conftest.$ac_ext fi { echo "$as_me:$LINENO: result: $ac_cv_func_pthread_spin_init" >&5 echo "${ECHO_T}$ac_cv_func_pthread_spin_init" >&6; } if test $ac_cv_func_pthread_spin_init = yes; then cat >>confdefs.h <<\_ACEOF #define HAVE_SPINLOCK 1 _ACEOF { echo "$as_me:$LINENO: checking for library containing pthread_spin_init" >&5 echo $ECHO_N "checking for library containing pthread_spin_init... $ECHO_C" >&6; } if test "${ac_cv_search_pthread_spin_init+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char pthread_spin_init (); int main () { return pthread_spin_init (); ; return 0; } _ACEOF for ac_lib in '' pthread; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_pthread_spin_init=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_pthread_spin_init+set}" = set; then break fi done if test "${ac_cv_search_pthread_spin_init+set}" = set; then : else ac_cv_search_pthread_spin_init=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_pthread_spin_init" >&5 echo "${ECHO_T}$ac_cv_search_pthread_spin_init" >&6; } ac_res=$ac_cv_search_pthread_spin_init if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" fi fi { echo "$as_me:$LINENO: checking for type of socket length (socklen_t)" >&5 echo $ECHO_N "checking for type of socket length (socklen_t)... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include #include int main () { (void)getsockopt (1, 1, 1, NULL, (socklen_t*)NULL) ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then { echo "$as_me:$LINENO: result: socklen_t" >&5 echo "${ECHO_T}socklen_t" >&6; } SOCKLEN_T=socklen_t else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include #include int main () { (void)getsockopt (1, 1, 1, NULL, (size_t*)NULL) ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then { echo "$as_me:$LINENO: result: size_t" >&5 echo "${ECHO_T}size_t" >&6; } SOCKLEN_T=size_t else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include #include int main () { (void)getsockopt (1, 1, 1, NULL, (int*)NULL) ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then { echo "$as_me:$LINENO: result: int" >&5 echo "${ECHO_T}int" >&6; } SOCKLEN_T=int else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { echo "$as_me:$LINENO: WARNING: could not determine" >&5 echo "$as_me: WARNING: could not determine" >&2;} SOCKLEN_T=int fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext cat >>confdefs.h <<_ACEOF #define SOCKLEN_T $SOCKLEN_T _ACEOF # Check whether --with-libxml2_includes was given. if test "${with_libxml2_includes+set}" = set; then withval=$with_libxml2_includes; CPPFLAGS="${CPPFLAGS} -I${withval}" INCVAL="yes" else INCVAL="no" fi # Check whether --with-libxml2_libraries was given. if test "${with_libxml2_libraries+set}" = set; then withval=$with_libxml2_libraries; LDFLAGS="${LDFLAGS} -L${withval}" LIBVAL="yes" else LIBVAL="no" fi if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args. set dummy ${ac_tool_prefix}pkg-config; ac_word=$2 { echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; } if test "${ac_cv_path_PKG_CONFIG+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else case $PKG_CONFIG in [\\/]* | ?:[\\/]*) ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi PKG_CONFIG=$ac_cv_path_PKG_CONFIG if test -n "$PKG_CONFIG"; then { echo "$as_me:$LINENO: result: $PKG_CONFIG" >&5 echo "${ECHO_T}$PKG_CONFIG" >&6; } else { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } fi fi if test -z "$ac_cv_path_PKG_CONFIG"; then ac_pt_PKG_CONFIG=$PKG_CONFIG # Extract the first word of "pkg-config", so it can be a program name with args. set dummy pkg-config; ac_word=$2 { echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; } if test "${ac_cv_path_ac_pt_PKG_CONFIG+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else case $ac_pt_PKG_CONFIG in [\\/]* | ?:[\\/]*) ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_path_ac_pt_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG if test -n "$ac_pt_PKG_CONFIG"; then { echo "$as_me:$LINENO: result: $ac_pt_PKG_CONFIG" >&5 echo "${ECHO_T}$ac_pt_PKG_CONFIG" >&6; } else { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } fi if test "x$ac_pt_PKG_CONFIG" = x; then PKG_CONFIG="" else case $cross_compiling:$ac_tool_warned in yes:) { echo "$as_me:$LINENO: WARNING: In the future, Autoconf will not detect cross-tools whose name does not start with the host triplet. If you think this configuration is useful to you, please write to autoconf@gnu.org." >&5 echo "$as_me: WARNING: In the future, Autoconf will not detect cross-tools whose name does not start with the host triplet. If you think this configuration is useful to you, please write to autoconf@gnu.org." >&2;} ac_tool_warned=yes ;; esac PKG_CONFIG=$ac_pt_PKG_CONFIG fi else PKG_CONFIG="$ac_cv_path_PKG_CONFIG" fi fi if test -n "$PKG_CONFIG"; then _pkg_min_version=0.9.0 { echo "$as_me:$LINENO: checking pkg-config is at least version $_pkg_min_version" >&5 echo $ECHO_N "checking pkg-config is at least version $_pkg_min_version... $ECHO_C" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then { echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6; } else { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } PKG_CONFIG="" fi fi pkg_failed=no { echo "$as_me:$LINENO: checking for libxml2" >&5 echo $ECHO_N "checking for libxml2... $ECHO_C" >&6; } if test -n "$PKG_CONFIG"; then if test -n "$libxml2_CFLAGS"; then pkg_cv_libxml2_CFLAGS="$libxml2_CFLAGS" else if test -n "$PKG_CONFIG" && \ { (echo "$as_me:$LINENO: \$PKG_CONFIG --exists --print-errors \"libxml-2.0\"") >&5 ($PKG_CONFIG --exists --print-errors "libxml-2.0") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; then pkg_cv_libxml2_CFLAGS=`$PKG_CONFIG --cflags "libxml-2.0" 2>/dev/null` else pkg_failed=yes fi fi else pkg_failed=untried fi if test -n "$PKG_CONFIG"; then if test -n "$libxml2_LIBS"; then pkg_cv_libxml2_LIBS="$libxml2_LIBS" else if test -n "$PKG_CONFIG" && \ { (echo "$as_me:$LINENO: \$PKG_CONFIG --exists --print-errors \"libxml-2.0\"") >&5 ($PKG_CONFIG --exists --print-errors "libxml-2.0") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; then pkg_cv_libxml2_LIBS=`$PKG_CONFIG --libs "libxml-2.0" 2>/dev/null` else pkg_failed=yes fi fi else pkg_failed=untried fi if test $pkg_failed = yes; then if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then libxml2_PKG_ERRORS=`$PKG_CONFIG --short-errors --errors-to-stdout --print-errors "libxml-2.0"` else libxml2_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "libxml-2.0"` fi # Put the nasty error message in config.log where it belongs echo "$libxml2_PKG_ERRORS" >&5 { { echo "$as_me:$LINENO: error: Package requirements (libxml-2.0) were not met: $libxml2_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables libxml2_CFLAGS and libxml2_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. " >&5 echo "$as_me: error: Package requirements (libxml-2.0) were not met: $libxml2_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables libxml2_CFLAGS and libxml2_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. " >&2;} { (exit 1); exit 1; }; } elif test $pkg_failed = untried; then { { echo "$as_me:$LINENO: error: The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables libxml2_CFLAGS and libxml2_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See \`config.log' for more details." >&5 echo "$as_me: error: The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables libxml2_CFLAGS and libxml2_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See \`config.log' for more details." >&2;} { (exit 1); exit 1; }; } else libxml2_CFLAGS=$pkg_cv_libxml2_CFLAGS libxml2_LIBS=$pkg_cv_libxml2_LIBS { echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6; } LIBS="${LIBS} $libxml2_LIBS" CPPFLAGS="${CPPFLAGS} $libxml2_CFLAGS" fi fi if test "${ac_cv_header_libxml_tree_h+set}" = set; then { echo "$as_me:$LINENO: checking for libxml/tree.h" >&5 echo $ECHO_N "checking for libxml/tree.h... $ECHO_C" >&6; } if test "${ac_cv_header_libxml_tree_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 fi { echo "$as_me:$LINENO: result: $ac_cv_header_libxml_tree_h" >&5 echo "${ECHO_T}$ac_cv_header_libxml_tree_h" >&6; } else # Is the header compilable? { echo "$as_me:$LINENO: checking libxml/tree.h usability" >&5 echo $ECHO_N "checking libxml/tree.h usability... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then ac_header_compiler=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_compiler=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 echo "${ECHO_T}$ac_header_compiler" >&6; } # Is the header present? { echo "$as_me:$LINENO: checking libxml/tree.h presence" >&5 echo $ECHO_N "checking libxml/tree.h presence... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then ac_header_preproc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_preproc=no fi rm -f conftest.err conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 echo "${ECHO_T}$ac_header_preproc" >&6; } # So? What about this header? case $ac_header_compiler:$ac_header_preproc:$ac_cxx_preproc_warn_flag in yes:no: ) { echo "$as_me:$LINENO: WARNING: libxml/tree.h: accepted by the compiler, rejected by the preprocessor!" >&5 echo "$as_me: WARNING: libxml/tree.h: accepted by the compiler, rejected by the preprocessor!" >&2;} { echo "$as_me:$LINENO: WARNING: libxml/tree.h: proceeding with the compiler's result" >&5 echo "$as_me: WARNING: libxml/tree.h: proceeding with the compiler's result" >&2;} ac_header_preproc=yes ;; no:yes:* ) { echo "$as_me:$LINENO: WARNING: libxml/tree.h: present but cannot be compiled" >&5 echo "$as_me: WARNING: libxml/tree.h: present but cannot be compiled" >&2;} { echo "$as_me:$LINENO: WARNING: libxml/tree.h: check for missing prerequisite headers?" >&5 echo "$as_me: WARNING: libxml/tree.h: check for missing prerequisite headers?" >&2;} { echo "$as_me:$LINENO: WARNING: libxml/tree.h: see the Autoconf documentation" >&5 echo "$as_me: WARNING: libxml/tree.h: see the Autoconf documentation" >&2;} { echo "$as_me:$LINENO: WARNING: libxml/tree.h: section \"Present But Cannot Be Compiled\"" >&5 echo "$as_me: WARNING: libxml/tree.h: section \"Present But Cannot Be Compiled\"" >&2;} { echo "$as_me:$LINENO: WARNING: libxml/tree.h: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: libxml/tree.h: proceeding with the preprocessor's result" >&2;} { echo "$as_me:$LINENO: WARNING: libxml/tree.h: in the future, the compiler will take precedence" >&5 echo "$as_me: WARNING: libxml/tree.h: in the future, the compiler will take precedence" >&2;} ( cat <<\_ASBOX ## ------------------------------------------- ## ## Report this to hugbug@users.sourceforge.net ## ## ------------------------------------------- ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac { echo "$as_me:$LINENO: checking for libxml/tree.h" >&5 echo $ECHO_N "checking for libxml/tree.h... $ECHO_C" >&6; } if test "${ac_cv_header_libxml_tree_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_cv_header_libxml_tree_h=$ac_header_preproc fi { echo "$as_me:$LINENO: result: $ac_cv_header_libxml_tree_h" >&5 echo "${ECHO_T}$ac_cv_header_libxml_tree_h" >&6; } fi if test $ac_cv_header_libxml_tree_h = yes; then : else { { echo "$as_me:$LINENO: error: \"libxml2 header files not found\"" >&5 echo "$as_me: error: \"libxml2 header files not found\"" >&2;} { (exit 1); exit 1; }; } fi { echo "$as_me:$LINENO: checking for library containing xmlNewNode" >&5 echo $ECHO_N "checking for library containing xmlNewNode... $ECHO_C" >&6; } if test "${ac_cv_search_xmlNewNode+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char xmlNewNode (); int main () { return xmlNewNode (); ; return 0; } _ACEOF for ac_lib in '' xml2; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_xmlNewNode=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_xmlNewNode+set}" = set; then break fi done if test "${ac_cv_search_xmlNewNode+set}" = set; then : else ac_cv_search_xmlNewNode=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_xmlNewNode" >&5 echo "${ECHO_T}$ac_cv_search_xmlNewNode" >&6; } ac_res=$ac_cv_search_xmlNewNode if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" else { { echo "$as_me:$LINENO: error: \"libxml2 library not found\"" >&5 echo "$as_me: error: \"libxml2 library not found\"" >&2;} { (exit 1); exit 1; }; } fi { echo "$as_me:$LINENO: checking whether to use curses" >&5 echo $ECHO_N "checking whether to use curses... $ECHO_C" >&6; } # Check whether --enable-curses was given. if test "${enable_curses+set}" = set; then enableval=$enable_curses; USECURSES=$enableval else USECURSES=yes fi { echo "$as_me:$LINENO: result: $USECURSES" >&5 echo "${ECHO_T}$USECURSES" >&6; } if test "$USECURSES" = "yes"; then INCVAL="${LIBPREF}/include" LIBVAL="${LIBPREF}/lib" # Check whether --with-libcurses_includes was given. if test "${with_libcurses_includes+set}" = set; then withval=$with_libcurses_includes; INCVAL="$withval" fi CPPFLAGS="${CPPFLAGS} -I${INCVAL}" # Check whether --with-libcurses_libraries was given. if test "${with_libcurses_libraries+set}" = set; then withval=$with_libcurses_libraries; LIBVAL="$withval" fi LDFLAGS="${LDFLAGS} -L${LIBVAL}" if test "${ac_cv_header_ncurses_h+set}" = set; then { echo "$as_me:$LINENO: checking for ncurses.h" >&5 echo $ECHO_N "checking for ncurses.h... $ECHO_C" >&6; } if test "${ac_cv_header_ncurses_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 fi { echo "$as_me:$LINENO: result: $ac_cv_header_ncurses_h" >&5 echo "${ECHO_T}$ac_cv_header_ncurses_h" >&6; } else # Is the header compilable? { echo "$as_me:$LINENO: checking ncurses.h usability" >&5 echo $ECHO_N "checking ncurses.h usability... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then ac_header_compiler=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_compiler=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 echo "${ECHO_T}$ac_header_compiler" >&6; } # Is the header present? { echo "$as_me:$LINENO: checking ncurses.h presence" >&5 echo $ECHO_N "checking ncurses.h presence... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then ac_header_preproc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_preproc=no fi rm -f conftest.err conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 echo "${ECHO_T}$ac_header_preproc" >&6; } # So? What about this header? case $ac_header_compiler:$ac_header_preproc:$ac_cxx_preproc_warn_flag in yes:no: ) { echo "$as_me:$LINENO: WARNING: ncurses.h: accepted by the compiler, rejected by the preprocessor!" >&5 echo "$as_me: WARNING: ncurses.h: accepted by the compiler, rejected by the preprocessor!" >&2;} { echo "$as_me:$LINENO: WARNING: ncurses.h: proceeding with the compiler's result" >&5 echo "$as_me: WARNING: ncurses.h: proceeding with the compiler's result" >&2;} ac_header_preproc=yes ;; no:yes:* ) { echo "$as_me:$LINENO: WARNING: ncurses.h: present but cannot be compiled" >&5 echo "$as_me: WARNING: ncurses.h: present but cannot be compiled" >&2;} { echo "$as_me:$LINENO: WARNING: ncurses.h: check for missing prerequisite headers?" >&5 echo "$as_me: WARNING: ncurses.h: check for missing prerequisite headers?" >&2;} { echo "$as_me:$LINENO: WARNING: ncurses.h: see the Autoconf documentation" >&5 echo "$as_me: WARNING: ncurses.h: see the Autoconf documentation" >&2;} { echo "$as_me:$LINENO: WARNING: ncurses.h: section \"Present But Cannot Be Compiled\"" >&5 echo "$as_me: WARNING: ncurses.h: section \"Present But Cannot Be Compiled\"" >&2;} { echo "$as_me:$LINENO: WARNING: ncurses.h: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: ncurses.h: proceeding with the preprocessor's result" >&2;} { echo "$as_me:$LINENO: WARNING: ncurses.h: in the future, the compiler will take precedence" >&5 echo "$as_me: WARNING: ncurses.h: in the future, the compiler will take precedence" >&2;} ( cat <<\_ASBOX ## ------------------------------------------- ## ## Report this to hugbug@users.sourceforge.net ## ## ------------------------------------------- ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac { echo "$as_me:$LINENO: checking for ncurses.h" >&5 echo $ECHO_N "checking for ncurses.h... $ECHO_C" >&6; } if test "${ac_cv_header_ncurses_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_cv_header_ncurses_h=$ac_header_preproc fi { echo "$as_me:$LINENO: result: $ac_cv_header_ncurses_h" >&5 echo "${ECHO_T}$ac_cv_header_ncurses_h" >&6; } fi if test $ac_cv_header_ncurses_h = yes; then FOUND=yes cat >>confdefs.h <<\_ACEOF #define HAVE_NCURSES_H 1 _ACEOF else FOUND=no fi if test "$FOUND" = "no"; then if test "${ac_cv_header_ncurses_ncurses_h+set}" = set; then { echo "$as_me:$LINENO: checking for ncurses/ncurses.h" >&5 echo $ECHO_N "checking for ncurses/ncurses.h... $ECHO_C" >&6; } if test "${ac_cv_header_ncurses_ncurses_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 fi { echo "$as_me:$LINENO: result: $ac_cv_header_ncurses_ncurses_h" >&5 echo "${ECHO_T}$ac_cv_header_ncurses_ncurses_h" >&6; } else # Is the header compilable? { echo "$as_me:$LINENO: checking ncurses/ncurses.h usability" >&5 echo $ECHO_N "checking ncurses/ncurses.h usability... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then ac_header_compiler=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_compiler=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 echo "${ECHO_T}$ac_header_compiler" >&6; } # Is the header present? { echo "$as_me:$LINENO: checking ncurses/ncurses.h presence" >&5 echo $ECHO_N "checking ncurses/ncurses.h presence... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then ac_header_preproc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_preproc=no fi rm -f conftest.err conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 echo "${ECHO_T}$ac_header_preproc" >&6; } # So? What about this header? case $ac_header_compiler:$ac_header_preproc:$ac_cxx_preproc_warn_flag in yes:no: ) { echo "$as_me:$LINENO: WARNING: ncurses/ncurses.h: accepted by the compiler, rejected by the preprocessor!" >&5 echo "$as_me: WARNING: ncurses/ncurses.h: accepted by the compiler, rejected by the preprocessor!" >&2;} { echo "$as_me:$LINENO: WARNING: ncurses/ncurses.h: proceeding with the compiler's result" >&5 echo "$as_me: WARNING: ncurses/ncurses.h: proceeding with the compiler's result" >&2;} ac_header_preproc=yes ;; no:yes:* ) { echo "$as_me:$LINENO: WARNING: ncurses/ncurses.h: present but cannot be compiled" >&5 echo "$as_me: WARNING: ncurses/ncurses.h: present but cannot be compiled" >&2;} { echo "$as_me:$LINENO: WARNING: ncurses/ncurses.h: check for missing prerequisite headers?" >&5 echo "$as_me: WARNING: ncurses/ncurses.h: check for missing prerequisite headers?" >&2;} { echo "$as_me:$LINENO: WARNING: ncurses/ncurses.h: see the Autoconf documentation" >&5 echo "$as_me: WARNING: ncurses/ncurses.h: see the Autoconf documentation" >&2;} { echo "$as_me:$LINENO: WARNING: ncurses/ncurses.h: section \"Present But Cannot Be Compiled\"" >&5 echo "$as_me: WARNING: ncurses/ncurses.h: section \"Present But Cannot Be Compiled\"" >&2;} { echo "$as_me:$LINENO: WARNING: ncurses/ncurses.h: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: ncurses/ncurses.h: proceeding with the preprocessor's result" >&2;} { echo "$as_me:$LINENO: WARNING: ncurses/ncurses.h: in the future, the compiler will take precedence" >&5 echo "$as_me: WARNING: ncurses/ncurses.h: in the future, the compiler will take precedence" >&2;} ( cat <<\_ASBOX ## ------------------------------------------- ## ## Report this to hugbug@users.sourceforge.net ## ## ------------------------------------------- ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac { echo "$as_me:$LINENO: checking for ncurses/ncurses.h" >&5 echo $ECHO_N "checking for ncurses/ncurses.h... $ECHO_C" >&6; } if test "${ac_cv_header_ncurses_ncurses_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_cv_header_ncurses_ncurses_h=$ac_header_preproc fi { echo "$as_me:$LINENO: result: $ac_cv_header_ncurses_ncurses_h" >&5 echo "${ECHO_T}$ac_cv_header_ncurses_ncurses_h" >&6; } fi if test $ac_cv_header_ncurses_ncurses_h = yes; then FOUND=yes cat >>confdefs.h <<\_ACEOF #define HAVE_NCURSES_NCURSES_H 1 _ACEOF else FOUND=no fi fi if test "$FOUND" = "no"; then if test "${ac_cv_header_curses_h+set}" = set; then { echo "$as_me:$LINENO: checking for curses.h" >&5 echo $ECHO_N "checking for curses.h... $ECHO_C" >&6; } if test "${ac_cv_header_curses_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 fi { echo "$as_me:$LINENO: result: $ac_cv_header_curses_h" >&5 echo "${ECHO_T}$ac_cv_header_curses_h" >&6; } else # Is the header compilable? { echo "$as_me:$LINENO: checking curses.h usability" >&5 echo $ECHO_N "checking curses.h usability... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then ac_header_compiler=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_compiler=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 echo "${ECHO_T}$ac_header_compiler" >&6; } # Is the header present? { echo "$as_me:$LINENO: checking curses.h presence" >&5 echo $ECHO_N "checking curses.h presence... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then ac_header_preproc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_preproc=no fi rm -f conftest.err conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 echo "${ECHO_T}$ac_header_preproc" >&6; } # So? What about this header? case $ac_header_compiler:$ac_header_preproc:$ac_cxx_preproc_warn_flag in yes:no: ) { echo "$as_me:$LINENO: WARNING: curses.h: accepted by the compiler, rejected by the preprocessor!" >&5 echo "$as_me: WARNING: curses.h: accepted by the compiler, rejected by the preprocessor!" >&2;} { echo "$as_me:$LINENO: WARNING: curses.h: proceeding with the compiler's result" >&5 echo "$as_me: WARNING: curses.h: proceeding with the compiler's result" >&2;} ac_header_preproc=yes ;; no:yes:* ) { echo "$as_me:$LINENO: WARNING: curses.h: present but cannot be compiled" >&5 echo "$as_me: WARNING: curses.h: present but cannot be compiled" >&2;} { echo "$as_me:$LINENO: WARNING: curses.h: check for missing prerequisite headers?" >&5 echo "$as_me: WARNING: curses.h: check for missing prerequisite headers?" >&2;} { echo "$as_me:$LINENO: WARNING: curses.h: see the Autoconf documentation" >&5 echo "$as_me: WARNING: curses.h: see the Autoconf documentation" >&2;} { echo "$as_me:$LINENO: WARNING: curses.h: section \"Present But Cannot Be Compiled\"" >&5 echo "$as_me: WARNING: curses.h: section \"Present But Cannot Be Compiled\"" >&2;} { echo "$as_me:$LINENO: WARNING: curses.h: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: curses.h: proceeding with the preprocessor's result" >&2;} { echo "$as_me:$LINENO: WARNING: curses.h: in the future, the compiler will take precedence" >&5 echo "$as_me: WARNING: curses.h: in the future, the compiler will take precedence" >&2;} ( cat <<\_ASBOX ## ------------------------------------------- ## ## Report this to hugbug@users.sourceforge.net ## ## ------------------------------------------- ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac { echo "$as_me:$LINENO: checking for curses.h" >&5 echo $ECHO_N "checking for curses.h... $ECHO_C" >&6; } if test "${ac_cv_header_curses_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_cv_header_curses_h=$ac_header_preproc fi { echo "$as_me:$LINENO: result: $ac_cv_header_curses_h" >&5 echo "${ECHO_T}$ac_cv_header_curses_h" >&6; } fi if test $ac_cv_header_curses_h = yes; then FOUND=yes cat >>confdefs.h <<\_ACEOF #define HAVE_CURSES_H 1 _ACEOF else FOUND=no fi fi if test "$FOUND" = "no"; then { { echo "$as_me:$LINENO: error: Couldn't find curses headers (ncurses.h or curses.h)" >&5 echo "$as_me: error: Couldn't find curses headers (ncurses.h or curses.h)" >&2;} { (exit 1); exit 1; }; } fi { echo "$as_me:$LINENO: checking for library containing refresh" >&5 echo $ECHO_N "checking for library containing refresh... $ECHO_C" >&6; } if test "${ac_cv_search_refresh+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char refresh (); int main () { return refresh (); ; return 0; } _ACEOF for ac_lib in '' ncurses curses; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_refresh=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_refresh+set}" = set; then break fi done if test "${ac_cv_search_refresh+set}" = set; then : else ac_cv_search_refresh=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_refresh" >&5 echo "${ECHO_T}$ac_cv_search_refresh" >&6; } ac_res=$ac_cv_search_refresh if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" else { { echo "$as_me:$LINENO: error: Couldn't find curses library" >&5 echo "$as_me: error: Couldn't find curses library" >&2;} { (exit 1); exit 1; }; } fi else cat >>confdefs.h <<\_ACEOF #define DISABLE_CURSES 1 _ACEOF fi { echo "$as_me:$LINENO: checking whether to include code for par-checking" >&5 echo $ECHO_N "checking whether to include code for par-checking... $ECHO_C" >&6; } # Check whether --enable-parcheck was given. if test "${enable_parcheck+set}" = set; then enableval=$enable_parcheck; ENABLEPARCHECK=$enableval else ENABLEPARCHECK=yes fi { echo "$as_me:$LINENO: result: $ENABLEPARCHECK" >&5 echo "${ECHO_T}$ENABLEPARCHECK" >&6; } if test "$ENABLEPARCHECK" = "yes"; then # Check whether --with-libsigc_includes was given. if test "${with_libsigc_includes+set}" = set; then withval=$with_libsigc_includes; CPPFLAGS="${CPPFLAGS} -I${withval}" INCVAL="yes" else INCVAL="no" fi # Check whether --with-libsigc_libraries was given. if test "${with_libsigc_libraries+set}" = set; then withval=$with_libsigc_libraries; LDFLAGS="${LDFLAGS} -L${withval}" CPPFLAGS="${CPPFLAGS} -I${withval}/sigc++-2.0/include" LIBVAL="yes" else LIBVAL="no" fi if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then pkg_failed=no { echo "$as_me:$LINENO: checking for libsigc" >&5 echo $ECHO_N "checking for libsigc... $ECHO_C" >&6; } if test -n "$PKG_CONFIG"; then if test -n "$libsigc_CFLAGS"; then pkg_cv_libsigc_CFLAGS="$libsigc_CFLAGS" else if test -n "$PKG_CONFIG" && \ { (echo "$as_me:$LINENO: \$PKG_CONFIG --exists --print-errors \"sigc++-2.0\"") >&5 ($PKG_CONFIG --exists --print-errors "sigc++-2.0") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; then pkg_cv_libsigc_CFLAGS=`$PKG_CONFIG --cflags "sigc++-2.0" 2>/dev/null` else pkg_failed=yes fi fi else pkg_failed=untried fi if test -n "$PKG_CONFIG"; then if test -n "$libsigc_LIBS"; then pkg_cv_libsigc_LIBS="$libsigc_LIBS" else if test -n "$PKG_CONFIG" && \ { (echo "$as_me:$LINENO: \$PKG_CONFIG --exists --print-errors \"sigc++-2.0\"") >&5 ($PKG_CONFIG --exists --print-errors "sigc++-2.0") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; then pkg_cv_libsigc_LIBS=`$PKG_CONFIG --libs "sigc++-2.0" 2>/dev/null` else pkg_failed=yes fi fi else pkg_failed=untried fi if test $pkg_failed = yes; then if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then libsigc_PKG_ERRORS=`$PKG_CONFIG --short-errors --errors-to-stdout --print-errors "sigc++-2.0"` else libsigc_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "sigc++-2.0"` fi # Put the nasty error message in config.log where it belongs echo "$libsigc_PKG_ERRORS" >&5 { { echo "$as_me:$LINENO: error: Package requirements (sigc++-2.0) were not met: $libsigc_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables libsigc_CFLAGS and libsigc_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. " >&5 echo "$as_me: error: Package requirements (sigc++-2.0) were not met: $libsigc_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables libsigc_CFLAGS and libsigc_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. " >&2;} { (exit 1); exit 1; }; } elif test $pkg_failed = untried; then { { echo "$as_me:$LINENO: error: The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables libsigc_CFLAGS and libsigc_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See \`config.log' for more details." >&5 echo "$as_me: error: The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables libsigc_CFLAGS and libsigc_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See \`config.log' for more details." >&2;} { (exit 1); exit 1; }; } else libsigc_CFLAGS=$pkg_cv_libsigc_CFLAGS libsigc_LIBS=$pkg_cv_libsigc_LIBS { echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6; } LIBS="${LIBS} $libsigc_LIBS" CPPFLAGS="${CPPFLAGS} $libsigc_CFLAGS" fi fi if test "${ac_cv_header_sigcpp_type_traits_h+set}" = set; then { echo "$as_me:$LINENO: checking for sigc++/type_traits.h" >&5 echo $ECHO_N "checking for sigc++/type_traits.h... $ECHO_C" >&6; } if test "${ac_cv_header_sigcpp_type_traits_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 fi { echo "$as_me:$LINENO: result: $ac_cv_header_sigcpp_type_traits_h" >&5 echo "${ECHO_T}$ac_cv_header_sigcpp_type_traits_h" >&6; } else # Is the header compilable? { echo "$as_me:$LINENO: checking sigc++/type_traits.h usability" >&5 echo $ECHO_N "checking sigc++/type_traits.h usability... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then ac_header_compiler=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_compiler=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 echo "${ECHO_T}$ac_header_compiler" >&6; } # Is the header present? { echo "$as_me:$LINENO: checking sigc++/type_traits.h presence" >&5 echo $ECHO_N "checking sigc++/type_traits.h presence... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then ac_header_preproc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_preproc=no fi rm -f conftest.err conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 echo "${ECHO_T}$ac_header_preproc" >&6; } # So? What about this header? case $ac_header_compiler:$ac_header_preproc:$ac_cxx_preproc_warn_flag in yes:no: ) { echo "$as_me:$LINENO: WARNING: sigc++/type_traits.h: accepted by the compiler, rejected by the preprocessor!" >&5 echo "$as_me: WARNING: sigc++/type_traits.h: accepted by the compiler, rejected by the preprocessor!" >&2;} { echo "$as_me:$LINENO: WARNING: sigc++/type_traits.h: proceeding with the compiler's result" >&5 echo "$as_me: WARNING: sigc++/type_traits.h: proceeding with the compiler's result" >&2;} ac_header_preproc=yes ;; no:yes:* ) { echo "$as_me:$LINENO: WARNING: sigc++/type_traits.h: present but cannot be compiled" >&5 echo "$as_me: WARNING: sigc++/type_traits.h: present but cannot be compiled" >&2;} { echo "$as_me:$LINENO: WARNING: sigc++/type_traits.h: check for missing prerequisite headers?" >&5 echo "$as_me: WARNING: sigc++/type_traits.h: check for missing prerequisite headers?" >&2;} { echo "$as_me:$LINENO: WARNING: sigc++/type_traits.h: see the Autoconf documentation" >&5 echo "$as_me: WARNING: sigc++/type_traits.h: see the Autoconf documentation" >&2;} { echo "$as_me:$LINENO: WARNING: sigc++/type_traits.h: section \"Present But Cannot Be Compiled\"" >&5 echo "$as_me: WARNING: sigc++/type_traits.h: section \"Present But Cannot Be Compiled\"" >&2;} { echo "$as_me:$LINENO: WARNING: sigc++/type_traits.h: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: sigc++/type_traits.h: proceeding with the preprocessor's result" >&2;} { echo "$as_me:$LINENO: WARNING: sigc++/type_traits.h: in the future, the compiler will take precedence" >&5 echo "$as_me: WARNING: sigc++/type_traits.h: in the future, the compiler will take precedence" >&2;} ( cat <<\_ASBOX ## ------------------------------------------- ## ## Report this to hugbug@users.sourceforge.net ## ## ------------------------------------------- ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac { echo "$as_me:$LINENO: checking for sigc++/type_traits.h" >&5 echo $ECHO_N "checking for sigc++/type_traits.h... $ECHO_C" >&6; } if test "${ac_cv_header_sigcpp_type_traits_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_cv_header_sigcpp_type_traits_h=$ac_header_preproc fi { echo "$as_me:$LINENO: result: $ac_cv_header_sigcpp_type_traits_h" >&5 echo "${ECHO_T}$ac_cv_header_sigcpp_type_traits_h" >&6; } fi if test $ac_cv_header_sigcpp_type_traits_h = yes; then : else { { echo "$as_me:$LINENO: error: \"libsigc++-2.0 header files not found\"" >&5 echo "$as_me: error: \"libsigc++-2.0 header files not found\"" >&2;} { (exit 1); exit 1; }; } fi INCVAL="${LIBPREF}/include" LIBVAL="${LIBPREF}/lib" # Check whether --with-libpar2_includes was given. if test "${with_libpar2_includes+set}" = set; then withval=$with_libpar2_includes; INCVAL="$withval" fi CPPFLAGS="${CPPFLAGS} -I${INCVAL}" if test "${ac_cv_header_libpar2_libpar2_h+set}" = set; then { echo "$as_me:$LINENO: checking for libpar2/libpar2.h" >&5 echo $ECHO_N "checking for libpar2/libpar2.h... $ECHO_C" >&6; } if test "${ac_cv_header_libpar2_libpar2_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 fi { echo "$as_me:$LINENO: result: $ac_cv_header_libpar2_libpar2_h" >&5 echo "${ECHO_T}$ac_cv_header_libpar2_libpar2_h" >&6; } else # Is the header compilable? { echo "$as_me:$LINENO: checking libpar2/libpar2.h usability" >&5 echo $ECHO_N "checking libpar2/libpar2.h usability... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then ac_header_compiler=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_compiler=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 echo "${ECHO_T}$ac_header_compiler" >&6; } # Is the header present? { echo "$as_me:$LINENO: checking libpar2/libpar2.h presence" >&5 echo $ECHO_N "checking libpar2/libpar2.h presence... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then ac_header_preproc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_preproc=no fi rm -f conftest.err conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 echo "${ECHO_T}$ac_header_preproc" >&6; } # So? What about this header? case $ac_header_compiler:$ac_header_preproc:$ac_cxx_preproc_warn_flag in yes:no: ) { echo "$as_me:$LINENO: WARNING: libpar2/libpar2.h: accepted by the compiler, rejected by the preprocessor!" >&5 echo "$as_me: WARNING: libpar2/libpar2.h: accepted by the compiler, rejected by the preprocessor!" >&2;} { echo "$as_me:$LINENO: WARNING: libpar2/libpar2.h: proceeding with the compiler's result" >&5 echo "$as_me: WARNING: libpar2/libpar2.h: proceeding with the compiler's result" >&2;} ac_header_preproc=yes ;; no:yes:* ) { echo "$as_me:$LINENO: WARNING: libpar2/libpar2.h: present but cannot be compiled" >&5 echo "$as_me: WARNING: libpar2/libpar2.h: present but cannot be compiled" >&2;} { echo "$as_me:$LINENO: WARNING: libpar2/libpar2.h: check for missing prerequisite headers?" >&5 echo "$as_me: WARNING: libpar2/libpar2.h: check for missing prerequisite headers?" >&2;} { echo "$as_me:$LINENO: WARNING: libpar2/libpar2.h: see the Autoconf documentation" >&5 echo "$as_me: WARNING: libpar2/libpar2.h: see the Autoconf documentation" >&2;} { echo "$as_me:$LINENO: WARNING: libpar2/libpar2.h: section \"Present But Cannot Be Compiled\"" >&5 echo "$as_me: WARNING: libpar2/libpar2.h: section \"Present But Cannot Be Compiled\"" >&2;} { echo "$as_me:$LINENO: WARNING: libpar2/libpar2.h: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: libpar2/libpar2.h: proceeding with the preprocessor's result" >&2;} { echo "$as_me:$LINENO: WARNING: libpar2/libpar2.h: in the future, the compiler will take precedence" >&5 echo "$as_me: WARNING: libpar2/libpar2.h: in the future, the compiler will take precedence" >&2;} ( cat <<\_ASBOX ## ------------------------------------------- ## ## Report this to hugbug@users.sourceforge.net ## ## ------------------------------------------- ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac { echo "$as_me:$LINENO: checking for libpar2/libpar2.h" >&5 echo $ECHO_N "checking for libpar2/libpar2.h... $ECHO_C" >&6; } if test "${ac_cv_header_libpar2_libpar2_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_cv_header_libpar2_libpar2_h=$ac_header_preproc fi { echo "$as_me:$LINENO: result: $ac_cv_header_libpar2_libpar2_h" >&5 echo "${ECHO_T}$ac_cv_header_libpar2_libpar2_h" >&6; } fi if test $ac_cv_header_libpar2_libpar2_h = yes; then : else { { echo "$as_me:$LINENO: error: \"libpar2 header files not found\"" >&5 echo "$as_me: error: \"libpar2 header files not found\"" >&2;} { (exit 1); exit 1; }; } fi # Check whether --with-libpar2_libraries was given. if test "${with_libpar2_libraries+set}" = set; then withval=$with_libpar2_libraries; LIBVAL="$withval" fi LDFLAGS="${LDFLAGS} -L${LIBVAL}" { echo "$as_me:$LINENO: checking for library containing _ZN12Par2RepairerC1Ev" >&5 echo $ECHO_N "checking for library containing _ZN12Par2RepairerC1Ev... $ECHO_C" >&6; } if test "${ac_cv_search__ZN12Par2RepairerC1Ev+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char _ZN12Par2RepairerC1Ev (); int main () { return _ZN12Par2RepairerC1Ev (); ; return 0; } _ACEOF for ac_lib in '' par2; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search__ZN12Par2RepairerC1Ev=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search__ZN12Par2RepairerC1Ev+set}" = set; then break fi done if test "${ac_cv_search__ZN12Par2RepairerC1Ev+set}" = set; then : else ac_cv_search__ZN12Par2RepairerC1Ev=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search__ZN12Par2RepairerC1Ev" >&5 echo "${ECHO_T}$ac_cv_search__ZN12Par2RepairerC1Ev" >&6; } ac_res=$ac_cv_search__ZN12Par2RepairerC1Ev if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" else { { echo "$as_me:$LINENO: error: \"libpar2 library not found\"" >&5 echo "$as_me: error: \"libpar2 library not found\"" >&2;} { (exit 1); exit 1; }; } fi { echo "$as_me:$LINENO: checking for libpar2 linking" >&5 echo $ECHO_N "checking for libpar2 linking... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include class Repairer : public Par2Repairer { }; int main () { Repairer* p = new Repairer(); ; return 0; } _ACEOF rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then { echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6; } else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } { { echo "$as_me:$LINENO: error: \"libpar2 library not found\"" >&5 echo "$as_me: error: \"libpar2 library not found\"" >&2;} { (exit 1); exit 1; }; } fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext conftest.$ac_ext { echo "$as_me:$LINENO: checking whether libpar2 supports cancelling" >&5 echo $ECHO_N "checking whether libpar2 supports cancelling... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include class Repairer : public Par2Repairer { void test() { cancelled = true; } }; int main () { ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then { echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6; } cat >>confdefs.h <<\_ACEOF #define HAVE_PAR2_CANCEL 1 _ACEOF else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: checking whether libpar2 has recent bugfixes-patch (version 2)" >&5 echo $ECHO_N "checking whether libpar2 has recent bugfixes-patch (version 2)... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include class Repairer : public Par2Repairer { void test() { BugfixesPatchVersion2(); } }; int main () { ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then { echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6; } PAR2PATCHV2=yes cat >>confdefs.h <<\_ACEOF #define HAVE_PAR2_BUGFIXES_V2 1 _ACEOF else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } PAR2PATCHV2=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test "$PAR2PATCHV2" = "no" ; then # Check whether --enable-libpar2-bugfixes-check was given. if test "${enable_libpar2_bugfixes_check+set}" = set; then enableval=$enable_libpar2_bugfixes_check; PAR2PATCHCHECK=$enableval else PAR2PATCHCHECK=yes fi if test "$PAR2PATCHCHECK" = "yes"; then { { echo "$as_me:$LINENO: error: Your version of libpar2 doesn't include the recent bugfixes-patch (version 2, updated Dec 3, 2012). Please patch libpar2 with the patches supplied with NZBGet (see README for details). If you cannot patch libpar2, you can use configure parameter --disable-libpar2-bugfixes-check to suppress the check. Please note however that in this case the program may crash during par-check/repair. The patch is highly recommended!" >&5 echo "$as_me: error: Your version of libpar2 doesn't include the recent bugfixes-patch (version 2, updated Dec 3, 2012). Please patch libpar2 with the patches supplied with NZBGet (see README for details). If you cannot patch libpar2, you can use configure parameter --disable-libpar2-bugfixes-check to suppress the check. Please note however that in this case the program may crash during par-check/repair. The patch is highly recommended!" >&2;} { (exit 1); exit 1; }; } fi fi else cat >>confdefs.h <<\_ACEOF #define DISABLE_PARCHECK 1 _ACEOF fi { echo "$as_me:$LINENO: checking whether to use TLS/SSL" >&5 echo $ECHO_N "checking whether to use TLS/SSL... $ECHO_C" >&6; } # Check whether --enable-tls was given. if test "${enable_tls+set}" = set; then enableval=$enable_tls; USETLS=$enableval else USETLS=yes fi { echo "$as_me:$LINENO: result: $USETLS" >&5 echo "${ECHO_T}$USETLS" >&6; } if test "$USETLS" = "yes"; then # Check whether --with-tlslib was given. if test "${with_tlslib+set}" = set; then withval=$with_tlslib; TLSLIB="$withval" fi if test "$TLSLIB" != "GnuTLS" -a "$TLSLIB" != "OpenSSL" -a "$TLSLIB" != ""; then { { echo "$as_me:$LINENO: error: Invalid argument for option --with-tlslib" >&5 echo "$as_me: error: Invalid argument for option --with-tlslib" >&2;} { (exit 1); exit 1; }; } fi if test "$TLSLIB" = "GnuTLS" -o "$TLSLIB" = ""; then INCVAL="${LIBPREF}/include" LIBVAL="${LIBPREF}/lib" # Check whether --with-libgnutls_includes was given. if test "${with_libgnutls_includes+set}" = set; then withval=$with_libgnutls_includes; INCVAL="$withval" fi CPPFLAGS="${CPPFLAGS} -I${INCVAL}" # Check whether --with-libgnutls_libraries was given. if test "${with_libgnutls_libraries+set}" = set; then withval=$with_libgnutls_libraries; LIBVAL="$withval" fi LDFLAGS="${LDFLAGS} -L${LIBVAL}" if test "${ac_cv_header_gnutls_gnutls_h+set}" = set; then { echo "$as_me:$LINENO: checking for gnutls/gnutls.h" >&5 echo $ECHO_N "checking for gnutls/gnutls.h... $ECHO_C" >&6; } if test "${ac_cv_header_gnutls_gnutls_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 fi { echo "$as_me:$LINENO: result: $ac_cv_header_gnutls_gnutls_h" >&5 echo "${ECHO_T}$ac_cv_header_gnutls_gnutls_h" >&6; } else # Is the header compilable? { echo "$as_me:$LINENO: checking gnutls/gnutls.h usability" >&5 echo $ECHO_N "checking gnutls/gnutls.h usability... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then ac_header_compiler=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_compiler=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 echo "${ECHO_T}$ac_header_compiler" >&6; } # Is the header present? { echo "$as_me:$LINENO: checking gnutls/gnutls.h presence" >&5 echo $ECHO_N "checking gnutls/gnutls.h presence... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then ac_header_preproc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_preproc=no fi rm -f conftest.err conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 echo "${ECHO_T}$ac_header_preproc" >&6; } # So? What about this header? case $ac_header_compiler:$ac_header_preproc:$ac_cxx_preproc_warn_flag in yes:no: ) { echo "$as_me:$LINENO: WARNING: gnutls/gnutls.h: accepted by the compiler, rejected by the preprocessor!" >&5 echo "$as_me: WARNING: gnutls/gnutls.h: accepted by the compiler, rejected by the preprocessor!" >&2;} { echo "$as_me:$LINENO: WARNING: gnutls/gnutls.h: proceeding with the compiler's result" >&5 echo "$as_me: WARNING: gnutls/gnutls.h: proceeding with the compiler's result" >&2;} ac_header_preproc=yes ;; no:yes:* ) { echo "$as_me:$LINENO: WARNING: gnutls/gnutls.h: present but cannot be compiled" >&5 echo "$as_me: WARNING: gnutls/gnutls.h: present but cannot be compiled" >&2;} { echo "$as_me:$LINENO: WARNING: gnutls/gnutls.h: check for missing prerequisite headers?" >&5 echo "$as_me: WARNING: gnutls/gnutls.h: check for missing prerequisite headers?" >&2;} { echo "$as_me:$LINENO: WARNING: gnutls/gnutls.h: see the Autoconf documentation" >&5 echo "$as_me: WARNING: gnutls/gnutls.h: see the Autoconf documentation" >&2;} { echo "$as_me:$LINENO: WARNING: gnutls/gnutls.h: section \"Present But Cannot Be Compiled\"" >&5 echo "$as_me: WARNING: gnutls/gnutls.h: section \"Present But Cannot Be Compiled\"" >&2;} { echo "$as_me:$LINENO: WARNING: gnutls/gnutls.h: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: gnutls/gnutls.h: proceeding with the preprocessor's result" >&2;} { echo "$as_me:$LINENO: WARNING: gnutls/gnutls.h: in the future, the compiler will take precedence" >&5 echo "$as_me: WARNING: gnutls/gnutls.h: in the future, the compiler will take precedence" >&2;} ( cat <<\_ASBOX ## ------------------------------------------- ## ## Report this to hugbug@users.sourceforge.net ## ## ------------------------------------------- ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac { echo "$as_me:$LINENO: checking for gnutls/gnutls.h" >&5 echo $ECHO_N "checking for gnutls/gnutls.h... $ECHO_C" >&6; } if test "${ac_cv_header_gnutls_gnutls_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_cv_header_gnutls_gnutls_h=$ac_header_preproc fi { echo "$as_me:$LINENO: result: $ac_cv_header_gnutls_gnutls_h" >&5 echo "${ECHO_T}$ac_cv_header_gnutls_gnutls_h" >&6; } fi if test $ac_cv_header_gnutls_gnutls_h = yes; then FOUND=yes TLSHEADERS=yes else FOUND=no fi if test "$FOUND" = "no" -a "$TLSLIB" = "GnuTLS"; then { { echo "$as_me:$LINENO: error: Couldn't find GnuTLS headers (gnutls.h)" >&5 echo "$as_me: error: Couldn't find GnuTLS headers (gnutls.h)" >&2;} { (exit 1); exit 1; }; } fi if test "$FOUND" = "yes"; then { echo "$as_me:$LINENO: checking for library containing gnutls_global_init" >&5 echo $ECHO_N "checking for library containing gnutls_global_init... $ECHO_C" >&6; } if test "${ac_cv_search_gnutls_global_init+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char gnutls_global_init (); int main () { return gnutls_global_init (); ; return 0; } _ACEOF for ac_lib in '' gnutls; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_gnutls_global_init=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_gnutls_global_init+set}" = set; then break fi done if test "${ac_cv_search_gnutls_global_init+set}" = set; then : else ac_cv_search_gnutls_global_init=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_gnutls_global_init" >&5 echo "${ECHO_T}$ac_cv_search_gnutls_global_init" >&6; } ac_res=$ac_cv_search_gnutls_global_init if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" { echo "$as_me:$LINENO: checking for library containing gcry_control" >&5 echo $ECHO_N "checking for library containing gcry_control... $ECHO_C" >&6; } if test "${ac_cv_search_gcry_control+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char gcry_control (); int main () { return gcry_control (); ; return 0; } _ACEOF for ac_lib in '' gnutls gcrypt; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_gcry_control=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_gcry_control+set}" = set; then break fi done if test "${ac_cv_search_gcry_control+set}" = set; then : else ac_cv_search_gcry_control=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_gcry_control" >&5 echo "${ECHO_T}$ac_cv_search_gcry_control" >&6; } ac_res=$ac_cv_search_gcry_control if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" FOUND=yes else FOUND=no fi else FOUND=no fi if test "$FOUND" = "no" -a "$TLSLIB" = "GnuTLS"; then { { echo "$as_me:$LINENO: error: Couldn't find GnuTLS library" >&5 echo "$as_me: error: Couldn't find GnuTLS library" >&2;} { (exit 1); exit 1; }; } fi if test "$FOUND" = "yes"; then TLSLIB="GnuTLS" cat >>confdefs.h <<\_ACEOF #define HAVE_LIBGNUTLS 1 _ACEOF fi fi fi if test "$TLSLIB" = "OpenSSL" -o "$TLSLIB" = ""; then # Check whether --with-openssl_includes was given. if test "${with_openssl_includes+set}" = set; then withval=$with_openssl_includes; CPPFLAGS="${CPPFLAGS} -I${withval}" INCVAL="yes" else INCVAL="no" fi # Check whether --with-openssl_libraries was given. if test "${with_openssl_libraries+set}" = set; then withval=$with_openssl_libraries; LDFLAGS="${LDFLAGS} -L${withval}" LIBVAL="yes" else LIBVAL="no" fi if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then pkg_failed=no { echo "$as_me:$LINENO: checking for openssl" >&5 echo $ECHO_N "checking for openssl... $ECHO_C" >&6; } if test -n "$PKG_CONFIG"; then if test -n "$openssl_CFLAGS"; then pkg_cv_openssl_CFLAGS="$openssl_CFLAGS" else if test -n "$PKG_CONFIG" && \ { (echo "$as_me:$LINENO: \$PKG_CONFIG --exists --print-errors \"openssl\"") >&5 ($PKG_CONFIG --exists --print-errors "openssl") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; then pkg_cv_openssl_CFLAGS=`$PKG_CONFIG --cflags "openssl" 2>/dev/null` else pkg_failed=yes fi fi else pkg_failed=untried fi if test -n "$PKG_CONFIG"; then if test -n "$openssl_LIBS"; then pkg_cv_openssl_LIBS="$openssl_LIBS" else if test -n "$PKG_CONFIG" && \ { (echo "$as_me:$LINENO: \$PKG_CONFIG --exists --print-errors \"openssl\"") >&5 ($PKG_CONFIG --exists --print-errors "openssl") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; then pkg_cv_openssl_LIBS=`$PKG_CONFIG --libs "openssl" 2>/dev/null` else pkg_failed=yes fi fi else pkg_failed=untried fi if test $pkg_failed = yes; then if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then openssl_PKG_ERRORS=`$PKG_CONFIG --short-errors --errors-to-stdout --print-errors "openssl"` else openssl_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "openssl"` fi # Put the nasty error message in config.log where it belongs echo "$openssl_PKG_ERRORS" >&5 { { echo "$as_me:$LINENO: error: Package requirements (openssl) were not met: $openssl_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables openssl_CFLAGS and openssl_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. " >&5 echo "$as_me: error: Package requirements (openssl) were not met: $openssl_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. Alternatively, you may set the environment variables openssl_CFLAGS and openssl_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. " >&2;} { (exit 1); exit 1; }; } elif test $pkg_failed = untried; then { { echo "$as_me:$LINENO: error: The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables openssl_CFLAGS and openssl_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See \`config.log' for more details." >&5 echo "$as_me: error: The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. Alternatively, you may set the environment variables openssl_CFLAGS and openssl_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details. To get pkg-config, see . See \`config.log' for more details." >&2;} { (exit 1); exit 1; }; } else openssl_CFLAGS=$pkg_cv_openssl_CFLAGS openssl_LIBS=$pkg_cv_openssl_LIBS { echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6; } LIBS="${LIBS} $openssl_LIBS" CPPFLAGS="${CPPFLAGS} $openssl_CFLAGS" fi fi if test "${ac_cv_header_openssl_ssl_h+set}" = set; then { echo "$as_me:$LINENO: checking for openssl/ssl.h" >&5 echo $ECHO_N "checking for openssl/ssl.h... $ECHO_C" >&6; } if test "${ac_cv_header_openssl_ssl_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 fi { echo "$as_me:$LINENO: result: $ac_cv_header_openssl_ssl_h" >&5 echo "${ECHO_T}$ac_cv_header_openssl_ssl_h" >&6; } else # Is the header compilable? { echo "$as_me:$LINENO: checking openssl/ssl.h usability" >&5 echo $ECHO_N "checking openssl/ssl.h usability... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then ac_header_compiler=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_compiler=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 echo "${ECHO_T}$ac_header_compiler" >&6; } # Is the header present? { echo "$as_me:$LINENO: checking openssl/ssl.h presence" >&5 echo $ECHO_N "checking openssl/ssl.h presence... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then ac_header_preproc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_preproc=no fi rm -f conftest.err conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 echo "${ECHO_T}$ac_header_preproc" >&6; } # So? What about this header? case $ac_header_compiler:$ac_header_preproc:$ac_cxx_preproc_warn_flag in yes:no: ) { echo "$as_me:$LINENO: WARNING: openssl/ssl.h: accepted by the compiler, rejected by the preprocessor!" >&5 echo "$as_me: WARNING: openssl/ssl.h: accepted by the compiler, rejected by the preprocessor!" >&2;} { echo "$as_me:$LINENO: WARNING: openssl/ssl.h: proceeding with the compiler's result" >&5 echo "$as_me: WARNING: openssl/ssl.h: proceeding with the compiler's result" >&2;} ac_header_preproc=yes ;; no:yes:* ) { echo "$as_me:$LINENO: WARNING: openssl/ssl.h: present but cannot be compiled" >&5 echo "$as_me: WARNING: openssl/ssl.h: present but cannot be compiled" >&2;} { echo "$as_me:$LINENO: WARNING: openssl/ssl.h: check for missing prerequisite headers?" >&5 echo "$as_me: WARNING: openssl/ssl.h: check for missing prerequisite headers?" >&2;} { echo "$as_me:$LINENO: WARNING: openssl/ssl.h: see the Autoconf documentation" >&5 echo "$as_me: WARNING: openssl/ssl.h: see the Autoconf documentation" >&2;} { echo "$as_me:$LINENO: WARNING: openssl/ssl.h: section \"Present But Cannot Be Compiled\"" >&5 echo "$as_me: WARNING: openssl/ssl.h: section \"Present But Cannot Be Compiled\"" >&2;} { echo "$as_me:$LINENO: WARNING: openssl/ssl.h: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: openssl/ssl.h: proceeding with the preprocessor's result" >&2;} { echo "$as_me:$LINENO: WARNING: openssl/ssl.h: in the future, the compiler will take precedence" >&5 echo "$as_me: WARNING: openssl/ssl.h: in the future, the compiler will take precedence" >&2;} ( cat <<\_ASBOX ## ------------------------------------------- ## ## Report this to hugbug@users.sourceforge.net ## ## ------------------------------------------- ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac { echo "$as_me:$LINENO: checking for openssl/ssl.h" >&5 echo $ECHO_N "checking for openssl/ssl.h... $ECHO_C" >&6; } if test "${ac_cv_header_openssl_ssl_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_cv_header_openssl_ssl_h=$ac_header_preproc fi { echo "$as_me:$LINENO: result: $ac_cv_header_openssl_ssl_h" >&5 echo "${ECHO_T}$ac_cv_header_openssl_ssl_h" >&6; } fi if test $ac_cv_header_openssl_ssl_h = yes; then FOUND=yes TLSHEADERS=yes else FOUND=no fi if test "$FOUND" = "no" -a "$TLSLIB" = "OpenSSL"; then { { echo "$as_me:$LINENO: error: Couldn't find OpenSSL headers (ssl.h)" >&5 echo "$as_me: error: Couldn't find OpenSSL headers (ssl.h)" >&2;} { (exit 1); exit 1; }; } fi if test "$FOUND" = "yes"; then { echo "$as_me:$LINENO: checking for library containing CRYPTO_set_locking_callback" >&5 echo $ECHO_N "checking for library containing CRYPTO_set_locking_callback... $ECHO_C" >&6; } if test "${ac_cv_search_CRYPTO_set_locking_callback+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char CRYPTO_set_locking_callback (); int main () { return CRYPTO_set_locking_callback (); ; return 0; } _ACEOF for ac_lib in '' crypto; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_CRYPTO_set_locking_callback=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_CRYPTO_set_locking_callback+set}" = set; then break fi done if test "${ac_cv_search_CRYPTO_set_locking_callback+set}" = set; then : else ac_cv_search_CRYPTO_set_locking_callback=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_CRYPTO_set_locking_callback" >&5 echo "${ECHO_T}$ac_cv_search_CRYPTO_set_locking_callback" >&6; } ac_res=$ac_cv_search_CRYPTO_set_locking_callback if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" { echo "$as_me:$LINENO: checking for library containing SSL_library_init" >&5 echo $ECHO_N "checking for library containing SSL_library_init... $ECHO_C" >&6; } if test "${ac_cv_search_SSL_library_init+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char SSL_library_init (); int main () { return SSL_library_init (); ; return 0; } _ACEOF for ac_lib in '' ssl; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_SSL_library_init=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_SSL_library_init+set}" = set; then break fi done if test "${ac_cv_search_SSL_library_init+set}" = set; then : else ac_cv_search_SSL_library_init=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_SSL_library_init" >&5 echo "${ECHO_T}$ac_cv_search_SSL_library_init" >&6; } ac_res=$ac_cv_search_SSL_library_init if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" FOUND=yes else FOUND=no fi else FOUND=no fi if test "$FOUND" = "no" -a "$TLSLIB" = "OpenSSL"; then { { echo "$as_me:$LINENO: error: Couldn't find OpenSSL library" >&5 echo "$as_me: error: Couldn't find OpenSSL library" >&2;} { (exit 1); exit 1; }; } fi if test "$FOUND" = "yes"; then TLSLIB="OpenSSL" cat >>confdefs.h <<\_ACEOF #define HAVE_OPENSSL 1 _ACEOF fi fi fi if test "$TLSLIB" = ""; then if test "$TLSHEADERS" = ""; then { { echo "$as_me:$LINENO: error: Couldn't find neither GnuTLS nor OpenSSL headers (gnutls.h or ssl.h)" >&5 echo "$as_me: error: Couldn't find neither GnuTLS nor OpenSSL headers (gnutls.h or ssl.h)" >&2;} { (exit 1); exit 1; }; } else { { echo "$as_me:$LINENO: error: Couldn't find neither GnuTLS nor OpenSSL library" >&5 echo "$as_me: error: Couldn't find neither GnuTLS nor OpenSSL library" >&2;} { (exit 1); exit 1; }; } fi fi else cat >>confdefs.h <<\_ACEOF #define DISABLE_TLS 1 _ACEOF fi { echo "$as_me:$LINENO: checking whether to use gzip" >&5 echo $ECHO_N "checking whether to use gzip... $ECHO_C" >&6; } # Check whether --enable-gzip was given. if test "${enable_gzip+set}" = set; then enableval=$enable_gzip; USEZLIB=$enableval else USEZLIB=yes fi { echo "$as_me:$LINENO: result: $USEZLIB" >&5 echo "${ECHO_T}$USEZLIB" >&6; } if test "$USEZLIB" = "yes"; then INCVAL="${LIBPREF}/include" LIBVAL="${LIBPREF}/lib" # Check whether --with-zlib_includes was given. if test "${with_zlib_includes+set}" = set; then withval=$with_zlib_includes; INCVAL="$withval" fi CPPFLAGS="${CPPFLAGS} -I${INCVAL}" # Check whether --with-zlib_libraries was given. if test "${with_zlib_libraries+set}" = set; then withval=$with_zlib_libraries; LIBVAL="$withval" fi LDFLAGS="${LDFLAGS} -L${LIBVAL}" if test "${ac_cv_header_zlib_h+set}" = set; then { echo "$as_me:$LINENO: checking for zlib.h" >&5 echo $ECHO_N "checking for zlib.h... $ECHO_C" >&6; } if test "${ac_cv_header_zlib_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 fi { echo "$as_me:$LINENO: result: $ac_cv_header_zlib_h" >&5 echo "${ECHO_T}$ac_cv_header_zlib_h" >&6; } else # Is the header compilable? { echo "$as_me:$LINENO: checking zlib.h usability" >&5 echo $ECHO_N "checking zlib.h usability... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then ac_header_compiler=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_compiler=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 echo "${ECHO_T}$ac_header_compiler" >&6; } # Is the header present? { echo "$as_me:$LINENO: checking zlib.h presence" >&5 echo $ECHO_N "checking zlib.h presence... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then ac_header_preproc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_preproc=no fi rm -f conftest.err conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 echo "${ECHO_T}$ac_header_preproc" >&6; } # So? What about this header? case $ac_header_compiler:$ac_header_preproc:$ac_cxx_preproc_warn_flag in yes:no: ) { echo "$as_me:$LINENO: WARNING: zlib.h: accepted by the compiler, rejected by the preprocessor!" >&5 echo "$as_me: WARNING: zlib.h: accepted by the compiler, rejected by the preprocessor!" >&2;} { echo "$as_me:$LINENO: WARNING: zlib.h: proceeding with the compiler's result" >&5 echo "$as_me: WARNING: zlib.h: proceeding with the compiler's result" >&2;} ac_header_preproc=yes ;; no:yes:* ) { echo "$as_me:$LINENO: WARNING: zlib.h: present but cannot be compiled" >&5 echo "$as_me: WARNING: zlib.h: present but cannot be compiled" >&2;} { echo "$as_me:$LINENO: WARNING: zlib.h: check for missing prerequisite headers?" >&5 echo "$as_me: WARNING: zlib.h: check for missing prerequisite headers?" >&2;} { echo "$as_me:$LINENO: WARNING: zlib.h: see the Autoconf documentation" >&5 echo "$as_me: WARNING: zlib.h: see the Autoconf documentation" >&2;} { echo "$as_me:$LINENO: WARNING: zlib.h: section \"Present But Cannot Be Compiled\"" >&5 echo "$as_me: WARNING: zlib.h: section \"Present But Cannot Be Compiled\"" >&2;} { echo "$as_me:$LINENO: WARNING: zlib.h: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: zlib.h: proceeding with the preprocessor's result" >&2;} { echo "$as_me:$LINENO: WARNING: zlib.h: in the future, the compiler will take precedence" >&5 echo "$as_me: WARNING: zlib.h: in the future, the compiler will take precedence" >&2;} ( cat <<\_ASBOX ## ------------------------------------------- ## ## Report this to hugbug@users.sourceforge.net ## ## ------------------------------------------- ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac { echo "$as_me:$LINENO: checking for zlib.h" >&5 echo $ECHO_N "checking for zlib.h... $ECHO_C" >&6; } if test "${ac_cv_header_zlib_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_cv_header_zlib_h=$ac_header_preproc fi { echo "$as_me:$LINENO: result: $ac_cv_header_zlib_h" >&5 echo "${ECHO_T}$ac_cv_header_zlib_h" >&6; } fi if test $ac_cv_header_zlib_h = yes; then : else { { echo "$as_me:$LINENO: error: \"zlib header files not found\"" >&5 echo "$as_me: error: \"zlib header files not found\"" >&2;} { (exit 1); exit 1; }; } fi { echo "$as_me:$LINENO: checking for library containing deflateBound" >&5 echo $ECHO_N "checking for library containing deflateBound... $ECHO_C" >&6; } if test "${ac_cv_search_deflateBound+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char deflateBound (); int main () { return deflateBound (); ; return 0; } _ACEOF for ac_lib in '' z; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_deflateBound=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_deflateBound+set}" = set; then break fi done if test "${ac_cv_search_deflateBound+set}" = set; then : else ac_cv_search_deflateBound=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_deflateBound" >&5 echo "${ECHO_T}$ac_cv_search_deflateBound" >&6; } ac_res=$ac_cv_search_deflateBound if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" else { { echo "$as_me:$LINENO: error: \"zlib library not found\"" >&5 echo "$as_me: error: \"zlib library not found\"" >&2;} { (exit 1); exit 1; }; } fi else cat >>confdefs.h <<\_ACEOF #define DISABLE_GZIP 1 _ACEOF fi { echo "$as_me:$LINENO: checking whether to use an empty SIGCHLD handler" >&5 echo $ECHO_N "checking whether to use an empty SIGCHLD handler... $ECHO_C" >&6; } # Check whether --enable-sigchld-handler was given. if test "${enable_sigchld_handler+set}" = set; then enableval=$enable_sigchld_handler; SIGCHLDHANDLER=$enableval else SIGCHLDHANDLER=yes fi { echo "$as_me:$LINENO: result: $SIGCHLDHANDLER" >&5 echo "${ECHO_T}$SIGCHLDHANDLER" >&6; } if test "$SIGCHLDHANDLER" = "yes"; then cat >>confdefs.h <<\_ACEOF #define SIGCHLD_HANDLER 1 _ACEOF fi { echo "$as_me:$LINENO: checking whether to include all debugging code" >&5 echo $ECHO_N "checking whether to include all debugging code... $ECHO_C" >&6; } # Check whether --enable-debug was given. if test "${enable_debug+set}" = set; then enableval=$enable_debug; ENABLEDEBUG=$enableval else ENABLEDEBUG=no fi { echo "$as_me:$LINENO: result: $ENABLEDEBUG" >&5 echo "${ECHO_T}$ENABLEDEBUG" >&6; } if test "$ENABLEDEBUG" = "yes"; then cat >>confdefs.h <<\_ACEOF #define DEBUG 1 _ACEOF if test "$CC" = "gcc"; then CXXFLAGS="-g -Wall" else CXXFLAGS="" fi { echo "$as_me:$LINENO: checking for macro returning current function name" >&5 echo $ECHO_N "checking for macro returning current function name... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include int main () { printf("%s\n", __FUNCTION__); ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then { echo "$as_me:$LINENO: result: __FUNCTION__" >&5 echo "${ECHO_T}__FUNCTION__" >&6; } FUNCTION_MACRO_NAME=__FUNCTION__ else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include int main () { printf("%s\n", __func__); ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then { echo "$as_me:$LINENO: result: __func__" >&5 echo "${ECHO_T}__func__" >&6; } FUNCTION_MACRO_NAME=__func__ else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { echo "$as_me:$LINENO: result: none" >&5 echo "${ECHO_T}none" >&6; } fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test "$FUNCTION_MACRO_NAME" != ""; then cat >>confdefs.h <<_ACEOF #define FUNCTION_MACRO_NAME $FUNCTION_MACRO_NAME _ACEOF fi { echo "$as_me:$LINENO: checking for variadic macros" >&5 echo $ECHO_N "checking for variadic macros... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF #define macro(...) macrofunc(__VA_ARGS__) int macrofunc(int a, int b) { return a + b; } int test() { return macro(1, 2); } _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then { echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6; } cat >>confdefs.h <<\_ACEOF #define HAVE_VARIADIC_MACROS 1 _ACEOF else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: checking for backtrace" >&5 echo $ECHO_N "checking for backtrace... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include #include int main () { void *array[100]; size_t size; char **strings; size = backtrace(array, 100); strings = backtrace_symbols(array, size); ; return 0; } _ACEOF rm -f conftest.$ac_objext if { (ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_compile") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest.$ac_objext; then FOUND=yes { echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6; } cat >>confdefs.h <<\_ACEOF #define HAVE_BACKTRACE 1 _ACEOF else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 FOUND=no { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: checking for rdynamic linker flag" >&5 echo $ECHO_N "checking for rdynamic linker flag... $ECHO_C" >&6; } old_LDFLAGS="$LDFLAGS" LDFLAGS="$LDFLAGS -rdynamic" cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ int main () { ; return 0; } _ACEOF rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then { echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6; } else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } LDFLAGS="$old_LDFLAGS" fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext conftest.$ac_ext fi ac_config_files="$ac_config_files Makefile" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure # scripts and configure runs, see configure's option --config-cache. # It is not useful on other systems. If it contains results you don't # want to keep, you may remove or edit it. # # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. # # `ac_cv_env_foo' variables (set or unset) will be overridden when # loading this file, other *unset* `ac_cv_foo' will be assigned the # following values. _ACEOF # The following way of writing the cache mishandles newlines in values, # but we know of no workaround that is simple, portable, and efficient. # So, we kill variables containing newlines. # Ultrix sh set writes to stderr and can't be redirected directly, # and sets the high bit in the cache file unless we assign to the vars. ( for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { echo "$as_me:$LINENO: WARNING: Cache variable $ac_var contains a newline." >&5 echo "$as_me: WARNING: Cache variable $ac_var contains a newline." >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( *) $as_unset $ac_var ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *) # `set' does not quote correctly, so add quotes (double-quote # substitution turns \\\\ into \\, and sed turns \\ into \). sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; #( *) # `set' quotes correctly as required by POSIX, so do not add quotes. sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) | sed ' /^ac_cv_env_/b end t clear :clear s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ t end s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ :end' >>confcache if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then test "x$cache_file" != "x/dev/null" && { echo "$as_me:$LINENO: updating cache $cache_file" >&5 echo "$as_me: updating cache $cache_file" >&6;} cat confcache >$cache_file else { echo "$as_me:$LINENO: not updating unwritable cache $cache_file" >&5 echo "$as_me: not updating unwritable cache $cache_file" >&6;} fi fi rm -f confcache test "x$prefix" = xNONE && prefix=$ac_default_prefix # Let make expand exec_prefix. test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' DEFS=-DHAVE_CONFIG_H ac_libobjs= ac_ltlibobjs= for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue # 1. Remove the extension, and $U if already installed. ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' ac_i=`echo "$ac_i" | sed "$ac_script"` # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR # will be set to the directory where LIBOBJS objects are built. ac_libobjs="$ac_libobjs \${LIBOBJDIR}$ac_i\$U.$ac_objext" ac_ltlibobjs="$ac_ltlibobjs \${LIBOBJDIR}$ac_i"'$U.lo' done LIBOBJS=$ac_libobjs LTLIBOBJS=$ac_ltlibobjs if test -z "${AMDEP_TRUE}" && test -z "${AMDEP_FALSE}"; then { { echo "$as_me:$LINENO: error: conditional \"AMDEP\" was never defined. Usually this means the macro was only invoked conditionally." >&5 echo "$as_me: error: conditional \"AMDEP\" was never defined. Usually this means the macro was only invoked conditionally." >&2;} { (exit 1); exit 1; }; } fi if test -z "${am__fastdepCXX_TRUE}" && test -z "${am__fastdepCXX_FALSE}"; then { { echo "$as_me:$LINENO: error: conditional \"am__fastdepCXX\" was never defined. Usually this means the macro was only invoked conditionally." >&5 echo "$as_me: error: conditional \"am__fastdepCXX\" was never defined. Usually this means the macro was only invoked conditionally." >&2;} { (exit 1); exit 1; }; } fi : ${CONFIG_STATUS=./config.status} ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files $CONFIG_STATUS" { echo "$as_me:$LINENO: creating $CONFIG_STATUS" >&5 echo "$as_me: creating $CONFIG_STATUS" >&6;} cat >$CONFIG_STATUS <<_ACEOF #! $SHELL # Generated by $as_me. # Run this file to recreate the current configuration. # Compiler output produced by configure, useful for debugging # configure, is in config.log if it exists. debug=false ac_cs_recheck=false ac_cs_silent=false SHELL=\${CONFIG_SHELL-$SHELL} _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF ## --------------------- ## ## M4sh Initialization. ## ## --------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then emulate sh NULLCMD=: # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in *posix*) set -o posix ;; esac fi # PATH needs CR # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits # The user is always right. if test "${PATH_SEPARATOR+set}" != set; then echo "#! /bin/sh" >conf$$.sh echo "exit 0" >>conf$$.sh chmod +x conf$$.sh if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then PATH_SEPARATOR=';' else PATH_SEPARATOR=: fi rm -f conf$$.sh fi # Support unset when possible. if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then as_unset=unset else as_unset=false fi # IFS # We need space, tab and new line, in precisely that order. Quoting is # there to prevent editors from complaining about space-tab. # (If _AS_PATH_WALK were called with IFS unset, it would disable word # splitting by setting IFS to empty value.) as_nl=' ' IFS=" "" $as_nl" # Find who we are. Look in the path if we contain no directory separator. case $0 in *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 { (exit 1); exit 1; } fi # Work around bugs in pre-3.0 UWIN ksh. for as_var in ENV MAIL MAILPATH do ($as_unset $as_var) >/dev/null 2>&1 && $as_unset $as_var done PS1='$ ' PS2='> ' PS4='+ ' # NLS nuisances. for as_var in \ LANG LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_CTYPE LC_IDENTIFICATION \ LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER \ LC_TELEPHONE LC_TIME do if (set +x; test -z "`(eval $as_var=C; export $as_var) 2>&1`"); then eval $as_var=C; export $as_var else ($as_unset $as_var) >/dev/null 2>&1 && $as_unset $as_var fi done # Required to use basename. if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi # Name of the executable. as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # CDPATH. $as_unset CDPATH as_lineno_1=$LINENO as_lineno_2=$LINENO test "x$as_lineno_1" != "x$as_lineno_2" && test "x`expr $as_lineno_1 + 1`" = "x$as_lineno_2" || { # Create $as_me.lineno as a copy of $as_myself, but with $LINENO # uniformly replaced by the line number. The first 'sed' inserts a # line-number line after each line using $LINENO; the second 'sed' # does the real work. The second script uses 'N' to pair each # line-number line with the line containing $LINENO, and appends # trailing '-' during substitution so that $LINENO is not a special # case at line end. # (Raja R Harinath suggested sed '=', and Paul Eggert wrote the # scripts with optimization help from Paolo Bonzini. Blame Lee # E. McMahon (1931-1989) for sed's syntax. :-) sed -n ' p /[$]LINENO/= ' <$as_myself | sed ' s/[$]LINENO.*/&-/ t lineno b :lineno N :loop s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ t loop s/-\n.*// ' >$as_me.lineno && chmod +x "$as_me.lineno" || { echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2 { (exit 1); exit 1; }; } # Don't try to exec as it changes $[0], causing all sort of problems # (the dirname of $[0] is not the place where we might find the # original and so on. Autoconf is especially sensitive to this). . "./$as_me.lineno" # Exit status is that of the last command. exit } if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in -n*) case `echo 'x\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. *) ECHO_C='\c';; esac;; *) ECHO_N='-n';; esac if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir fi echo >conf$$.file if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -p'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -p' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -p' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null if mkdir -p . 2>/dev/null; then as_mkdir_p=: else test -d ./-p && rmdir ./-p as_mkdir_p=false fi if test -x / >/dev/null 2>&1; then as_test_x='test -x' else if ls -dL / >/dev/null 2>&1; then as_ls_L_option=L else as_ls_L_option= fi as_test_x=' eval sh -c '\'' if test -d "$1"; then test -d "$1/."; else case $1 in -*)set "./$1";; esac; case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in ???[sx]*):;;*)false;;esac;fi '\'' sh ' fi as_executable_p=$as_test_x # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" exec 6>&1 # Save the log message, to keep $[0] and so on meaningful, and to # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" This file was extended by nzbget $as_me 12.0, which was generated by GNU Autoconf 2.61. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS CONFIG_LINKS = $CONFIG_LINKS CONFIG_COMMANDS = $CONFIG_COMMANDS $ $0 $@ on `(hostname || uname -n) 2>/dev/null | sed 1q` " _ACEOF cat >>$CONFIG_STATUS <<_ACEOF # Files that config.status was made for. config_files="$ac_config_files" config_headers="$ac_config_headers" config_commands="$ac_config_commands" _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF ac_cs_usage="\ \`$as_me' instantiates files from templates according to the current configuration. Usage: $0 [OPTIONS] [FILE]... -h, --help print this help, then exit -V, --version print version number and configuration settings, then exit -q, --quiet do not print progress messages -d, --debug don't remove temporary files --recheck update $as_me by reconfiguring in the same conditions --file=FILE[:TEMPLATE] instantiate the configuration file FILE --header=FILE[:TEMPLATE] instantiate the configuration header FILE Configuration files: $config_files Configuration headers: $config_headers Configuration commands: $config_commands Report bugs to ." _ACEOF cat >>$CONFIG_STATUS <<_ACEOF ac_cs_version="\\ nzbget config.status 12.0 configured by $0, generated by GNU Autoconf 2.61, with options \\"`echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`\\" Copyright (C) 2006 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." ac_pwd='$ac_pwd' srcdir='$srcdir' INSTALL='$INSTALL' _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF # If no file are specified by the user, then we need to provide default # value. By we need to know if files were specified by the user. ac_need_defaults=: while test $# != 0 do case $1 in --*=*) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` ac_shift=: ;; *) ac_option=$1 ac_optarg=$2 ac_shift=shift ;; esac case $ac_option in # Handling of the options. -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) ac_cs_recheck=: ;; --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) echo "$ac_cs_version"; exit ;; --debug | --debu | --deb | --de | --d | -d ) debug=: ;; --file | --fil | --fi | --f ) $ac_shift CONFIG_FILES="$CONFIG_FILES $ac_optarg" ac_need_defaults=false;; --header | --heade | --head | --hea ) $ac_shift CONFIG_HEADERS="$CONFIG_HEADERS $ac_optarg" ac_need_defaults=false;; --he | --h) # Conflict between --help and --header { echo "$as_me: error: ambiguous option: $1 Try \`$0 --help' for more information." >&2 { (exit 1); exit 1; }; };; --help | --hel | -h ) echo "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil | --si | --s) ac_cs_silent=: ;; # This is an error. -*) { echo "$as_me: error: unrecognized option: $1 Try \`$0 --help' for more information." >&2 { (exit 1); exit 1; }; } ;; *) ac_config_targets="$ac_config_targets $1" ac_need_defaults=false ;; esac shift done ac_configure_extra_args= if $ac_cs_silent; then exec 6>/dev/null ac_configure_extra_args="$ac_configure_extra_args --silent" fi _ACEOF cat >>$CONFIG_STATUS <<_ACEOF if \$ac_cs_recheck; then echo "running CONFIG_SHELL=$SHELL $SHELL $0 "$ac_configure_args \$ac_configure_extra_args " --no-create --no-recursion" >&6 CONFIG_SHELL=$SHELL export CONFIG_SHELL exec $SHELL "$0"$ac_configure_args \$ac_configure_extra_args --no-create --no-recursion fi _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF exec 5>>config.log { echo sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX ## Running $as_me. ## _ASBOX echo "$ac_log" } >&5 _ACEOF cat >>$CONFIG_STATUS <<_ACEOF # # INIT-COMMANDS # AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir" _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF # Handling of arguments. for ac_config_target in $ac_config_targets do case $ac_config_target in "config.h") CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;; "depfiles") CONFIG_COMMANDS="$CONFIG_COMMANDS depfiles" ;; "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; *) { { echo "$as_me:$LINENO: error: invalid argument: $ac_config_target" >&5 echo "$as_me: error: invalid argument: $ac_config_target" >&2;} { (exit 1); exit 1; }; };; esac done # If the user did not use the arguments to specify the items to instantiate, # then the envvar interface is used. Set only those that are not. # We use the long form for the default assignment because of an extremely # bizarre bug on SunOS 4.1.3. if $ac_need_defaults; then test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files test "${CONFIG_HEADERS+set}" = set || CONFIG_HEADERS=$config_headers test "${CONFIG_COMMANDS+set}" = set || CONFIG_COMMANDS=$config_commands fi # Have a temporary directory for convenience. Make it in the build tree # simply because there is no reason against having it here, and in addition, # creating and moving files from /tmp can sometimes cause problems. # Hook for its removal unless debugging. # Note that there is a small window in which the directory will not be cleaned: # after its creation but before its name has been assigned to `$tmp'. $debug || { tmp= trap 'exit_status=$? { test -z "$tmp" || test ! -d "$tmp" || rm -fr "$tmp"; } && exit $exit_status ' 0 trap '{ (exit 1); exit 1; }' 1 2 13 15 } # Create a (secure) tmp directory for tmp files. { tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" } || { tmp=./conf$$-$RANDOM (umask 077 && mkdir "$tmp") } || { echo "$me: cannot create a temporary directory in ." >&2 { (exit 1); exit 1; } } # # Set up the sed scripts for CONFIG_FILES section. # # No need to generate the scripts if there are no CONFIG_FILES. # This happens for instance when ./config.status config.h if test -n "$CONFIG_FILES"; then _ACEOF ac_delim='%!_!# ' for ac_last_try in false false false false false :; do cat >conf$$subs.sed <<_ACEOF SHELL!$SHELL$ac_delim PATH_SEPARATOR!$PATH_SEPARATOR$ac_delim PACKAGE_NAME!$PACKAGE_NAME$ac_delim PACKAGE_TARNAME!$PACKAGE_TARNAME$ac_delim PACKAGE_VERSION!$PACKAGE_VERSION$ac_delim PACKAGE_STRING!$PACKAGE_STRING$ac_delim PACKAGE_BUGREPORT!$PACKAGE_BUGREPORT$ac_delim exec_prefix!$exec_prefix$ac_delim prefix!$prefix$ac_delim program_transform_name!$program_transform_name$ac_delim bindir!$bindir$ac_delim sbindir!$sbindir$ac_delim libexecdir!$libexecdir$ac_delim datarootdir!$datarootdir$ac_delim datadir!$datadir$ac_delim sysconfdir!$sysconfdir$ac_delim sharedstatedir!$sharedstatedir$ac_delim localstatedir!$localstatedir$ac_delim includedir!$includedir$ac_delim oldincludedir!$oldincludedir$ac_delim docdir!$docdir$ac_delim infodir!$infodir$ac_delim htmldir!$htmldir$ac_delim dvidir!$dvidir$ac_delim pdfdir!$pdfdir$ac_delim psdir!$psdir$ac_delim libdir!$libdir$ac_delim localedir!$localedir$ac_delim mandir!$mandir$ac_delim DEFS!$DEFS$ac_delim ECHO_C!$ECHO_C$ac_delim ECHO_N!$ECHO_N$ac_delim ECHO_T!$ECHO_T$ac_delim LIBS!$LIBS$ac_delim build_alias!$build_alias$ac_delim host_alias!$host_alias$ac_delim target_alias!$target_alias$ac_delim build!$build$ac_delim build_cpu!$build_cpu$ac_delim build_vendor!$build_vendor$ac_delim build_os!$build_os$ac_delim host!$host$ac_delim host_cpu!$host_cpu$ac_delim host_vendor!$host_vendor$ac_delim host_os!$host_os$ac_delim target!$target$ac_delim target_cpu!$target_cpu$ac_delim target_vendor!$target_vendor$ac_delim target_os!$target_os$ac_delim INSTALL_PROGRAM!$INSTALL_PROGRAM$ac_delim INSTALL_SCRIPT!$INSTALL_SCRIPT$ac_delim INSTALL_DATA!$INSTALL_DATA$ac_delim CYGPATH_W!$CYGPATH_W$ac_delim PACKAGE!$PACKAGE$ac_delim VERSION!$VERSION$ac_delim ACLOCAL!$ACLOCAL$ac_delim AUTOCONF!$AUTOCONF$ac_delim AUTOMAKE!$AUTOMAKE$ac_delim AUTOHEADER!$AUTOHEADER$ac_delim MAKEINFO!$MAKEINFO$ac_delim install_sh!$install_sh$ac_delim STRIP!$STRIP$ac_delim INSTALL_STRIP_PROGRAM!$INSTALL_STRIP_PROGRAM$ac_delim mkdir_p!$mkdir_p$ac_delim AWK!$AWK$ac_delim SET_MAKE!$SET_MAKE$ac_delim am__leading_dot!$am__leading_dot$ac_delim AMTAR!$AMTAR$ac_delim am__tar!$am__tar$ac_delim am__untar!$am__untar$ac_delim CXX!$CXX$ac_delim CXXFLAGS!$CXXFLAGS$ac_delim LDFLAGS!$LDFLAGS$ac_delim CPPFLAGS!$CPPFLAGS$ac_delim ac_ct_CXX!$ac_ct_CXX$ac_delim EXEEXT!$EXEEXT$ac_delim OBJEXT!$OBJEXT$ac_delim DEPDIR!$DEPDIR$ac_delim am__include!$am__include$ac_delim am__quote!$am__quote$ac_delim AMDEP_TRUE!$AMDEP_TRUE$ac_delim AMDEP_FALSE!$AMDEP_FALSE$ac_delim AMDEPBACKSLASH!$AMDEPBACKSLASH$ac_delim CXXDEPMODE!$CXXDEPMODE$ac_delim am__fastdepCXX_TRUE!$am__fastdepCXX_TRUE$ac_delim am__fastdepCXX_FALSE!$am__fastdepCXX_FALSE$ac_delim TAR!$TAR$ac_delim MAKE!$MAKE$ac_delim CXXCPP!$CXXCPP$ac_delim GREP!$GREP$ac_delim EGREP!$EGREP$ac_delim PKG_CONFIG!$PKG_CONFIG$ac_delim libxml2_CFLAGS!$libxml2_CFLAGS$ac_delim libxml2_LIBS!$libxml2_LIBS$ac_delim libsigc_CFLAGS!$libsigc_CFLAGS$ac_delim libsigc_LIBS!$libsigc_LIBS$ac_delim openssl_CFLAGS!$openssl_CFLAGS$ac_delim _ACEOF if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 97; then break elif $ac_last_try; then { { echo "$as_me:$LINENO: error: could not make $CONFIG_STATUS" >&5 echo "$as_me: error: could not make $CONFIG_STATUS" >&2;} { (exit 1); exit 1; }; } else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done ac_eof=`sed -n '/^CEOF[0-9]*$/s/CEOF/0/p' conf$$subs.sed` if test -n "$ac_eof"; then ac_eof=`echo "$ac_eof" | sort -nru | sed 1q` ac_eof=`expr $ac_eof + 1` fi cat >>$CONFIG_STATUS <<_ACEOF cat >"\$tmp/subs-1.sed" <<\CEOF$ac_eof /@[a-zA-Z_][a-zA-Z_0-9]*@/!b _ACEOF sed ' s/[,\\&]/\\&/g; s/@/@|#_!!_#|/g s/^/s,@/; s/!/@,|#_!!_#|/ :n t n s/'"$ac_delim"'$/,g/; t s/$/\\/; p N; s/^.*\n//; s/[,\\&]/\\&/g; s/@/@|#_!!_#|/g; b n ' >>$CONFIG_STATUS >$CONFIG_STATUS <<_ACEOF CEOF$ac_eof _ACEOF ac_delim='%!_!# ' for ac_last_try in false false false false false :; do cat >conf$$subs.sed <<_ACEOF openssl_LIBS!$openssl_LIBS$ac_delim LIBOBJS!$LIBOBJS$ac_delim LTLIBOBJS!$LTLIBOBJS$ac_delim _ACEOF if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 3; then break elif $ac_last_try; then { { echo "$as_me:$LINENO: error: could not make $CONFIG_STATUS" >&5 echo "$as_me: error: could not make $CONFIG_STATUS" >&2;} { (exit 1); exit 1; }; } else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done ac_eof=`sed -n '/^CEOF[0-9]*$/s/CEOF/0/p' conf$$subs.sed` if test -n "$ac_eof"; then ac_eof=`echo "$ac_eof" | sort -nru | sed 1q` ac_eof=`expr $ac_eof + 1` fi cat >>$CONFIG_STATUS <<_ACEOF cat >"\$tmp/subs-2.sed" <<\CEOF$ac_eof /@[a-zA-Z_][a-zA-Z_0-9]*@/!b end _ACEOF sed ' s/[,\\&]/\\&/g; s/@/@|#_!!_#|/g s/^/s,@/; s/!/@,|#_!!_#|/ :n t n s/'"$ac_delim"'$/,g/; t s/$/\\/; p N; s/^.*\n//; s/[,\\&]/\\&/g; s/@/@|#_!!_#|/g; b n ' >>$CONFIG_STATUS >$CONFIG_STATUS <<_ACEOF :end s/|#_!!_#|//g CEOF$ac_eof _ACEOF # VPATH may cause trouble with some makes, so we remove $(srcdir), # ${srcdir} and @srcdir@ from VPATH if srcdir is ".", strip leading and # trailing colons and then remove the whole line if VPATH becomes empty # (actually we leave an empty line to preserve line numbers). if test "x$srcdir" = x.; then ac_vpsub='/^[ ]*VPATH[ ]*=/{ s/:*\$(srcdir):*/:/ s/:*\${srcdir}:*/:/ s/:*@srcdir@:*/:/ s/^\([^=]*=[ ]*\):*/\1/ s/:*$// s/^[^=]*=[ ]*$// }' fi cat >>$CONFIG_STATUS <<\_ACEOF fi # test -n "$CONFIG_FILES" for ac_tag in :F $CONFIG_FILES :H $CONFIG_HEADERS :C $CONFIG_COMMANDS do case $ac_tag in :[FHLC]) ac_mode=$ac_tag; continue;; esac case $ac_mode$ac_tag in :[FHL]*:*);; :L* | :C*:*) { { echo "$as_me:$LINENO: error: Invalid tag $ac_tag." >&5 echo "$as_me: error: Invalid tag $ac_tag." >&2;} { (exit 1); exit 1; }; };; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac ac_save_IFS=$IFS IFS=: set x $ac_tag IFS=$ac_save_IFS shift ac_file=$1 shift case $ac_mode in :L) ac_source=$1;; :[FH]) ac_file_inputs= for ac_f do case $ac_f in -) ac_f="$tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style, # because $ac_f cannot contain `:'. test -f "$ac_f" || case $ac_f in [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || { { echo "$as_me:$LINENO: error: cannot find input file: $ac_f" >&5 echo "$as_me: error: cannot find input file: $ac_f" >&2;} { (exit 1); exit 1; }; };; esac ac_file_inputs="$ac_file_inputs $ac_f" done # Let's still pretend it is `configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input="Generated from "`IFS=: echo $* | sed 's|^[^:]*/||;s|:[^:]*/|, |g'`" by configure." if test x"$ac_file" != x-; then configure_input="$ac_file. $configure_input" { echo "$as_me:$LINENO: creating $ac_file" >&5 echo "$as_me: creating $ac_file" >&6;} fi case $ac_tag in *:-:* | *:-) cat >"$tmp/stdin";; esac ;; esac ac_dir=`$as_dirname -- "$ac_file" || $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_file" : 'X\(//\)[^/]' \| \ X"$ac_file" : 'X\(//\)$' \| \ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || echo X"$ac_file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` { as_dir="$ac_dir" case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || { $as_mkdir_p && mkdir -p "$as_dir"; } || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || echo X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || { { echo "$as_me:$LINENO: error: cannot create directory $as_dir" >&5 echo "$as_me: error: cannot create directory $as_dir" >&2;} { (exit 1); exit 1; }; }; } ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`echo "$ac_dir" | sed 's,^\.[\\/],,'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`echo "$ac_dir_suffix" | sed 's,/[^\\/]*,/..,g;s,/,,'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix case $ac_mode in :F) # # CONFIG_FILE # case $INSTALL in [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;; *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;; esac _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF # If the template does not know about datarootdir, expand it. # FIXME: This hack should be removed a few years after 2.60. ac_datarootdir_hack=; ac_datarootdir_seen= case `sed -n '/datarootdir/ { p q } /@datadir@/p /@docdir@/p /@infodir@/p /@localedir@/p /@mandir@/p ' $ac_file_inputs` in *datarootdir*) ac_datarootdir_seen=yes;; *@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) { echo "$as_me:$LINENO: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} _ACEOF cat >>$CONFIG_STATUS <<_ACEOF ac_datarootdir_hack=' s&@datadir@&$datadir&g s&@docdir@&$docdir&g s&@infodir@&$infodir&g s&@localedir@&$localedir&g s&@mandir@&$mandir&g s&\\\${datarootdir}&$datarootdir&g' ;; esac _ACEOF # Neutralize VPATH when `$srcdir' = `.'. # Shell code in configure.ac might set extrasub. # FIXME: do we really want to maintain this feature? cat >>$CONFIG_STATUS <<_ACEOF sed "$ac_vpsub $extrasub _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF :t /@[a-zA-Z_][a-zA-Z_0-9]*@/!b s&@configure_input@&$configure_input&;t t s&@top_builddir@&$ac_top_builddir_sub&;t t s&@srcdir@&$ac_srcdir&;t t s&@abs_srcdir@&$ac_abs_srcdir&;t t s&@top_srcdir@&$ac_top_srcdir&;t t s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t s&@builddir@&$ac_builddir&;t t s&@abs_builddir@&$ac_abs_builddir&;t t s&@abs_top_builddir@&$ac_abs_top_builddir&;t t s&@INSTALL@&$ac_INSTALL&;t t $ac_datarootdir_hack " $ac_file_inputs | sed -f "$tmp/subs-1.sed" | sed -f "$tmp/subs-2.sed" >$tmp/out test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && { ac_out=`sed -n '/\${datarootdir}/p' "$tmp/out"`; test -n "$ac_out"; } && { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' "$tmp/out"`; test -z "$ac_out"; } && { echo "$as_me:$LINENO: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined." >&5 echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined." >&2;} rm -f "$tmp/stdin" case $ac_file in -) cat "$tmp/out"; rm -f "$tmp/out";; *) rm -f "$ac_file"; mv "$tmp/out" $ac_file;; esac ;; :H) # # CONFIG_HEADER # _ACEOF # Transform confdefs.h into a sed script `conftest.defines', that # substitutes the proper values into config.h.in to produce config.h. rm -f conftest.defines conftest.tail # First, append a space to every undef/define line, to ease matching. echo 's/$/ /' >conftest.defines # Then, protect against being on the right side of a sed subst, or in # an unquoted here document, in config.status. If some macros were # called several times there might be several #defines for the same # symbol, which is useless. But do not sort them, since the last # AC_DEFINE must be honored. ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]* # These sed commands are passed to sed as "A NAME B PARAMS C VALUE D", where # NAME is the cpp macro being defined, VALUE is the value it is being given. # PARAMS is the parameter list in the macro definition--in most cases, it's # just an empty string. ac_dA='s,^\\([ #]*\\)[^ ]*\\([ ]*' ac_dB='\\)[ (].*,\\1define\\2' ac_dC=' ' ac_dD=' ,' uniq confdefs.h | sed -n ' t rset :rset s/^[ ]*#[ ]*define[ ][ ]*// t ok d :ok s/[\\&,]/\\&/g s/^\('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/ '"$ac_dA"'\1'"$ac_dB"'\2'"${ac_dC}"'\3'"$ac_dD"'/p s/^\('"$ac_word_re"'\)[ ]*\(.*\)/'"$ac_dA"'\1'"$ac_dB$ac_dC"'\2'"$ac_dD"'/p ' >>conftest.defines # Remove the space that was appended to ease matching. # Then replace #undef with comments. This is necessary, for # example, in the case of _POSIX_SOURCE, which is predefined and required # on some systems where configure will not decide to define it. # (The regexp can be short, since the line contains either #define or #undef.) echo 's/ $// s,^[ #]*u.*,/* & */,' >>conftest.defines # Break up conftest.defines: ac_max_sed_lines=50 # First sed command is: sed -f defines.sed $ac_file_inputs >"$tmp/out1" # Second one is: sed -f defines.sed "$tmp/out1" >"$tmp/out2" # Third one will be: sed -f defines.sed "$tmp/out2" >"$tmp/out1" # et cetera. ac_in='$ac_file_inputs' ac_out='"$tmp/out1"' ac_nxt='"$tmp/out2"' while : do # Write a here document: cat >>$CONFIG_STATUS <<_ACEOF # First, check the format of the line: cat >"\$tmp/defines.sed" <<\\CEOF /^[ ]*#[ ]*undef[ ][ ]*$ac_word_re[ ]*\$/b def /^[ ]*#[ ]*define[ ][ ]*$ac_word_re[( ]/b def b :def _ACEOF sed ${ac_max_sed_lines}q conftest.defines >>$CONFIG_STATUS echo 'CEOF sed -f "$tmp/defines.sed"' "$ac_in >$ac_out" >>$CONFIG_STATUS ac_in=$ac_out; ac_out=$ac_nxt; ac_nxt=$ac_in sed 1,${ac_max_sed_lines}d conftest.defines >conftest.tail grep . conftest.tail >/dev/null || break rm -f conftest.defines mv conftest.tail conftest.defines done rm -f conftest.defines conftest.tail echo "ac_result=$ac_in" >>$CONFIG_STATUS cat >>$CONFIG_STATUS <<\_ACEOF if test x"$ac_file" != x-; then echo "/* $configure_input */" >"$tmp/config.h" cat "$ac_result" >>"$tmp/config.h" if diff $ac_file "$tmp/config.h" >/dev/null 2>&1; then { echo "$as_me:$LINENO: $ac_file is unchanged" >&5 echo "$as_me: $ac_file is unchanged" >&6;} else rm -f $ac_file mv "$tmp/config.h" $ac_file fi else echo "/* $configure_input */" cat "$ac_result" fi rm -f "$tmp/out12" # Compute $ac_file's index in $config_headers. _am_stamp_count=1 for _am_header in $config_headers :; do case $_am_header in $ac_file | $ac_file:* ) break ;; * ) _am_stamp_count=`expr $_am_stamp_count + 1` ;; esac done echo "timestamp for $ac_file" >`$as_dirname -- $ac_file || $as_expr X$ac_file : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X$ac_file : 'X\(//\)[^/]' \| \ X$ac_file : 'X\(//\)$' \| \ X$ac_file : 'X\(/\)' \| . 2>/dev/null || echo X$ac_file | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'`/stamp-h$_am_stamp_count ;; :C) { echo "$as_me:$LINENO: executing $ac_file commands" >&5 echo "$as_me: executing $ac_file commands" >&6;} ;; esac case $ac_file$ac_mode in "depfiles":C) test x"$AMDEP_TRUE" != x"" || for mf in $CONFIG_FILES; do # Strip MF so we end up with the name of the file. mf=`echo "$mf" | sed -e 's/:.*$//'` # Check whether this is an Automake generated Makefile or not. # We used to match only the files named `Makefile.in', but # 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 dirpart=`$as_dirname -- "$mf" || $as_expr X"$mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$mf" : 'X\(//\)[^/]' \| \ X"$mf" : 'X\(//\)$' \| \ X"$mf" : 'X\(/\)' \| . 2>/dev/null || echo X"$mf" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` else continue fi # Extract the definition of DEPDIR, am__include, and am__quote # from the Makefile without running `make'. DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"` test -z "$DEPDIR" && continue am__include=`sed -n 's/^am__include = //p' < "$mf"` test -z "am__include" && continue am__quote=`sed -n 's/^am__quote = //p' < "$mf"` # When using ansi2knr, U may be empty or an underscore; expand it U=`sed -n 's/^U = //p' < "$mf"` # Find all dependency output files, they are included files with # $(DEPDIR) in their names. We invoke sed twice because it is the # simplest approach to changing $(DEPDIR) to its actual value in the # expansion. for file in `sed -n " s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \ sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g' -e 's/\$U/'"$U"'/g'`; do # Make sure the directory exists. test -f "$dirpart/$file" && continue fdir=`$as_dirname -- "$file" || $as_expr X"$file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$file" : 'X\(//\)[^/]' \| \ X"$file" : 'X\(//\)$' \| \ X"$file" : 'X\(/\)' \| . 2>/dev/null || echo X"$file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` { as_dir=$dirpart/$fdir case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || { $as_mkdir_p && mkdir -p "$as_dir"; } || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || echo X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || { { echo "$as_me:$LINENO: error: cannot create directory $as_dir" >&5 echo "$as_me: error: cannot create directory $as_dir" >&2;} { (exit 1); exit 1; }; }; } # echo "creating $dirpart/$file" echo '# dummy' > "$dirpart/$file" done done ;; esac done # for ac_tag { (exit 0); exit 0; } _ACEOF chmod +x $CONFIG_STATUS ac_clean_files=$ac_clean_files_save # configure is writing to config.log, and then calls config.status. # config.status does its own redirection, appending to config.log. # Unfortunately, on DOS this fails, as config.log is still kept open # by configure, so config.status won't be able to write to it; its # output is simply discarded. So we exec the FD to /dev/null, # effectively closing config.log, so it can be properly (re)opened and # appended to by config.status. When coming back to configure, we # need to make the FD available again. if test "$no_create" != yes; then ac_cs_success=: ac_config_status_args= test "$silent" = yes && ac_config_status_args="$ac_config_status_args --quiet" exec 5>/dev/null $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false exec 5>>config.log # Use ||, not &&, to avoid exiting from the if with $? = 1, which # would make configure fail if this is the last instruction. $ac_cs_success || { (exit 1); exit 1; } fi nzbget-12.0+dfsg/configure.ac000066400000000000000000000431531226450633000161410ustar00rootroot00000000000000# -*- Autoconf -*- # Process this file with autoconf to produce a configure script. AC_PREREQ(2.59) AC_INIT(nzbget, 12.0, hugbug@users.sourceforge.net) AC_CANONICAL_SYSTEM AM_INIT_AUTOMAKE(nzbget, 12.0) AC_CONFIG_SRCDIR([nzbget.cpp]) AC_CONFIG_HEADERS([config.h]) dnl dnl Set default library path, if not specified in environment variable "LIBPREF". dnl if test "$LIBPREF" = ""; then LIBPREF="/usr" fi dnl dnl Check for programs. dnl AC_PROG_CXX AC_PATH_PROG(TAR, tar, $FALSE) AC_PATH_PROG(MAKE, make, $FALSE) AC_PROG_INSTALL dnl dnl Do all tests with c++ compiler. dnl AC_LANG(C++) dnl dnl Checks for header files. dnl AC_CHECK_HEADERS(sys/prctl.h) AC_CHECK_HEADERS(regex.h) dnl dnl Check for libs dnl AC_SEARCH_LIBS([pthread_create], [pthread]) AC_SEARCH_LIBS([socket], [socket]) AC_SEARCH_LIBS([inet_addr], [nsl]) AC_SEARCH_LIBS([hstrerror], [resolv]) dnl dnl Getopt dnl AC_CHECK_FUNC(getopt_long, [AC_DEFINE([HAVE_GETOPT_LONG], 1, [Define to 1 if getopt_long is supported])],) dnl dnl use 64-Bits for file sizes dnl AC_SYS_LARGEFILE dnl dnl check ctime_r dnl AC_MSG_CHECKING(for ctime_r) AC_TRY_COMPILE( [#include ], [ time_t clock; char buf[26]; ctime_r(&clock, buf, 26); ], AC_MSG_RESULT([[yes, and it takes 3 arguments]]) FOUND="yes" AC_DEFINE([HAVE_CTIME_R_3], 1, [Define to 1 if ctime_r takes 3 arguments]), FOUND="no") if test "$FOUND" = "no"; then AC_TRY_COMPILE( [#include ], [ time_t clock; char buf[26]; ctime_r(&clock, buf); ], AC_MSG_RESULT([[yes, and it takes 2 arguments]]) FOUND="yes" AC_DEFINE([HAVE_CTIME_R_2], 1, [Define to 1 if ctime_r takes 2 arguments]), FOUND="no") fi if test "$FOUND" = "no"; then AC_MSG_RESULT([no]) AC_MSG_ERROR("function ctime_r not found") fi dnl dnl check getaddrinfo dnl AC_CHECK_FUNC(getaddrinfo, FOUND="yes" [AC_DEFINE([HAVE_GETADDRINFO], 1, [Define to 1 if getaddrinfo is supported])] AC_SEARCH_LIBS([getaddrinfo], [nsl]), FOUND="no") dnl dnl check gethostbyname_r, if getaddrinfo is not available dnl if test "$FOUND" = "no"; then AC_MSG_CHECKING(for gethostbyname_r) AC_TRY_COMPILE( [#include ], [ char* szHost; struct hostent hinfobuf; char* strbuf; int h_errnop; struct hostent* hinfo = gethostbyname_r(szHost, &hinfobuf, strbuf, 1024, &h_errnop); ], AC_MSG_RESULT([[yes, and it takes 5 arguments]]) FOUND="yes" AC_DEFINE([HAVE_GETHOSTBYNAME_R_5], 1, [Define to 1 if gethostbyname_r takes 5 arguments]), FOUND="no") if test "$FOUND" = "no"; then AC_TRY_COMPILE( [#include ], [ char* szHost; struct hostent* hinfo; struct hostent hinfobuf; char* strbuf; int h_errnop; int err = gethostbyname_r(szHost, &hinfobuf, strbuf, 1024, &hinfo, &h_errnop); ], AC_MSG_RESULT([[yes, and it takes 6 arguments]]) FOUND="yes" AC_DEFINE([HAVE_GETHOSTBYNAME_R_6], 1, [Define to 1 if gethostbyname_r takes 6 arguments]), FOUND="no") fi if test "$FOUND" = "no"; then AC_TRY_COMPILE( [#include ], [ char* szHost; struct hostent hinfo; struct hostent_data hinfobuf; int err = gethostbyname_r(szHost, &hinfo, &hinfobuf); ], AC_MSG_RESULT([[yes, and it takes 3 arguments]]) FOUND="yes" AC_DEFINE([HAVE_GETHOSTBYNAME_R_3], 1, [Define to 1 if gethostbyname_r takes 3 arguments]), AC_MSG_RESULT([[no]]) FOUND="no") fi if test "$FOUND" = "yes"; then AC_DEFINE([HAVE_GETHOSTBYNAME_R], 1, [Define to 1 if gethostbyname_r is supported]) AC_SEARCH_LIBS([gethostbyname_r], [nsl]) fi fi dnl dnl cCheck if spinlocks are available dnl AC_CHECK_FUNC(pthread_spin_init, [AC_DEFINE([HAVE_SPINLOCK], 1, [Define to 1 if spinlocks are supported])] AC_SEARCH_LIBS([pthread_spin_init], [pthread]),) dnl dnl Determine what socket length (socklen_t) data type is dnl AC_MSG_CHECKING([for type of socket length (socklen_t)]) AC_TRY_COMPILE([ #include #include #include ],[ (void)getsockopt (1, 1, 1, NULL, (socklen_t*)NULL)],[ AC_MSG_RESULT(socklen_t) SOCKLEN_T=socklen_t],[ AC_TRY_COMPILE([ #include #include #include ],[ (void)getsockopt (1, 1, 1, NULL, (size_t*)NULL)],[ AC_MSG_RESULT(size_t) SOCKLEN_T=size_t],[ AC_TRY_COMPILE([ #include #include #include ],[ (void)getsockopt (1, 1, 1, NULL, (int*)NULL)],[ AC_MSG_RESULT(int) SOCKLEN_T=int],[ AC_MSG_WARN(could not determine) SOCKLEN_T=int])])]) AC_DEFINE_UNQUOTED(SOCKLEN_T, $SOCKLEN_T, [Determine what socket length (socklen_t) data type is]) dnl dnl checks for libxml2 includes and libraries. dnl AC_ARG_WITH(libxml2_includes, [AS_HELP_STRING([--with-libxml2-includes=DIR], [libxml2 include directory])], [CPPFLAGS="${CPPFLAGS} -I${withval}"] [INCVAL="yes"], [INCVAL="no"]) AC_ARG_WITH(libxml2_libraries, [AS_HELP_STRING([--with-libxml2-libraries=DIR], [libxml2 library directory])], [LDFLAGS="${LDFLAGS} -L${withval}"] [LIBVAL="yes"], [LIBVAL="no"]) if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then PKG_CHECK_MODULES(libxml2, libxml-2.0, [LIBS="${LIBS} $libxml2_LIBS"] [CPPFLAGS="${CPPFLAGS} $libxml2_CFLAGS"]) fi AC_CHECK_HEADER(libxml/tree.h,, AC_MSG_ERROR("libxml2 header files not found")) AC_SEARCH_LIBS([xmlNewNode], [xml2], , AC_MSG_ERROR("libxml2 library not found")) dnl dnl Use curses. Deafult: yes dnl AC_MSG_CHECKING(whether to use curses) AC_ARG_ENABLE(curses, [AS_HELP_STRING([--disable-curses], [do not use curses (removes dependency from curses-library)])], [USECURSES=$enableval], [USECURSES=yes] ) AC_MSG_RESULT($USECURSES) if test "$USECURSES" = "yes"; then INCVAL="${LIBPREF}/include" LIBVAL="${LIBPREF}/lib" AC_ARG_WITH(libcurses_includes, [AS_HELP_STRING([--with-libcurses-includes=DIR], [libcurses include directory])], [INCVAL="$withval"]) CPPFLAGS="${CPPFLAGS} -I${INCVAL}" AC_ARG_WITH(libcurses_libraries, [AS_HELP_STRING([--with-libcurses-libraries=DIR], [libcurses library directory])], [LIBVAL="$withval"]) LDFLAGS="${LDFLAGS} -L${LIBVAL}" AC_CHECK_HEADER(ncurses.h, FOUND=yes AC_DEFINE([HAVE_NCURSES_H],1,[Define to 1 if you have the header file.]), FOUND=no) if test "$FOUND" = "no"; then AC_CHECK_HEADER(ncurses/ncurses.h, FOUND=yes AC_DEFINE([HAVE_NCURSES_NCURSES_H],1,[Define to 1 if you have the header file.]), FOUND=no) fi if test "$FOUND" = "no"; then AC_CHECK_HEADER(curses.h, FOUND=yes AC_DEFINE([HAVE_CURSES_H],1,[Define to 1 if you have the header file.]), FOUND=no) fi if test "$FOUND" = "no"; then AC_MSG_ERROR([Couldn't find curses headers (ncurses.h or curses.h)]) fi AC_SEARCH_LIBS([refresh], [ncurses curses],, AC_ERROR([Couldn't find curses library])) else AC_DEFINE([DISABLE_CURSES],1,[Define to 1 to not use curses]) fi dnl dnl Use libpar2 for par-checking. Deafult: no dnl AC_MSG_CHECKING(whether to include code for par-checking) AC_ARG_ENABLE(parcheck, [AS_HELP_STRING([--disable-parcheck], [do not include par-check/-repair-support (removes dependency from libpar2- and libsigc-libraries)])], [ ENABLEPARCHECK=$enableval ], [ ENABLEPARCHECK=yes] ) AC_MSG_RESULT($ENABLEPARCHECK) if test "$ENABLEPARCHECK" = "yes"; then dnl dnl checks for libsigc++ includes and libraries (required for libpar2). dnl AC_ARG_WITH(libsigc_includes, [AS_HELP_STRING([--with-libsigc-includes=DIR], [libsigc++-2.0 include directory])], [CPPFLAGS="${CPPFLAGS} -I${withval}"] [INCVAL="yes"], [INCVAL="no"]) AC_ARG_WITH(libsigc_libraries, [AS_HELP_STRING([--with-libsigc-libraries=DIR], [libsigc++-2.0 library directory])], [LDFLAGS="${LDFLAGS} -L${withval}"] [CPPFLAGS="${CPPFLAGS} -I${withval}/sigc++-2.0/include"] [LIBVAL="yes"], [LIBVAL="no"]) if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then PKG_CHECK_MODULES(libsigc, sigc++-2.0, [LIBS="${LIBS} $libsigc_LIBS"] [CPPFLAGS="${CPPFLAGS} $libsigc_CFLAGS"]) fi AC_CHECK_HEADER(sigc++/type_traits.h,, AC_MSG_ERROR("libsigc++-2.0 header files not found")) dnl dnl checks for libpar2 includes and libraries. dnl INCVAL="${LIBPREF}/include" LIBVAL="${LIBPREF}/lib" AC_ARG_WITH(libpar2_includes, [AS_HELP_STRING([--with-libpar2-includes=DIR], [libpar2 include directory])], [INCVAL="$withval"]) CPPFLAGS="${CPPFLAGS} -I${INCVAL}" AC_CHECK_HEADER(libpar2/libpar2.h,, AC_MSG_ERROR("libpar2 header files not found")) AC_ARG_WITH(libpar2_libraries, [AS_HELP_STRING([--with-libpar2-libraries=DIR], [libpar2 library directory])], [LIBVAL="$withval"]) LDFLAGS="${LDFLAGS} -L${LIBVAL}" AC_SEARCH_LIBS([_ZN12Par2RepairerC1Ev], [par2], , AC_MSG_ERROR("libpar2 library not found")) dnl dnl check if libpar2 library is linkable dnl AC_MSG_CHECKING(for libpar2 linking) AC_TRY_LINK( [#include ] [#include ] [ class Repairer : public Par2Repairer { }; ], [ Repairer* p = new Repairer(); ], AC_MSG_RESULT([[yes]]), AC_MSG_RESULT([[no]]) AC_MSG_ERROR("libpar2 library not found")) dnl dnl check if libpar2 has support for cancelling dnl AC_MSG_CHECKING(whether libpar2 supports cancelling) AC_TRY_COMPILE( [#include ] [#include ] [ class Repairer : public Par2Repairer { void test() { cancelled = true; } }; ], [], AC_MSG_RESULT([[yes]]) AC_DEFINE([HAVE_PAR2_CANCEL], 1, [Define to 1 if libpar2 supports cancelling (needs a special patch)]), AC_MSG_RESULT([[no]])) dnl dnl check if libpar2 has recent bugfixes-patch dnl AC_MSG_CHECKING(whether libpar2 has recent bugfixes-patch (version 2)) AC_TRY_COMPILE( [#include ] [#include ] [ class Repairer : public Par2Repairer { void test() { BugfixesPatchVersion2(); } }; ], [], AC_MSG_RESULT([[yes]]) PAR2PATCHV2=yes AC_DEFINE([HAVE_PAR2_BUGFIXES_V2], 1, [Define to 1 if libpar2 has recent bugfixes-patch (version 2)]), AC_MSG_RESULT([[no]]) PAR2PATCHV2=no) if test "$PAR2PATCHV2" = "no" ; then AC_ARG_ENABLE(libpar2-bugfixes-check, [AS_HELP_STRING([--disable-libpar2-bugfixes-check], [do not check if libpar2 has recent bugfixes-patch applied])], [ PAR2PATCHCHECK=$enableval ], [ PAR2PATCHCHECK=yes] ) if test "$PAR2PATCHCHECK" = "yes"; then AC_ERROR([Your version of libpar2 doesn't include the recent bugfixes-patch (version 2, updated Dec 3, 2012). Please patch libpar2 with the patches supplied with NZBGet (see README for details). If you cannot patch libpar2, you can use configure parameter --disable-libpar2-bugfixes-check to suppress the check. Please note however that in this case the program may crash during par-check/repair. The patch is highly recommended!]) fi fi else AC_DEFINE([DISABLE_PARCHECK],1,[Define to 1 to disable smart par-verification and restoration]) fi dnl dnl Use TLS/SSL. Deafult: yes dnl AC_MSG_CHECKING(whether to use TLS/SSL) AC_ARG_ENABLE(tls, [AS_HELP_STRING([--disable-tls], [do not use TLS/SSL (removes dependency from TLS/SSL-libraries)])], [ USETLS=$enableval ], [ USETLS=yes] ) AC_MSG_RESULT($USETLS) if test "$USETLS" = "yes"; then AC_ARG_WITH(tlslib, [AS_HELP_STRING([--with-tlslib=(GnuTLS, OpenSSL)], [TLS/SSL library to use])], [TLSLIB="$withval"]) if test "$TLSLIB" != "GnuTLS" -a "$TLSLIB" != "OpenSSL" -a "$TLSLIB" != ""; then AC_MSG_ERROR([Invalid argument for option --with-tlslib]) fi if test "$TLSLIB" = "GnuTLS" -o "$TLSLIB" = ""; then INCVAL="${LIBPREF}/include" LIBVAL="${LIBPREF}/lib" AC_ARG_WITH(libgnutls_includes, [AS_HELP_STRING([--with-libgnutls-includes=DIR], [GnuTLS include directory])], [INCVAL="$withval"]) CPPFLAGS="${CPPFLAGS} -I${INCVAL}" AC_ARG_WITH(libgnutls_libraries, [AS_HELP_STRING([--with-libgnutls-libraries=DIR], [GnuTLS library directory])], [LIBVAL="$withval"]) LDFLAGS="${LDFLAGS} -L${LIBVAL}" AC_CHECK_HEADER(gnutls/gnutls.h, FOUND=yes TLSHEADERS=yes, FOUND=no) if test "$FOUND" = "no" -a "$TLSLIB" = "GnuTLS"; then AC_MSG_ERROR([Couldn't find GnuTLS headers (gnutls.h)]) fi if test "$FOUND" = "yes"; then AC_SEARCH_LIBS([gnutls_global_init], [gnutls], AC_SEARCH_LIBS([gcry_control], [gnutls gcrypt], FOUND=yes, FOUND=no), FOUND=no) if test "$FOUND" = "no" -a "$TLSLIB" = "GnuTLS"; then AC_MSG_ERROR([Couldn't find GnuTLS library]) fi if test "$FOUND" = "yes"; then TLSLIB="GnuTLS" AC_DEFINE([HAVE_LIBGNUTLS],1,[Define to 1 to use GnuTLS library for TLS/SSL-support.]) fi fi fi if test "$TLSLIB" = "OpenSSL" -o "$TLSLIB" = ""; then AC_ARG_WITH(openssl_includes, [AS_HELP_STRING([--with-openssl-includes=DIR], [OpenSSL include directory])], [CPPFLAGS="${CPPFLAGS} -I${withval}"] [INCVAL="yes"], [INCVAL="no"]) AC_ARG_WITH(openssl_libraries, [AS_HELP_STRING([--with-openssl-libraries=DIR], [OpenSSL library directory])], [LDFLAGS="${LDFLAGS} -L${withval}"] [LIBVAL="yes"], [LIBVAL="no"]) if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then PKG_CHECK_MODULES(openssl, openssl, [LIBS="${LIBS} $openssl_LIBS"] [CPPFLAGS="${CPPFLAGS} $openssl_CFLAGS"]) fi AC_CHECK_HEADER(openssl/ssl.h, FOUND=yes TLSHEADERS=yes, FOUND=no) if test "$FOUND" = "no" -a "$TLSLIB" = "OpenSSL"; then AC_MSG_ERROR([Couldn't find OpenSSL headers (ssl.h)]) fi if test "$FOUND" = "yes"; then AC_SEARCH_LIBS([CRYPTO_set_locking_callback], [crypto], AC_SEARCH_LIBS([SSL_library_init], [ssl], FOUND=yes, FOUND=no), FOUND=no) if test "$FOUND" = "no" -a "$TLSLIB" = "OpenSSL"; then AC_MSG_ERROR([Couldn't find OpenSSL library]) fi if test "$FOUND" = "yes"; then TLSLIB="OpenSSL" AC_DEFINE([HAVE_OPENSSL],1,[Define to 1 to use OpenSSL library for TLS/SSL-support.]) fi fi fi if test "$TLSLIB" = ""; then if test "$TLSHEADERS" = ""; then AC_MSG_ERROR([Couldn't find neither GnuTLS nor OpenSSL headers (gnutls.h or ssl.h)]) else AC_MSG_ERROR([Couldn't find neither GnuTLS nor OpenSSL library]) fi fi else AC_DEFINE([DISABLE_TLS],1,[Define to 1 to not use TLS/SSL]) fi dnl dnl checks for zlib includes and libraries. dnl AC_MSG_CHECKING(whether to use gzip) AC_ARG_ENABLE(gzip, [AS_HELP_STRING([--disable-gzip], [disable gzip-compression/decompression (removes dependency from zlib-library)])], [USEZLIB=$enableval], [USEZLIB=yes] ) AC_MSG_RESULT($USEZLIB) if test "$USEZLIB" = "yes"; then INCVAL="${LIBPREF}/include" LIBVAL="${LIBPREF}/lib" AC_ARG_WITH(zlib_includes, [AS_HELP_STRING([--with-zlib-includes=DIR], [zlib include directory])], [INCVAL="$withval"]) CPPFLAGS="${CPPFLAGS} -I${INCVAL}" AC_ARG_WITH(zlib_libraries, [AS_HELP_STRING([--with-zlib-libraries=DIR], [zlib library directory])], [LIBVAL="$withval"]) LDFLAGS="${LDFLAGS} -L${LIBVAL}" AC_CHECK_HEADER(zlib.h,, AC_MSG_ERROR("zlib header files not found")) AC_SEARCH_LIBS([deflateBound], [z], , AC_MSG_ERROR("zlib library not found")) else AC_DEFINE([DISABLE_GZIP],1,[Define to 1 to disable gzip-support]) fi dnl dnl Some Linux systems require an empty signal handler for SIGCHLD dnl in order for exit codes to be correctly delivered to parent process. dnl Some 32-Bit BSD systems however may not function properly if the handler is installed. dnl The default behavior is to install the handler. dnl AC_MSG_CHECKING(whether to use an empty SIGCHLD handler) AC_ARG_ENABLE(sigchld-handler, [AS_HELP_STRING([--disable-sigchld-handler], [do not use sigchld-handler (the disabling may be neccessary on 32-Bit BSD)])], [SIGCHLDHANDLER=$enableval], [SIGCHLDHANDLER=yes]) AC_MSG_RESULT($SIGCHLDHANDLER) if test "$SIGCHLDHANDLER" = "yes"; then AC_DEFINE([SIGCHLD_HANDLER], 1, [Define to 1 to install an empty signal handler for SIGCHLD]) fi dnl dnl Debugging. Default: no dnl AC_MSG_CHECKING(whether to include all debugging code) AC_ARG_ENABLE(debug, [AS_HELP_STRING([--enable-debug], [enable debugging])], [ ENABLEDEBUG=$enableval ], [ ENABLEDEBUG=no] ) AC_MSG_RESULT($ENABLEDEBUG) if test "$ENABLEDEBUG" = "yes"; then dnl dnl Begin of debugging code dnl AC_DEFINE([DEBUG],1,Define to 1 to include debug-code) dnl dnl Set debug flags for gcc (if gcc is used) dnl if test "$CC" = "gcc"; then CXXFLAGS="-g -Wall" else CXXFLAGS="" fi dnl dnl check for __FUNCTION__ or __func__ macro dnl AC_MSG_CHECKING(for macro returning current function name) AC_TRY_COMPILE( [#include ], [printf("%s\n", __FUNCTION__);], AC_MSG_RESULT(__FUNCTION__) FUNCTION_MACRO_NAME=__FUNCTION__, AC_TRY_COMPILE([#include ], [printf("%s\n", __func__);], AC_MSG_RESULT(__func__) FUNCTION_MACRO_NAME=__func__, AC_MSG_RESULT(none))) if test "$FUNCTION_MACRO_NAME" != ""; then AC_DEFINE_UNQUOTED(FUNCTION_MACRO_NAME, $FUNCTION_MACRO_NAME, [Define to the name of macro which returns the name of function being compiled]) fi dnl dnl variadic macros dnl AC_MSG_CHECKING(for variadic macros) AC_COMPILE_IFELSE([ #define macro(...) macrofunc(__VA_ARGS__) int macrofunc(int a, int b) { return a + b; } int test() { return macro(1, 2); } ], AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_VARIADIC_MACROS], 1, Define to 1 if variadic macros are supported), AC_MSG_RESULT([no])) dnl dnl Backtracing on segmentation faults dnl AC_MSG_CHECKING(for backtrace) AC_TRY_COMPILE( [#include ] [#include ] [#include ], [ void *array[100]; size_t size; char **strings; ] [ size = backtrace(array, 100); ] [ strings = backtrace_symbols(array, size); ], FOUND=yes AC_MSG_RESULT([[yes]]) AC_DEFINE([HAVE_BACKTRACE], 1, [Define to 1 to create stacktrace on segmentation faults]), FOUND=no AC_MSG_RESULT([[no]])) dnl dnl "rdynamic" linker flag dnl AC_MSG_CHECKING(for rdynamic linker flag) old_LDFLAGS="$LDFLAGS" LDFLAGS="$LDFLAGS -rdynamic" AC_TRY_LINK([], [], AC_MSG_RESULT([[yes]]), AC_MSG_RESULT([[no]]) [LDFLAGS="$old_LDFLAGS"]) dnl dnl End of debugging code dnl fi AC_CONFIG_FILES([Makefile]) AC_OUTPUT nzbget-12.0+dfsg/depcomp000077500000000000000000000311741226450633000152300ustar00rootroot00000000000000#! /bin/sh # depcomp - compile a program generating dependencies as side-effects # Copyright 1999, 2000 Free Software Foundation, Inc. # 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, 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., 59 Temple Place - Suite 330, Boston, MA # 02111-1307, USA. # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # Originally written by Alexandre Oliva . if test -z "$depmode" || test -z "$source" || test -z "$object"; then echo "depcomp: Variables source, object and depmode must be set" 1>&2 exit 1 fi # `libtool' can also be set to `yes' or `no'. depfile=${depfile-`echo "$object" | sed 's,\([^/]*\)$,.deps/\1,;s/\.\([^.]*\)$/.P\1/'`} tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`} rm -f "$tmpdepfile" # Some modes work just like other modes, but use different flags. We # parameterize here, but still list the modes in the big case below, # to make depend.m4 easier to write. Note that we *cannot* use a case # here, because this file can only contain one case statement. if test "$depmode" = hp; then # HP compiler uses -M and no extra arg. gccflag=-M depmode=gcc fi if test "$depmode" = dashXmstdout; then # This is just like dashmstdout with a different argument. dashmflag=-xM depmode=dashmstdout fi case "$depmode" in gcc3) ## gcc 3 implements dependency tracking that does exactly what ## we want. Yay! Note: for some reason libtool 1.4 doesn't like ## it if -MD -MP comes after the -MF stuff. Hmm. "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" stat=$? if test $stat -eq 0; then : else rm -f "$tmpdepfile" exit $stat fi mv "$tmpdepfile" "$depfile" ;; gcc) ## There are various ways to get dependency output from gcc. Here's ## why we pick this rather obscure method: ## - Don't want to use -MD because we'd like the dependencies to end ## up in a subdir. Having to rename by hand is ugly. ## (We might end up doing this anyway to support other compilers.) ## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like ## -MM, not -M (despite what the docs say). ## - Using -M directly means running the compiler twice (even worse ## than renaming). if test -z "$gccflag"; then gccflag=-MD, fi "$@" -Wp,"$gccflag$tmpdepfile" stat=$? if test $stat -eq 0; then : else rm -f "$tmpdepfile" exit $stat fi rm -f "$depfile" echo "$object : \\" > "$depfile" alpha=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ## The second -e expression handles DOS-style file names with drive letters. sed -e 's/^[^:]*: / /' \ -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile" ## This next piece of magic avoids the `deleted header file' problem. ## The problem is that when a header file which appears in a .P file ## is deleted, the dependency causes make to die (because there is ## typically no way to rebuild the header). We avoid this by adding ## dummy dependencies for each header file. Too bad gcc doesn't do ## this for us directly. tr ' ' ' ' < "$tmpdepfile" | ## Some versions of gcc put a space before the `:'. On the theory ## that the space means something, we add a space to the output as ## well. ## Some versions of the HPUX 10.20 sed can't process this invocation ## correctly. Breaking it into two sed invocations is a workaround. sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile" rm -f "$tmpdepfile" ;; hp) # This case exists only to let depend.m4 do its work. It works by # looking at the text of this script. This case will never be run, # since it is checked for above. exit 1 ;; sgi) if test "$libtool" = yes; then "$@" "-Wp,-MDupdate,$tmpdepfile" else "$@" -MDupdate "$tmpdepfile" fi stat=$? if test $stat -eq 0; then : else rm -f "$tmpdepfile" exit $stat fi rm -f "$depfile" if test -f "$tmpdepfile"; then # yes, the sourcefile depend on other files echo "$object : \\" > "$depfile" # Clip off the initial element (the dependent). Don't try to be # clever and replace this with sed code, as IRIX sed won't handle # lines with more than a fixed number of characters (4096 in # IRIX 6.2 sed, 8192 in IRIX 6.5). We also remove comment lines; # the IRIX cc adds comments like `#:fec' to the end of the # dependency line. tr ' ' ' ' < "$tmpdepfile" \ | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' | \ tr ' ' ' ' >> $depfile echo >> $depfile # The second pass generates a dummy entry for each header file. tr ' ' ' ' < "$tmpdepfile" \ | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \ >> $depfile else # The sourcefile does not contain any dependencies, so just # store a dummy comment line, to avoid errors with the Makefile # "include basename.Plo" scheme. echo "#dummy" > "$depfile" fi rm -f "$tmpdepfile" ;; aix) # The C for AIX Compiler uses -M and outputs the dependencies # in a .u file. This file always lives in the current directory. # Also, the AIX compiler puts `$object:' at the start of each line; # $object doesn't have directory information. stripped=`echo "$object" | sed -e 's,^.*/,,' -e 's/\(.*\)\..*$/\1/'` tmpdepfile="$stripped.u" outname="$stripped.o" if test "$libtool" = yes; then "$@" -Wc,-M else "$@" -M fi stat=$? if test $stat -eq 0; then : else rm -f "$tmpdepfile" exit $stat fi if test -f "$tmpdepfile"; then # Each line is of the form `foo.o: dependent.h'. # Do two passes, one to just change these to # `$object: dependent.h' and one to simply `dependent.h:'. sed -e "s,^$outname:,$object :," < "$tmpdepfile" > "$depfile" sed -e "s,^$outname: \(.*\)$,\1:," < "$tmpdepfile" >> "$depfile" else # The sourcefile does not contain any dependencies, so just # store a dummy comment line, to avoid errors with the Makefile # "include basename.Plo" scheme. echo "#dummy" > "$depfile" fi rm -f "$tmpdepfile" ;; icc) # Must come before tru64. # Intel's C compiler understands `-MD -MF file'. However # icc -MD -MF foo.d -c -o sub/foo.o sub/foo.c # will fill foo.d with something like # foo.o: sub/foo.c # foo.o: sub/foo.h # which is wrong. We want: # sub/foo.o: sub/foo.c # sub/foo.o: sub/foo.h # sub/foo.c: # sub/foo.h: "$@" -MD -MF "$tmpdepfile" stat=$? if test $stat -eq 0; then : else rm -f "$tmpdepfile" exit $stat fi rm -f "$depfile" # Each line is of the form `foo.o: dependent.h'. # Do two passes, one to just change these to # `$object: dependent.h' and one to simply `dependent.h:'. sed -e "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile" sed -e "s,^[^:]*: \(.*\)$,\1:," < "$tmpdepfile" >> "$depfile" rm -f "$tmpdepfile" ;; tru64) # The Tru64 AIX compiler uses -MD to generate dependencies as a side # effect. `cc -MD -o foo.o ...' puts the dependencies into `foo.o.d'. # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put # dependencies in `foo.d' instead, so we check for that too. # Subdirectories are respected. tmpdepfile1="$object.d" tmpdepfile2=`echo "$object" | sed -e 's/.o$/.d/'` if test "$libtool" = yes; then "$@" -Wc,-MD else "$@" -MD fi stat=$? if test $stat -eq 0; then : else rm -f "$tmpdepfile1" "$tmpdepfile2" exit $stat fi if test -f "$tmpdepfile1"; then tmpdepfile="$tmpdepfile1" else tmpdepfile="$tmpdepfile2" fi if test -f "$tmpdepfile"; then sed -e "s,^.*\.[a-z]*:,$object:," < "$tmpdepfile" > "$depfile" # That's a space and a tab in the []. sed -e 's,^.*\.[a-z]*:[ ]*,,' -e 's,$,:,' < "$tmpdepfile" >> "$depfile" else echo "#dummy" > "$depfile" fi rm -f "$tmpdepfile" ;; #nosideeffect) # This comment above is used by automake to tell side-effect # dependency tracking mechanisms from slower ones. dashmstdout) # Important note: in order to support this mode, a compiler *must* # always write the proprocessed file to stdout, regardless of -o, # because we must use -o when running libtool. test -z "$dashmflag" && dashmflag=-M ( IFS=" " case " $* " in *" --mode=compile "*) # this is libtool, let us make it quiet for arg do # cycle over the arguments case "$arg" in "--mode=compile") # insert --quiet before "--mode=compile" set fnord "$@" --quiet shift # fnord ;; esac set fnord "$@" "$arg" shift # fnord shift # "$arg" done ;; esac "$@" $dashmflag | sed 's:^[^:]*\:[ ]*:'"$object"'\: :' > "$tmpdepfile" ) & proc=$! "$@" stat=$? wait "$proc" if test "$stat" != 0; then exit $stat; fi rm -f "$depfile" cat < "$tmpdepfile" > "$depfile" tr ' ' ' ' < "$tmpdepfile" | \ ## Some versions of the HPUX 10.20 sed can't process this invocation ## correctly. Breaking it into two sed invocations is a workaround. sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile" rm -f "$tmpdepfile" ;; dashXmstdout) # This case only exists to satisfy depend.m4. It is never actually # run, as this mode is specially recognized in the preamble. exit 1 ;; makedepend) # X makedepend ( shift cleared=no for arg in "$@"; do case $cleared in no) set ""; shift cleared=yes esac case "$arg" in -D*|-I*) set fnord "$@" "$arg"; shift;; -*) ;; *) set fnord "$@" "$arg"; shift;; esac done obj_suffix="`echo $object | sed 's/^.*\././'`" touch "$tmpdepfile" ${MAKEDEPEND-makedepend} 2>/dev/null -o"$obj_suffix" -f"$tmpdepfile" "$@" ) & proc=$! "$@" stat=$? wait "$proc" if test "$stat" != 0; then exit $stat; fi rm -f "$depfile" cat < "$tmpdepfile" > "$depfile" tail +3 "$tmpdepfile" | tr ' ' ' ' | \ ## Some versions of the HPUX 10.20 sed can't process this invocation ## correctly. Breaking it into two sed invocations is a workaround. sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile" rm -f "$tmpdepfile" "$tmpdepfile".bak ;; cpp) # Important note: in order to support this mode, a compiler *must* # always write the proprocessed file to stdout, regardless of -o, # because we must use -o when running libtool. ( IFS=" " case " $* " in *" --mode=compile "*) for arg do # cycle over the arguments case $arg in "--mode=compile") # insert --quiet before "--mode=compile" set fnord "$@" --quiet shift # fnord ;; esac set fnord "$@" "$arg" shift # fnord shift # "$arg" done ;; esac "$@" -E | sed -n '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' | sed '$ s: \\$::' > "$tmpdepfile" ) & proc=$! "$@" stat=$? wait "$proc" if test "$stat" != 0; then exit $stat; fi rm -f "$depfile" echo "$object : \\" > "$depfile" cat < "$tmpdepfile" >> "$depfile" sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile" rm -f "$tmpdepfile" ;; msvisualcpp) # Important note: in order to support this mode, a compiler *must* # always write the proprocessed file to stdout, regardless of -o, # because we must use -o when running libtool. ( IFS=" " case " $* " in *" --mode=compile "*) for arg do # cycle over the arguments case $arg in "--mode=compile") # insert --quiet before "--mode=compile" set fnord "$@" --quiet shift # fnord ;; esac set fnord "$@" "$arg" shift # fnord shift # "$arg" done ;; esac "$@" -E | sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::echo "`cygpath -u \\"\1\\"`":p' | sort | uniq > "$tmpdepfile" ) & proc=$! "$@" stat=$? wait "$proc" if test "$stat" != 0; then exit $stat; fi rm -f "$depfile" echo "$object : \\" > "$depfile" . "$tmpdepfile" | sed 's% %\\ %g' | sed -n '/^\(.*\)$/ s:: \1 \\:p' >> "$depfile" echo " " >> "$depfile" . "$tmpdepfile" | sed 's% %\\ %g' | sed -n '/^\(.*\)$/ s::\1\::p' >> "$depfile" rm -f "$tmpdepfile" ;; none) exec "$@" ;; *) echo "Unknown depmode $depmode" 1>&2 exit 1 ;; esac exit 0 nzbget-12.0+dfsg/install-sh000077500000000000000000000142531226450633000156560ustar00rootroot00000000000000#!/bin/sh # # install - install a program, script, or datafile # This comes from X11R5 (mit/util/scripts/install.sh). # # Copyright 1991 by the Massachusetts Institute of Technology # # Permission to use, copy, modify, distribute, and sell this software and its # documentation for any purpose is hereby granted without fee, provided that # the above copyright notice appear in all copies and that both that # copyright notice and this permission notice appear in supporting # documentation, and that the name of M.I.T. not be used in advertising or # publicity pertaining to distribution of the software without specific, # written prior permission. M.I.T. makes no representations about the # suitability of this software for any purpose. It is provided "as is" # without express or implied warranty. # # Calling this script install-sh is preferred over install.sh, to prevent # `make' implicit rules from creating a file called install from it # when there is no Makefile. # # This script is compatible with the BSD install script, but was written # from scratch. It can only install one file at a time, a restriction # shared with many OS's install programs. # set DOITPROG to echo to test this script # Don't use :- since 4.3BSD and earlier shells don't like it. doit="${DOITPROG-}" # put in absolute paths if you don't have them in your path; or use env. vars. mvprog="${MVPROG-mv}" cpprog="${CPPROG-cp}" chmodprog="${CHMODPROG-chmod}" chownprog="${CHOWNPROG-chown}" chgrpprog="${CHGRPPROG-chgrp}" stripprog="${STRIPPROG-strip}" rmprog="${RMPROG-rm}" mkdirprog="${MKDIRPROG-mkdir}" transformbasename="" transform_arg="" instcmd="$mvprog" chmodcmd="$chmodprog 0755" chowncmd="" chgrpcmd="" stripcmd="" rmcmd="$rmprog -f" mvcmd="$mvprog" src="" dst="" dir_arg="" while [ x"$1" != x ]; do case $1 in -c) instcmd=$cpprog shift continue;; -d) dir_arg=true shift continue;; -m) chmodcmd="$chmodprog $2" shift shift continue;; -o) chowncmd="$chownprog $2" shift shift continue;; -g) chgrpcmd="$chgrpprog $2" shift shift continue;; -s) stripcmd=$stripprog shift continue;; -t=*) transformarg=`echo $1 | sed 's/-t=//'` shift continue;; -b=*) transformbasename=`echo $1 | sed 's/-b=//'` shift continue;; *) if [ x"$src" = x ] then src=$1 else # this colon is to work around a 386BSD /bin/sh bug : dst=$1 fi shift continue;; esac done if [ x"$src" = x ] then echo "$0: no input file specified" >&2 exit 1 else : fi if [ x"$dir_arg" != x ]; then dst=$src src="" if [ -d "$dst" ]; then instcmd=: chmodcmd="" else instcmd=$mkdirprog fi else # Waiting for this to be detected by the "$instcmd $src $dsttmp" command # might cause directories to be created, which would be especially bad # if $src (and thus $dsttmp) contains '*'. if [ -f "$src" ] || [ -d "$src" ] then : else echo "$0: $src does not exist" >&2 exit 1 fi if [ x"$dst" = x ] then echo "$0: no destination specified" >&2 exit 1 else : fi # If destination is a directory, append the input filename; if your system # does not like double slashes in filenames, you may need to add some logic if [ -d "$dst" ] then dst=$dst/`basename "$src"` else : fi fi ## this sed command emulates the dirname command dstdir=`echo "$dst" | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` # Make sure that the destination directory exists. # this part is taken from Noah Friedman's mkinstalldirs script # Skip lots of stat calls in the usual case. if [ ! -d "$dstdir" ]; then defaultIFS=' ' IFS="${IFS-$defaultIFS}" oIFS=$IFS # Some sh's can't handle IFS=/ for some reason. IFS='%' set - `echo "$dstdir" | sed -e 's@/@%@g' -e 's@^%@/@'` IFS=$oIFS pathcomp='' while [ $# -ne 0 ] ; do pathcomp=$pathcomp$1 shift if [ ! -d "$pathcomp" ] ; then $mkdirprog "$pathcomp" else : fi pathcomp=$pathcomp/ done fi if [ x"$dir_arg" != x ] then $doit $instcmd "$dst" && if [ x"$chowncmd" != x ]; then $doit $chowncmd "$dst"; else : ; fi && if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd "$dst"; else : ; fi && if [ x"$stripcmd" != x ]; then $doit $stripcmd "$dst"; else : ; fi && if [ x"$chmodcmd" != x ]; then $doit $chmodcmd "$dst"; else : ; fi else # If we're going to rename the final executable, determine the name now. if [ x"$transformarg" = x ] then dstfile=`basename "$dst"` else dstfile=`basename "$dst" $transformbasename | sed $transformarg`$transformbasename fi # don't allow the sed command to completely eliminate the filename if [ x"$dstfile" = x ] then dstfile=`basename "$dst"` else : fi # Make a couple of temp file names in the proper directory. dsttmp=$dstdir/#inst.$$# rmtmp=$dstdir/#rm.$$# # Trap to clean up temp files at exit. trap 'status=$?; rm -f "$dsttmp" "$rmtmp" && exit $status' 0 trap '(exit $?); exit' 1 2 13 15 # Move or copy the file name to the temp name $doit $instcmd "$src" "$dsttmp" && # and set any options; do chmod last to preserve setuid bits # If any of these fail, we abort the whole thing. If we want to # ignore errors from any of these, just make sure not to ignore # errors from the above "$doit $instcmd $src $dsttmp" command. if [ x"$chowncmd" != x ]; then $doit $chowncmd "$dsttmp"; else :;fi && if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd "$dsttmp"; else :;fi && if [ x"$stripcmd" != x ]; then $doit $stripcmd "$dsttmp"; else :;fi && if [ x"$chmodcmd" != x ]; then $doit $chmodcmd "$dsttmp"; else :;fi && # Now remove or move aside any old file at destination location. We try this # two ways since rm can't unlink itself on some systems and the destination # file might be busy for other reasons. In this case, the final cleanup # might fail but the new file should still install successfully. { if [ -f "$dstdir/$dstfile" ] then $doit $rmcmd -f "$dstdir/$dstfile" 2>/dev/null || $doit $mvcmd -f "$dstdir/$dstfile" "$rmtmp" 2>/dev/null || { echo "$0: cannot unlink or rename $dstdir/$dstfile" >&2 (exit 1); exit } else : fi } && # Now rename the file to the real destination. $doit $mvcmd "$dsttmp" "$dstdir/$dstfile" fi && # The final little trick to "correctly" pass the exit status to the exit trap. { (exit 0); exit } nzbget-12.0+dfsg/libpar2-0.2-MSVC8.patch000066400000000000000000000237431226450633000173650ustar00rootroot00000000000000diff -urN libpar2-0.2-original/par2.sln libpar2-0.2-modified/par2.sln --- libpar2-0.2-original/par2.sln 1970-01-01 01:00:00.000000000 +0100 +++ libpar2-0.2-modified/par2.sln 2007-10-31 08:13:25.055723100 +0100 @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual C++ Express 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "par2", "par2.vcproj", "{B2EA2B24-5F2E-42B1-91F0-5954880CDC9B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B2EA2B24-5F2E-42B1-91F0-5954880CDC9B}.Debug|Win32.ActiveCfg = Debug|Win32 + {B2EA2B24-5F2E-42B1-91F0-5954880CDC9B}.Debug|Win32.Build.0 = Debug|Win32 + {B2EA2B24-5F2E-42B1-91F0-5954880CDC9B}.Release|Win32.ActiveCfg = Release|Win32 + {B2EA2B24-5F2E-42B1-91F0-5954880CDC9B}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff -urN libpar2-0.2-original/par2.vcproj libpar2-0.2-modified/par2.vcproj --- libpar2-0.2-original/par2.vcproj 1970-01-01 01:00:00.000000000 +0100 +++ libpar2-0.2-modified/par2.vcproj 2007-10-31 14:02:31.412999100 +0100 @@ -0,0 +1,380 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -urN libpar2-0.2-original/par2cmdline.h libpar2-0.2-modified/par2cmdline.h --- libpar2-0.2-original/par2cmdline.h 2005-11-25 17:20:00.000000000 +0100 +++ libpar2-0.2-modified/par2cmdline.h 2007-10-31 08:15:00.669883000 +0100 @@ -59,6 +59,13 @@ # define _SIZE_T_DEFINED #endif +#ifndef VERSION +#define VERSION "0.2" +#endif + +#ifndef PACKAGE +#define PACKAGE "libpar2" +#endif #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-02-13 15:37:59.899314300 +0100 @@ -78,6 +78,7 @@ delete mainpacket; delete creatorpacket; + delete headers; } @@ -1261,7 +1262,7 @@ DiskFile::SplitFilename(filename, path, name); cout << "Target: \"" << name << "\" - missing." << endl; - sig_done.emit(name, 0, sourcefile->GetVerificationPacket()->BlockCount()); + sig_done.emit(name, 0, sourcefile && sourcefile->GetVerificationPacket() ? sourcefile->GetVerificationPacket()->BlockCount() : 0); } } @@ -1804,7 +1805,7 @@ } } } - sig_done.emit(name,count,sourcefile->GetVerificationPacket()->BlockCount()); + sig_done.emit(name,count, sourcefile && sourcefile->GetVerificationPacket() ? sourcefile->GetVerificationPacket()->BlockCount() : 0); sig_progress.emit(1000.0); return true; } nzbget-12.0+dfsg/libpar2-0.2-bugfixes.patch000066400000000000000000000040571226450633000203360ustar00rootroot00000000000000diff -aud -U 5 ../libpar2-0.2-original/par2repairer.cpp ../libpar2-0.2/par2repairer.cpp --- ../libpar2-0.2-original/par2repairer.cpp 2006-01-20 18:25:20.000000000 +0100 +++ ../libpar2-0.2/par2repairer.cpp 2012-11-30 14:23:31.000000000 +0100 @@ -76,10 +76,11 @@ ++sf; } delete mainpacket; delete creatorpacket; + delete headers; } Result Par2Repairer::PreProcess(const CommandLine &commandline) { @@ -1259,11 +1260,11 @@ string path; string name; DiskFile::SplitFilename(filename, path, name); cout << "Target: \"" << name << "\" - missing." << endl; - sig_done.emit(name, 0, sourcefile->GetVerificationPacket()->BlockCount()); + sig_done.emit(name, 0, sourcefile->GetVerificationPacket() ? sourcefile->GetVerificationPacket()->BlockCount() : 0); } } ++sf; } @@ -1802,11 +1803,11 @@ << "\" - no data found." << endl; } } } - sig_done.emit(name,count,sourcefile->GetVerificationPacket()->BlockCount()); + sig_done.emit(name,count, count>0 && sourcefile->GetVerificationPacket() ? sourcefile->GetVerificationPacket()->BlockCount() : 0); sig_progress.emit(1000.0); return true; } // Find out how much data we have found diff -aud -U 5 ../libpar2-0.2-original/par2repairer.h ../libpar2-0.2/par2repairer.h --- ../libpar2-0.2-original/par2repairer.h 2006-01-20 00:38:27.000000000 +0100 +++ ../libpar2-0.2/par2repairer.h 2012-11-30 14:24:46.000000000 +0100 @@ -34,10 +34,15 @@ sigc::signal sig_filename; sigc::signal sig_progress; sigc::signal sig_headers; sigc::signal sig_done; + // This method allows to determine whether libpar2 includes the patches + // ("libpar2-0.2-bugfixes.patch") submitted to libpar2 project. + // Use the method in configure scripts for detection. + void BugfixesPatchVersion2() { } + protected: // Steps in verifying and repairing files: // Load packets from the specified file bool LoadPacketsFromFile(string filename); nzbget-12.0+dfsg/libpar2-0.2-cancel.patch000066400000000000000000000124251226450633000177450ustar00rootroot00000000000000diff -aud -U 5 ../libpar2-0.2-original/par2repairer.cpp ../libpar2-0.2/par2repairer.cpp --- ../libpar2-0.2-original/par2repairer.cpp 2012-12-03 10:47:04.000000000 +0100 +++ ../libpar2-0.2/par2repairer.cpp 2012-12-03 10:48:13.000000000 +0100 @@ -50,10 +50,12 @@ outputbuffer = 0; noiselevel = CommandLine::nlNormal; headers = new ParHeaders; alreadyloaded = false; + + cancelled = false; } Par2Repairer::~Par2Repairer(void) { delete [] (u8*)inputbuffer; @@ -404,10 +406,14 @@ { cout << "Loading: " << newfraction/10 << '.' << newfraction%10 << "%\r" << flush; progress = offset; sig_progress.emit(newfraction); + if (cancelled) + { + break; + } } } // Attempt to read the next packet header PACKET_HEADER header; @@ -582,10 +588,15 @@ if (noiselevel > CommandLine::nlQuiet) cout << "No new packets found" << endl; delete diskfile; } + if (cancelled) + { + return false; + } + return true; } // Finish loading a recovery packet bool Par2Repairer::LoadRecoveryPacket(DiskFile *diskfile, u64 offset, PACKET_HEADER &header) @@ -831,26 +842,42 @@ // Load packets from each file that was found for (list::const_iterator s=files->begin(); s!=files->end(); ++s) { LoadPacketsFromFile(*s); + if (cancelled) + { + break; + } } delete files; + if (cancelled) + { + return false; + } } { string wildcard = name.empty() ? "*.PAR2" : name + ".*.PAR2"; list *files = DiskFile::FindFiles(path, wildcard); // Load packets from each file that was found for (list::const_iterator s=files->begin(); s!=files->end(); ++s) { LoadPacketsFromFile(*s); + if (cancelled) + { + break; + } } delete files; + if (cancelled) + { + return false; + } } return true; } @@ -864,13 +891,22 @@ // If the filename contains ".par2" anywhere if (string::npos != filename.find(".par2") || string::npos != filename.find(".PAR2")) { LoadPacketsFromFile(filename); + if (cancelled) + { + break; + } } } + if (cancelled) + { + return false; + } + return true; } // Check that the packets are consistent and discard any that are not bool Par2Repairer::CheckPacketConsistency(void) @@ -1208,10 +1244,15 @@ // Start verifying the files sf = sortedfiles.begin(); while (sf != sortedfiles.end()) { + if (cancelled) + { + return false; + } + // Do we have a source file Par2RepairerSourceFile *sourcefile = *sf; // What filename does the file use string filename = sourcefile->TargetFileName(); @@ -1560,10 +1601,14 @@ if (oldfraction != newfraction) { cout << "Scanning: \"" << shortname << "\": " << newfraction/10 << '.' << newfraction%10 << "%\r" << flush; sig_progress.emit(newfraction); + if (cancelled) + { + break; + } } } // If we fail to find a match, it might be because it was a duplicate of a block // that we have already found. @@ -1649,10 +1694,15 @@ return false; } } } + if (cancelled) + { + return false; + } + // Get the Full and 16k hash values of the file filechecksummer.GetFileHashes(hashfull, hash16k); // Did we make any matches at all if (count > 0) @@ -2289,14 +2339,23 @@ if (oldfraction != newfraction) { cout << "Repairing: " << newfraction/10 << '.' << newfraction%10 << "%\r" << flush; sig_progress.emit(newfraction); + if (cancelled) + { + break; + } } } } + if (cancelled) + { + break; + } + ++inputblock; ++inputindex; } } else @@ -2346,13 +2405,22 @@ if (oldfraction != newfraction) { cout << "Processing: " << newfraction/10 << '.' << newfraction%10 << "%\r" << flush; sig_progress.emit(newfraction); + if (cancelled) + { + break; + } } } + if (cancelled) + { + break; + } + ++copyblock; ++inputblock; } } @@ -2360,10 +2428,15 @@ if (lastopenfile != NULL) { lastopenfile->Close(); } + if (cancelled) + { + return false; + } + if (noiselevel > CommandLine::nlQuiet) cout << "Writing recovered data\r"; // For each output block that has been recomputed vector::iterator outputblock = outputblocks.begin(); diff -aud -U 5 ../libpar2-0.2-with-bugfixes-patch/par2repairer.h ../libpar2-0.2/par2repairer.h --- ../libpar2-0.2-original/par2repairer.h 2012-12-03 10:47:04.000000000 +0100 +++ ../libpar2-0.2/par2repairer.h 2012-12-03 10:48:13.000000000 +0100 @@ -186,8 +186,9 @@ u64 progress; // How much data has been processed. u64 totaldata; // Total amount of data to be processed. u64 totalsize; // Total data size + bool cancelled; // repair cancelled }; #endif // __PAR2REPAIRER_H__ nzbget-12.0+dfsg/libsigc++-2.0.18-MSVC8.patch000066400000000000000000000276121226450633000200220ustar00rootroot00000000000000diff -urN libsigc++-2.0.18-original/sigc-2.0.sln libsigc++-2.0.18-modified/sigc-2.0.sln --- libsigc++-2.0.18-original/sigc-2.0.sln 1970-01-01 01:00:00.000000000 +0100 +++ libsigc++-2.0.18-modified/sigc-2.0.sln 2007-10-31 08:09:51.953427400 +0100 @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual C++ Express 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sigc-2.0", "sigc-2.0.vcproj", "{5882A1D7-2F65-4C0D-A88C-11A63A2B29EE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5882A1D7-2F65-4C0D-A88C-11A63A2B29EE}.Debug|Win32.ActiveCfg = Debug|Win32 + {5882A1D7-2F65-4C0D-A88C-11A63A2B29EE}.Debug|Win32.Build.0 = Debug|Win32 + {5882A1D7-2F65-4C0D-A88C-11A63A2B29EE}.Release|Win32.ActiveCfg = Release|Win32 + {5882A1D7-2F65-4C0D-A88C-11A63A2B29EE}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff -urN libsigc++-2.0.18-original/sigc-2.0.vcproj libsigc++-2.0.18-modified/sigc-2.0.vcproj --- libsigc++-2.0.18-original/sigc-2.0.vcproj 1970-01-01 01:00:00.000000000 +0100 +++ libsigc++-2.0.18-modified/sigc-2.0.vcproj 2007-10-31 14:04:13.867933500 +0100 @@ -0,0 +1,367 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -urN libsigc++-2.0.18-original/sigc++config.h libsigc++-2.0.18-modified/sigc++config.h --- libsigc++-2.0.18-original/sigc++config.h 1970-01-01 01:00:00.000000000 +0100 +++ libsigc++-2.0.18-modified/sigc++config.h 2007-10-31 08:10:45.607783900 +0100 @@ -0,0 +1,81 @@ +/* sigc++config.h. Generated from sigc++config.h.in by configure. */ + +#ifndef _SIGCXX_CONFIG_H +#define _SIGCXX_CONFIG_H + +// Detect common platforms +#if defined(_WIN32) + // Win32 compilers have a lot of variation + #if defined(_MSC_VER) + #define SIGC_MSC + #define SIGC_WIN32 +// #define SIGC_DLL + #elif defined(__CYGWIN__) + #define SIGC_CONFIGURE + #elif defined(__MINGW32__) + #define SIGC_WIN32 + #define SIGC_CONFIGURE + #else + //The Tru64 compiler complains about this "unrecognized preprocessing directive", but it should never get this far anyway. + //#warning "libsigc++ config: Unknown win32 architecture (send me gcc --dumpspecs or equiv)" + #endif +#else + #define SIGC_CONFIGURE +#endif /* _WIN32 */ + +#ifdef SIGC_CONFIGURE + // configure checks + #define SIGC_GCC_TEMPLATE_SPECIALIZATION_OPERATOR_OVERLOAD 1 + #define SIGC_MSVC_TEMPLATE_SPECIALIZATION_OPERATOR_OVERLOAD 1 + #define SIGC_SELF_REFERENCE_IN_MEMBER_INITIALIZATION 1 + + #define SIGC_HAVE_NAMESPACE_STD 1 +/* #undef SIGC_HAVE_SUN_REVERSE_ITERATOR */ +/* #undef SIGC_TYPEDEF_REDEFINE_ALLOWED */ + + // platform specific macros + // #define LIBSIGC_DISABLE_DEPRECATED + // #define SIGC_NEW_DELETE_IN_LIBRARY_ONLY +#endif /* SIGC_CONFIGURE */ + +#ifdef SIGC_MSC + + // MS VC7 Warning 4251 says that the classes to any member objects in an + // exported class must be also be exported. Some of the libsigc++ + // template classes contain std::list members. MS KB article 168958 says + // that it's not possible to export a std::list instantiation due to some + // wacky class nesting issues, so our only options are to ignore the + // warning or to modify libsigc++ to remove the std::list dependency. + // AFAICT, the std::list members are used internally by the library code + // and don't need to be used from the outside, and ignoring the warning + // seems to have no adverse effects, so that seems like a good enough + // solution for now. + // + #pragma warning(disable:4251) + + #define SIGC_MSVC_TEMPLATE_SPECIALIZATION_OPERATOR_OVERLOAD + #define SIGC_NEW_DELETE_IN_LIBRARY_ONLY // To keep ABI compatibility + #define SIGC_HAVE_NAMESPACE_STD 1 + +#endif /* SIGC_MSC */ + +//Put things in the std namespace, if they should be there. +#ifndef SIGC_HAVE_NAMESPACE_STD + # define SIGC_USING_STD(Symbol) namespace std { using ::Symbol; } +#else + # define SIGC_USING_STD(Symbol) /* empty */ +#endif + +#ifdef SIGC_DLL + #if defined(SIGC_BUILD) && defined(_WINDLL) + #define SIGC_API __declspec(dllexport) + #elif !defined(SIGC_BUILD) + #define SIGC_API __declspec(dllimport) + #else + #define SIGC_API + #endif /* SIGC_BUILD - _WINDLL */ +#else + #define SIGC_API +#endif /* SIGC_DLL */ + +#endif /* _SIGCXX_CONFIG_H */ nzbget-12.0+dfsg/missing000077500000000000000000000240361226450633000152510ustar00rootroot00000000000000#! /bin/sh # Common stub for a few missing GNU programs while installing. # Copyright (C) 1996, 1997, 1999, 2000, 2002 Free Software Foundation, Inc. # Originally by Fran,cois Pinard , 1996. # 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, 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., 59 Temple Place - Suite 330, Boston, MA # 02111-1307, USA. # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. if test $# -eq 0; then echo 1>&2 "Try \`$0 --help' for more information" exit 1 fi run=: # In the cases where this matters, `missing' is being run in the # srcdir already. if test -f configure.ac; then configure_ac=configure.ac else configure_ac=configure.in fi case "$1" in --run) # Try to run requested program, and just exit if it succeeds. run= shift "$@" && exit 0 ;; esac # If it does not exist, or fails to run (possibly an outdated version), # try to emulate it. case "$1" in -h|--h|--he|--hel|--help) echo "\ $0 [OPTION]... PROGRAM [ARGUMENT]... Handle \`PROGRAM [ARGUMENT]...' for when PROGRAM is missing, or return an error status if there is no known handling for PROGRAM. Options: -h, --help display this help and exit -v, --version output version information and exit --run try to run the given command, and emulate it if it fails Supported PROGRAM values: aclocal touch file \`aclocal.m4' autoconf touch file \`configure' autoheader touch file \`config.h.in' automake touch all \`Makefile.in' files bison create \`y.tab.[ch]', if possible, from existing .[ch] flex create \`lex.yy.c', if possible, from existing .c help2man touch the output file lex create \`lex.yy.c', if possible, from existing .c makeinfo touch the output file tar try tar, gnutar, gtar, then tar without non-portable flags yacc create \`y.tab.[ch]', if possible, from existing .[ch]" ;; -v|--v|--ve|--ver|--vers|--versi|--versio|--version) echo "missing 0.4 - GNU automake" ;; -*) echo 1>&2 "$0: Unknown \`$1' option" echo 1>&2 "Try \`$0 --help' for more information" exit 1 ;; aclocal*) if test -z "$run" && ($1 --version) > /dev/null 2>&1; then # We have it, but it failed. exit 1 fi echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified \`acinclude.m4' or \`${configure_ac}'. You might want to install the \`Automake' and \`Perl' packages. Grab them from any GNU archive site." touch aclocal.m4 ;; autoconf) if test -z "$run" && ($1 --version) > /dev/null 2>&1; then # We have it, but it failed. exit 1 fi echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified \`${configure_ac}'. You might want to install the \`Autoconf' and \`GNU m4' packages. Grab them from any GNU archive site." touch configure ;; autoheader) if test -z "$run" && ($1 --version) > /dev/null 2>&1; then # We have it, but it failed. exit 1 fi echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified \`acconfig.h' or \`${configure_ac}'. You might want to install the \`Autoconf' and \`GNU m4' packages. Grab them from any GNU archive site." files=`sed -n 's/^[ ]*A[CM]_CONFIG_HEADER(\([^)]*\)).*/\1/p' ${configure_ac}` test -z "$files" && files="config.h" touch_files= for f in $files; do case "$f" in *:*) touch_files="$touch_files "`echo "$f" | sed -e 's/^[^:]*://' -e 's/:.*//'`;; *) touch_files="$touch_files $f.in";; esac done touch $touch_files ;; automake*) if test -z "$run" && ($1 --version) > /dev/null 2>&1; then # We have it, but it failed. exit 1 fi echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified \`Makefile.am', \`acinclude.m4' or \`${configure_ac}'. You might want to install the \`Automake' and \`Perl' packages. Grab them from any GNU archive site." find . -type f -name Makefile.am -print | sed 's/\.am$/.in/' | while read f; do touch "$f"; done ;; autom4te) if test -z "$run" && ($1 --version) > /dev/null 2>&1; then # We have it, but it failed. exit 1 fi echo 1>&2 "\ WARNING: \`$1' is needed, and you do not seem to have it handy on your system. You might have modified some files without having the proper tools for further handling them. You can get \`$1Help2man' as part of \`Autoconf' from any GNU archive site." file=`echo "$*" | sed -n 's/.*--output[ =]*\([^ ]*\).*/\1/p'` test -z "$file" && file=`echo "$*" | sed -n 's/.*-o[ ]*\([^ ]*\).*/\1/p'` if test -f "$file"; then touch $file else test -z "$file" || exec >$file echo "#! /bin/sh" echo "# Created by GNU Automake missing as a replacement of" echo "# $ $@" echo "exit 0" chmod +x $file exit 1 fi ;; bison|yacc) echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified a \`.y' file. You may need the \`Bison' package in order for those modifications to take effect. You can get \`Bison' from any GNU archive site." rm -f y.tab.c y.tab.h if [ $# -ne 1 ]; then eval LASTARG="\${$#}" case "$LASTARG" in *.y) SRCFILE=`echo "$LASTARG" | sed 's/y$/c/'` if [ -f "$SRCFILE" ]; then cp "$SRCFILE" y.tab.c fi SRCFILE=`echo "$LASTARG" | sed 's/y$/h/'` if [ -f "$SRCFILE" ]; then cp "$SRCFILE" y.tab.h fi ;; esac fi if [ ! -f y.tab.h ]; then echo >y.tab.h fi if [ ! -f y.tab.c ]; then echo 'main() { return 0; }' >y.tab.c fi ;; lex|flex) echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified a \`.l' file. You may need the \`Flex' package in order for those modifications to take effect. You can get \`Flex' from any GNU archive site." rm -f lex.yy.c if [ $# -ne 1 ]; then eval LASTARG="\${$#}" case "$LASTARG" in *.l) SRCFILE=`echo "$LASTARG" | sed 's/l$/c/'` if [ -f "$SRCFILE" ]; then cp "$SRCFILE" lex.yy.c fi ;; esac fi if [ ! -f lex.yy.c ]; then echo 'main() { return 0; }' >lex.yy.c fi ;; help2man) if test -z "$run" && ($1 --version) > /dev/null 2>&1; then # We have it, but it failed. exit 1 fi echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified a dependency of a manual page. You may need the \`Help2man' package in order for those modifications to take effect. You can get \`Help2man' from any GNU archive site." file=`echo "$*" | sed -n 's/.*-o \([^ ]*\).*/\1/p'` if test -z "$file"; then file=`echo "$*" | sed -n 's/.*--output=\([^ ]*\).*/\1/p'` fi if [ -f "$file" ]; then touch $file else test -z "$file" || exec >$file echo ".ab help2man is required to generate this page" exit 1 fi ;; makeinfo) if test -z "$run" && (makeinfo --version) > /dev/null 2>&1; then # We have makeinfo, but it failed. exit 1 fi echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified a \`.texi' or \`.texinfo' file, or any other file indirectly affecting the aspect of the manual. The spurious call might also be the consequence of using a buggy \`make' (AIX, DU, IRIX). You might want to install the \`Texinfo' package or the \`GNU make' package. Grab either from any GNU archive site." file=`echo "$*" | sed -n 's/.*-o \([^ ]*\).*/\1/p'` if test -z "$file"; then file=`echo "$*" | sed 's/.* \([^ ]*\) *$/\1/'` file=`sed -n '/^@setfilename/ { s/.* \([^ ]*\) *$/\1/; p; q; }' $file` fi touch $file ;; tar) shift if test -n "$run"; then echo 1>&2 "ERROR: \`tar' requires --run" exit 1 fi # We have already tried tar in the generic part. # Look for gnutar/gtar before invocation to avoid ugly error # messages. if (gnutar --version > /dev/null 2>&1); then gnutar "$@" && exit 0 fi if (gtar --version > /dev/null 2>&1); then gtar "$@" && exit 0 fi firstarg="$1" if shift; then case "$firstarg" in *o*) firstarg=`echo "$firstarg" | sed s/o//` tar "$firstarg" "$@" && exit 0 ;; esac case "$firstarg" in *h*) firstarg=`echo "$firstarg" | sed s/h//` tar "$firstarg" "$@" && exit 0 ;; esac fi echo 1>&2 "\ WARNING: I can't seem to be able to run \`tar' with the given arguments. You may want to install GNU tar or Free paxutils, or check the command line arguments." exit 1 ;; *) echo 1>&2 "\ WARNING: \`$1' is needed, and you do not seem to have it handy on your system. You might have modified some files without having the proper tools for further handling them. Check the \`README' file, it often tells you about the needed prerequirements for installing this package. You may also peek at any GNU archive site, in case some other package would contain this missing \`$1' program." exit 1 ;; esac exit 0 nzbget-12.0+dfsg/nzbget-shell.bat000066400000000000000000000026171226450633000167410ustar00rootroot00000000000000@echo off rem rem Batch file to start nzbget shell rem rem Copyright (C) 2009 orbisvicis rem Copyright (C) 2009 Andrey Prygunkov rem rem This program is free software; you can redistribute it and/or modify rem it under the terms of the GNU General Public License as published by rem the Free Software Foundation; either version 2 of the License, or rem (at your option) any later version. rem rem This program is distributed in the hope that it will be useful, rem but WITHOUT ANY WARRANTY; without even the implied warranty of rem MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the rem GNU General Public License for more details. rem rem You should have received a copy of the GNU General Public License rem along with this program; if not, write to the Free Software rem Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. rem rem ####################### Usage instructions ####################### rem rem After starting the batch file you can use all nzbget commands rem (like nzbget -s, nzbget -L, etc) without typing the full rem path to nzbget executable. rem rem ####################### End of Usage instructions ####################### rem expression "%~dp0" means the location of an executing batch file set PATH=%PATH%;%~dp0 cmd /U /K "cd %USERPROFILE% & nzbget" nzbget-12.0+dfsg/nzbget.conf000066400000000000000000001616701226450633000160200ustar00rootroot00000000000000# Sample configuration file for NZBGet # # 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/myconfig.txt # For quick start change the option MainDir and configure one news-server ############################################################################## ### PATHS ### # Root directory for all tasks. # # On POSIX you can use "~" as alias for home directory (e.g. "~/downloads"). # On Windows use absolute paths (e.g. "C:\Downloads"). MainDir=~/downloads # Destination directory for downloaded files. # # If you want to distinguish between partially downloaded files and # completed downloads, use also option . DestDir=${MainDir}/dst # Directory to store intermediate files. # # If this option is set (not empty) the files are downloaded into # this directory first. After successful download of nzb-file (possibly # after par-repair) the files are moved to destination directory # (option ). If download or unpack fail the files remain in # intermediate directory. # # Using of intermediate directory can significantly improve unpack # performance if you can put intermediate directory (option ) # and destination directory (option ) on separate physical # hard drives. # # NOTE: If the option is set to empty value the downloaded # files are put directly to destination directory (option ). InterDir=${MainDir}/inter # Directory for incoming nzb-files. # # If a new nzb-file is added to queue via web-interface or RPC-API, it # is saved into this directory and then processed by pre-processing # script (option ). # # This directory is also monitored for new nzb-files. If a new file # is found it is added to download queue. The directory can have # sub-directories. A nzb-file queued from a subdirectory is automatically # assigned to category with sub-directory-name. NzbDir=${MainDir}/nzb # Directory to store program state. # # This directory is used to save download queue, history, information # about fetched RSS feeds, statistics, etc. QueueDir=${MainDir}/queue # Directory to store temporary files. TempDir=${MainDir}/tmp # Directory with web-interface files. # # Example: /usr/local/share/nzbget/webui. # # NOTE: To disable web-interface set the option to an empty value. # This however doesn't disable the built-in web-server completely because # it is also used to serve JSON-/XML-RPC requests. WebDir= # Directory with post-processing scripts. # # NOTE: For information on writing post-processing scripts visit # http://nzbget.sourceforge.net/Post-processing_scripts. ScriptDir=${MainDir}/ppscripts # Lock-file for daemon-mode, POSIX only. # # If the option is not empty, NZBGet creates the file and writes process-id # (PID) into it. That info can be used in shell scripts. LockFile=${MainDir}/nzbget.lock # Where to store log file, if it needs to be created. # # NOTE: See also option . LogFile=${DestDir}/nzbget.log # Configuration file template. # # Put the path to the example configuration file which comes with # NZBGet. Web-interface needs this file to read option descriptions. # # Do not put here your actual configuration file (typically stored # in your home directory or in /etc/nzbget.conf) but instead the unchanged # example configuration file (typically installed to # /usr/local/share/nzbget/nzbget.conf). # # Example: /usr/local/share/nzbget/nzbget.conf. ConfigTemplate= ############################################################################## ### NEWS-SERVERS ### # This section defines which servers NZBGet should connect to. # # The servers should be numbered subsequently without holes. # For example if you configure three servers you should name them as Server1, # Server2 and Server3. If you need to delete Server2 later you should also # change the name of Server3 to Server2. Otherwise it will not be properly # read from the config file. Server number doesn't affect its priority (level). # Use this news server (yes, no). # # Set to "no" to temporary disable the server. Server1.Active=yes # Name of news server. # # The name is used in UI and for logging. It can be any string, you # may even leave it empty. Server1.Name= # Level (priority) of news server (0-99). # # The servers are ordered by their level. NZBGet first tries to download # an article from one (any) of level-0-servers. If that server fails, # NZBGet tries all other level-0-servers. If all servers fail, it proceeds # with the level-1-servers, etc. # # Put your major download servers at level 0 and your fill servers at # levels 1, 2, etc.. # # Several servers with the same level may be defined, they have # the same priority. Server1.Level=0 # Group of news server (0-99). # # If you have multiple accounts with same conditions (retention, etc.) # on the same news server, set the same group (greater than 0) for all # of them. If download fails on one news server, NZBGet does not try # other servers from the same group. # # Value "0" means no group defined (default). Server1.Group=0 # Host name of news server. Server1.Host=my.newsserver.com # Port to connect to (1-65535). Server1.Port=119 # User name to use for authentication. Server1.Username=user # Password to use for authentication. Server1.Password=pass # Server requires "Join Group"-command (yes, no). Server1.JoinGroup=no # Encrypted server connection (TLS/SSL) (yes, no). # # NOTE: By changing this option you should also change the option # accordingly because unsecure and encrypted connections use different ports. Server1.Encryption=no # Cipher to use for encrypted server connection. # # By default (when the option is empty) the underlying encryption library # chooses the cipher automatically. To achieve the best performance # however you can manually select a faster cipher. # # See http://nzbget.sourceforge.net/Choosing_a_cipher for details. # # NOTE: One of the fastest cipher is RC4, it also provides strong 128 bit # encryption. To select it use the cipher string "RC4-MD5" (if NZBGet was # configured to use OpenSSL) or "NONE:+VERS-TLS-ALL:+ARCFOUR-128:+RSA:+MD5:+COMP-ALL" # (if NZBGet was configured to use GnuTLS). # # NOTE: You may get a TLS handshake error if the news server does # not support the chosen cipher. You can also get an error "Could not # select cipher for TLS" if the cipher string is not valid. Server1.Cipher= # Maximum number of simultaneous connections to this server (0-999). Server1.Connections=4 # Second server, on level 0. #Server2.Level=0 #Server2.Host=my2.newsserver.com #Server2.Port=119 #Server2.Username=me #Server2.Password=mypass #Server2.JoinGroup=yes #Server2.Connections=4 # Third server, on level 1. #Server3.Level=1 #Server3.Host=fills.newsserver.com #Server3.Port=119 #Server3.Username=me2 #Server3.Password=mypass2 #Server3.JoinGroup=yes #Server3.Connections=1 ############################################################################## ### SECURITY ### # IP on which NZBGet server listen and which clients use to contact NZBGet. # # It could be a dns-hostname (e. g. "mypc") or an ip-address (e. g. "192.168.1.2" or # "127.0.0.1"). An IP-address is more effective because does not require dns-lookup. # # Your computer may have multiple network interfaces and therefore multiple IP # addresses. If you want NZBGet to listen to all interfaces and be available from # all IP-addresses use value "0.0.0.0". # # NOTE: When you start NZBGet as client (to send remote commands to NZBGet server) and # the option is set to "0.0.0.0" the client will use IP "127.0.0.1". # # NOTE: If you set the option to "127.0.0.1" you will be able to connect to NZBGet # only from the computer running NZBGet. This restriction applies to web-interface too. ControlIP=0.0.0.0 # Port which NZBGet server and remote client use (1-65535). # # NOTE: The communication via this port is not encrypted. For encrypted # communication see option . ControlPort=6789 # User name which NZBGet server and remote client use. # # Set to empty value to disable user name check (check only password). # # NOTE: this option was added in NZBGet 11. Older versions used predefined # not changeable user name "nzbget". Third-party tools or web-sites written # for older NZBGet versions may not have an option to define user name. In # this case you should set option to the default value # "nzbget" or use empty value. ControlUsername=nzbget # Password which NZBGet server and remote client use. # # Set to empty value to disable authorization request. ControlPassword=tegbzn6789 # Secure control of NZBGet server (yes, no). # # Activate the option if you want to access NZBGet built-in web-server # via HTTPS (web-interface and RPC). You should also provide certificate # and key files, see option and option . SecureControl=no # Port which NZBGet server and remote client use for encrypted # communication (1-65535). SecurePort=6791 # Full path to certificate file for encrypted communication. SecureCert= # Full path to key file for encrypted communication. SecureKey= # IP-addresses allowed to connect without authorization. # # Comma separated list of privileged IPs for easy access to NZBGet # built-in web-server (web-interface and RPC). # # Example: 127.0.0.1,192.168.178.2. # # NOTE: Do not use this option if the program works behind another # web-server because all requests will have the address of this server. AuthorizedIP= # User name for daemon-mode, POSIX only. # # Set the user that the daemon normally runs at (POSIX in daemon-mode only). # 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 (000-1000). # # The value should be written in octal form (the same as for "umask" shell # command). # Empty value or value "1000" disable the setting of umask-mode; current # umask-mode (set via shell) is used in this case. UMask=1000 ############################################################################## ### CATEGORIES ### # This section defines categories available in web-interface. # Category name. # # Each nzb-file can be assigned to a category. # Category name is passed to post-processing script and can be used by it # to perform category specific processing. Category1.Name=Movies # Destination directory for this category. # # If this option is empty, then the default destination directory # (option ) is used. In this case if the option # is active, the program creates a subdirectory with category name within # destination directory. Category1.DestDir= # Unpack downloaded nzb-files (yes, no). # # For more information see global option . Category1.Unpack=yes # Default list of post-processing scripts. # # For more information see global option . Category1.DefScript= # List of aliases. # # When a nzb-file is added from URL, RSS or RPC the category name # is usually supplied by nzb-site or by application accessing # NZBGet. Using Aliases you can match their categories with your owns. # # Separate aliases with commas or semicolons. Use wildcard-characters # * and ? for pattern matching. # # Example: TV - HD, TV - SD, TV* Category1.Aliases= Category2.Name=Series Category3.Name=Music Category4.Name=Software ############################################################################## ### RSS FEEDS ### # Name of RSS Feed. # # The name is used in UI and for logging. It can be any string. #Feed1.Name=my feed # Address (URL) of RSS Feed. # # Example: https://myindexer.com/api?apikey=3544646bfd1c535a9654645609800901&t=search&q=game. # # NOTE: When the feed is fetched for the very first time all existing # items are ignored. The items found on subsequentional fetches are processed. #Feed1.URL= # Filter rules for items. # # Use filter to ignore unwanted items in the feed. In its simplest version # the filter is a space separated list of words which must be present in # the item title. # # Example: linux debian dvd. # # MORE INFO: # NOTE: This is a short documentation, for more information visit # http://nzbget.sourceforge.net/RSS. # # Feed filter consists of rules - one rule per line. Each rule defines # a search string and a command, which must be performed if the search # string matches. There are five kinds of rule-commands: Accept, # Reject, Require, Options, Comment. # # NOTE: Since options in the configuration file can not span multiple # lines, the lines (rules) must be separated with %-character (percent). # # Definition of a rule: # [A:|A(options):|R:|Q:|O(options):|#] search-string # # A - declares Accept-rule. Rules are accept-rules by default, the # "A:" can be imitted. If the feed item matches to the rule the # item is considered good and no further rules are checked. # R - declares Reject-rule. If the feed item matches to the rule the # item is considered bad and no further rules are checked. # Q - declares Require-rule. If the feed item DOES NOT match to the rule # the item is considered bad and no further rules are checked. # O - declares Options-rule. If the feed item matches to the rule the # options declared in the rule are set for the item. The item is # neither accepted nor rejected via this rule but can be accepted # later by one of Accept-rules. In this case the item will have its # options already set (unless the Accept-rule overrides them). # # - lines starting with # are considered comments and are ignored. You # can use comments to explain complex rules or to temporary disable # rules for debugging. # # Options allow to set properties on nzb-file. It's a comma-separated # list of property names with their values. # # Definition of an option: # name:value # # Options can be defined using long option names or short names: # category (cat, c) - set category name, value is a string; # pause (p) - add nzb in paused or unpaused state, possible # values are: yes (y), no (n); # priority (pr, r) - set priority, value is a signed integer number; # priority+ (pr+, r+) - increase priority, value is a signed integer number; # dupescore (ds, s) - set duplicate score, value is a signed integer number; # dupescore+ (ds+, s+) - increase duplicate score, value is a signed integer number; # dupekey (dk, k) - set duplicate key, value is a string; # dupekey+ (dk+, k+) - add to duplicate key, value is a string; # dupemode (dm, m) - set duplicate check mode, possible values # are: score (s), all (a), force (f); # rageid - generate duplicate key using this rageid # (integer number) and season/episode numbers; # series - generate duplicate key using series identifier # (any unique string) and season/episode numbers. # # Examples of option definitions: # Accept(category:my series, pause:yes, priority:100): my show 1080p; # Options(c:my series, p:y, r:100): 1080p; # Options(s:1000): 1080p; # Options(k+:1080p): 1080p; # Options(dupemode:force): BluRay. # # Rule-options override values set in feed-options. # # The search-string is similar to used in search engines. It consists of # search terms separated with spaces. Every term is checked for a feed # item and if they all succeed the rule is considered matching. # # Definition of a term: # [+|-][field:][command]param # # + - declares a positive term. Terms are positive by default, # the "+" can be omitted; # - - declares a negative term. If the term succeed the feed # item is ignored; # field - field to which apply the term. If not specified # the default field "title" is used; # command - a special character defining how to interpret the # parameter (followed after the command): # @ - search for string "param". This is default command, # the "@" can be omitted; # $ - "param" defines a regular expression (using POSIX Extended # Regular Expressions syntax); # = - equal; # < - less than; # <= - equal or less than; # > - greater than; # >= - equal or greater than; # param - parameter for command. # # Commands @ and $ are for use with text fields (title, filename, category, # link, description, dupekey). Commands =, <, <=, > and >= are for use # with numeric fields (size, age, imdbid, rageid, season, episode, priority, # dupescore). # # Only fields title, filename and age are always present. The availability of # other fields depend on rss feed provider. # # Any newznab attribute (encoded as "newznab:attr" in the RSS feed) can # be used as search field with prefix "attr-", for example "attr-genre". # # Text search (Command @) supports supports wildcard characters * (matches # any number of any characters), ? (matches any one character) # and # (matches one digit). # Text search is by default performed against words (word-search mode): the # field content is separated into words and then each word is checked # against pattern. If the search pattern starts and ends with * (star) # the search is performed against the whole field content # (substring-search mode). If the search pattern contains word separator # characters (except * and ?) the search is performed on the whole # field (the word-search would be obviously never successful in this # case). Word separators are: !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~. # # Field "size" can have suffixes "K" or "KB" for kilobytes, "M" or "MB" # for megabytes and "G" or "GB" for gigabytes. Field "age" can have # suffixes "m" for minutes, "h" for hours and "d" for days. If suffix # is not specified default is days. # # Examples (the trailing ; or . is not part of filter): # 1) A: s01* -category:anime; # 2) my show WEB-DL; # 3) *my?show* WEB-DL size:<1.8GB age:>2h; # 4) R: size:>9GB; # 5) Q: HDTV. # # NOTE: This is a short documentation, for more information visit # http://nzbget.sourceforge.net/RSS. #Feed1.Filter= # How often to check for new items (minutes). # # Value "0" disables the automatic check of this feed. #Feed1.Interval=15 # Add nzb-files as paused (yes, no). #Feed1.PauseNzb=no # Category for added nzb-files. # # NOTE: Feed providers may include category name within response when nzb-file # is downloaded. If you want to use the providers category leave the option empty. #Feed1.Category= # Priority for added nzb-files (number). # # Priority can be any integer value. The web-interface however operates # with only five predefined priorities: -100 (very low priority), -50 # (low priority), 0 (normal priority, default), 50 (high priority), # 100 (very high priority). #Feed1.Priority=0 ############################################################################## ### INCOMING NZBS ### # Create subdirectory with category-name in destination-directory (yes, no). AppendCategoryDir=yes # How often incoming-directory (option ) must be checked for new # nzb-files (seconds). # # Value "0" disables the check. NzbDirInterval=5 # How old nzb-file should at least be for it to be loaded to queue (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 # Set path to program, that must be executed before a nzb-file is added # to queue. # # This program is called each time a new file is found in incoming # directory (option ) or a file is received via RPC (web-interface, # command "nzbget --append", etc.). # # Example: ~/nzbprocess.sh. # # That program can unpack archives which were put in incoming directory, make # filename cleanup, change nzb-name, category, priority and post-processing # parameters of the nzb-file or do other things. # # INFO FOR DEVELOPERS: # NZBGet passes following arguments to nzbprocess-program as environment # variables: # NZBNP_DIRECTORY - path to directory, where file is located. It is a directory # specified by the option or a subdirectory; # NZBNP_FILENAME - name of file to be processed; # NZBNP_NZBNAME - nzb-name (without path but with extension); # NZBNP_CATEGORY - category of nzb-file; # NZBNP_PRIORITY - priority of nzb-file; # NZBNP_TOP - flag indicating that the file will be added to the top # of queue: 0 or 1; # NZBNP_PAUSED - flag indicating that the file will be added as # paused: 0 or 1. # # In addition to these arguments NZBGet passes all # nzbget.conf-options to nzbprocess-program as environment variables. These # variables have prefix "NZBOP_" and are written in UPPER CASE. For Example # option "ParRepair" is passed as environment variable "NZBOP_PARREPAIR". # The dots in option names are replaced with underscores, for example # "SERVER1_HOST". For options with predefined possible values (yes/no, etc.) # the values are passed always in lower case. # # The nzbprocess-script can change nzb-name, category, priority, # post-processing parameters and top-/paused-flags of the nzb-file # by printing special messages into standard output (which is processed # by NZBGet). # # To change nzb-name use following syntax: # echo "[NZB] NZBNAME=my download"; # # To change category: # echo "[NZB] CATEGORY=my category"; # # To change priority: # echo "[NZB] PRIORITY=signed_integer_value"; # # for example: to set priority higher than normal: # echo "[NZB] PRIORITY=50"; # # another example: use a negative value for "lower than normal" priority: # echo "[NZB] PRIORITY=-100"; # # Although priority can be any integer value, the web-interface operates # with five predefined priorities: # -100 - very low priority; # -50 - low priority; # 0 - normal priority (default); # 50 - high priority; # 100 - very high priority. # # To assign post-processing parameters: # echo "[NZB] NZBPR_myvar=my value"; # # The prefix "NZBPR_" will be removed. In this example a post-processing # parameter with name "myvar" and value "my value" will be associated # with nzb-file. # # To change top-flag (nzb-file will be added to the top of queue): # echo "[NZB] TOP=1"; # # To change paused-flag (nzb-file will be added in paused state): # echo "[NZB] PAUSED=1"; # # The nzbprocess-script can delete processed file, rename it or move somewhere. # After the calling of the script the file will be either added to queue # (if it was an nzb-file) or renamed by adding the extension ".processed". # # NOTE: Files with extensions ".processed", ".queued" and ".error" are skipped # during the directory scanning. # # NOTE: Files with extension ".nzb_processed" are not passed to # NzbProcess-script before adding to queue. This feature allows # NzbProcess-script to prevent the scanning of nzb-files extracted from # archives, if they were already processed by the script. # # NOTE: Files added via RPC calls in particular from web-interface are # saved into incoming nzb-directory and then processed by the script. NzbProcess= # Set path to program, that must be executed after a nzb-file is added # to queue. # # This program is called each time a new nzb-file is added to queue. # # Example: ~/nzbaddedprocess.sh. # # That program can modify the files in download queue (for example # delete or pause all nfo, sfv, sample files) or do something else. # # INFO FOR DEVELOPERS: # NZBGet passes following arguments to nzbaddedprocess-program as environment # variables: # NZBNA_NZBNAME - name of nzb-group. This name can be used in calls # to nzbget edit-command using subswitch "-GN name"; # NZBNA_FILENAME - filename of the nzb-file. If the file was added # from nzb-directory this is the fullname with path. # If the file was added via web-interface it contains # only filename without path; # NZBNA_CATEGORY - category of nzb-file (if assigned); # NZBNA_LASTID - the id of the last file in the nzb-file. This ID can # be used with calls to nzbget edit-command; # NZBNA_PRIORITY - priority (default is 0). # # In addition to these arguments NZBGet passes all # nzbget.conf-options to nzbaddedprocess-program as environment variables. These # variables have prefix "NZBOP_" and are written in UPPER CASE. For Example # option "ParRepair" is passed as environment variable "NZBOP_PARREPAIR". # The dots in option names are replaced with underscores, for example # "SERVER1_HOST". For options with predefined possible values (yes/no, etc.) # the values are passed always in lower case. # # Examples: # 1) pausing nzb-file using file-id: # "$NZBOP_APPBIN" -c "$NZBOP_CONFIGFILE" -E G P $NZBNA_LASTID; # 2) setting category using nzb-name: # "$NZBOP_APPBIN" -c "$NZBOP_CONFIGFILE" -E GN K "my cat" "$NZBNA_NZBNAME"; # 3) pausing files with extension "nzb": # "$NZBOP_APPBIN" -c "$NZBOP_CONFIGFILE" -E FR P "$NZBNA_NZBNAME/.*\.nzb"; NzbAddedProcess= # Check for duplicate titles (yes, no). # # If this option is enabled the program checks by adding of a new nzb-file: # 1) if history contains the same title (see below) with success status # the nzb-file is not added to queue; # 2) if download queue already contains the same title the nzb-file is # added to queue for backup (if firt file fails); # 3) if nzb-file contains duplicate entries. This helps to find errors # in bad nzb-files. # # "Same title" means the nzb file name is same or the duplicate key is # same. Duplicate keys are set by fetching from RSS feeds using title # identifier fields provided by RSS provider (imdbid or rageid/season/episode). # # If duplicates were detected only one of them is downloaded. If download # fails another duplicate is tried. If download succeeds all remaining # duplicates are deleted from queue. # # NOTE: for automatic duplicate handling option must be # set to "Delete" or "None". If it is set to "Pause" you will need to # manually unpause another duplicate (if any exists in queue). # # NOTE: For more info on duplicates see http://nzbget.sourceforge.net/RSS. DupeCheck=yes ############################################################################## ### DOWNLOAD QUEUE ### # Save download queue to disk (yes, no). # # This allows to reload it on next start. SaveQueue=yes # Reload download queue on start, if it exists (yes, no). ReloadQueue=yes # Reload url-queue on start, if it exists (yes, no). # # For this option to work the options and must # be also enabled. ReloadUrlQueue=yes # Reload Post-processor-queue on start, if it exists (yes, no). # # For this option to work the options and must # be also enabled. ReloadPostQueue=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 # Decode articles (yes, no). # # yes - decode articles using internal decoder (supports yEnc and UU formats); # no - the articles will not be decoded and joined. Useful for debugging to # look at article's source text. Decode=yes # Write decoded articles directly into destination output file (yes, no). # # Files are posted to Usenet within artilce bodies. Each file typically # requires hundreds of articles. # # When option is disabled, the program downloads all articles # into temporary directory and then combine them into destination file. # # 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. # # This may improve performance but depends on OS and file system ability to # instantly create large files without initializing them with nulls. Such # files are called sparse files and are supported by modern file systems # like EXT3 on Linux or NTFS on Windows. # # Using of this option reduces disk operations but may produce more fragmented # files (depends on disk driver), which may slow down the post-processing. # It's recommended to test how the option behave on your platform to find the # best setting. # # INFO: a particular test on a Linux router with EXT3-partition showed that # activating of this option results in up to 20% better performance during # downloading. # # NOTE: For test try to download few big nzb-collections (each 4GB or more) # and measure the time used for downloading and post-processing (use timestamps # in a log-file to determine when the post-processing was ended). # # NOTE: When option is enabled the temporary directory (option # ) must be located on the same partition with destination directory # (option DestDir>) for better performance. If option is disabled # it's better to use different drives for temporary and destination directories. # # NOTE: If both options and are enabled, # the program still creates empty article-files in temp-directory. They are used # by the option to check if a certain article was downloaded. # To minimize disk-io it is recommended to disable option , # if is enabled. Especially on a fast connections (where you # would want to activate ) it should not be a problem to redownload # an interrupted file. DirectWrite=yes # 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 CPU time. On a fast connection and # slow CPU disabling of CRC-Check may improve performance. CrcCheck=yes # How many retries should be attempted if a download error occurs (0-99). # # 1) If download fails because of "article or group not found error" the # program tries another news server. # # 2) If download fails because of interrupted connection, the program # tries the same server again until connection can be established. # # In both cases 1) and 2) option is not used. # # If download however fails because of incomplete article, CRC-error or other # error not mentioned above the program tries to redownload the article from # the same news server as many times as defined in option . If all # attempts fail the program tries another news server. Retries=3 # Set the interval between retries (seconds). RetryInterval=10 # Set connection timeout (seconds). ConnectionTimeout=60 # Timeout until a download-thread should be killed (seconds). # # This can help on hanging downloads, but is dangerous. # Do not use small values! TerminateTimeout=600 # Set the maximum download rate on program start (kilobytes/sec). # # The download rate can be changed later via remote calls. # # Value "0" means no speed control. DownloadRate=0 # Accurate speed rate calculation (yes, no). # # During downloading using several connections the download threads may # interfere with each other when updating statistical data for speed # meter. This may cause small errors in current download speed reported # by the program. The speed meter recovers automatically from such errors # after max. 30 seconds (time window used for speed calculation). # # Enable the option to use thread synchronisation mechanisms in order to # provide absolutely accurate speed calculations. # # NOTE: Thread synchronisation increases CPU load and therefore can # decrease download speed. Do not activate this option on computers with # limited CPU power. Before activating the option it is recommended to # run tests to determine how the option affects the CPU usage and the # download speed on a particular system. AccurateRate=no # Set the size of memory buffer used by writing the articles (bytes). # # Bigger values decrease disk-io, but increase memory usage. # Value "0" causes an OS-dependent 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-dependent size) could be better alternative. # # NOTE: Write-buffer is managed by OS (system libraries) and therefore # the effect of the option is highly OS-dependent. WriteBufferSize=0 # Pause if disk space gets below this value (megabytes). # # Disk space is checked for directories pointed by option and # option . # # Value "0" disables the check. DiskSpace=250 # Delete already downloaded files from disk when nzb-file is deleted # (yes, no). # # This option defines if downloaded files must be deleted when: # 1) download of nzb-file is cancelled (deleted from queue); # 2) history record with failure-status (par-failure or unpack-failure) # is deleted from history. DeleteCleanupDisk=yes # Delete source nzb-file when it is not needed anymore (yes, no). # # Enable this option for automatic deletion of source nzb-file from # incoming directory when the program doesn't require it anymore (the # nzb-file has been deleted from queue and history). NzbCleanupDisk=yes # Keep the history of downloaded nzb-files (days). # # After download and post-processing the items are added to history where # their status can be checked and they can be post-processed again if # neccessary. # # After expiring of defined period: # # If option is active the items become hidden and the amount # of data kept is significantly reduced (for better performance), only # fields necessary for duplicate check are kept. The item remain in the # hidden history (forever); # # If option is NOT active the items are removed from history. # # Value "0" disables history. Duplicate check will not work. KeepHistory=30 # Keep the history of outdated feed items (days). # # After fetching of an RSS feed the information about included items (nzb-files) # is saved to disk. This allows to detect new items on next fetch. Feed # providers update RSS feeds constantly. Since the feed length is limited # (usually 100 items or less) the old items get pushed away by new # ones. When an item is not present in the feed anymore it's not necessary # to keep the information about this item on the disk. # # If option is set to "0", the outdated items are deleted from history # immediately. # # Otherwise the items are held in the history for defined number of # days. Keeping of items for few days helps in situations when feed provider # has technical issues and may response with empty feeds (or with missing # items). When the technical issue is fixed the items may reappear in the # feed causing the program to redownload items if they were not found in # the feed history. FeedHistory=7 # Maximum number of simultaneous connections for nzb URL downloads (0-999). # # When NZB-files are added to queue via URL, the program downloads them # from the specified URL. The option limits the maximal number of connections # used for this purpose, when multiple URLs were added at the same time. UrlConnections=4 # Force URL-downloads even if download queue is paused (yes, no). # # If option is active the URL-downloads (such as appending of nzb-files # via URL or fetching of RSS feeds and nzb-files from feeds) are performed # even if download is in paused state. UrlForce=yes ############################################################################## ### LOGGING ### # Create log file (yes, no). CreateLog=yes # Delete log file upon server start (only in server-mode) (yes, no). ResetLog=no # How error messages must be printed (screen, log, both, none). ErrorTarget=both # How warning messages must be printed (screen, log, both, none). WarningTarget=both # How info messages must be printed (screen, log, both, none). InfoTarget=both # How detail messages must be printed (screen, log, both, none). DetailTarget=both # How debug messages must be printed (screen, log, both, none). # # Debug-messages can be printed only if the program was compiled in # debug-mode: "./configure --enable-debug". DebugTarget=both # Number of messages stored in buffer and available for remote # clients (messages). 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 # Create memory dump (core-file) on abnormal termination, Linux only (yes, no). # # Core-files are very helpful for debugging. # # NOTE: Core-files may contain sensible data, like your login/password to # newsserver etc. DumpCore=no # Local time correction (hours or minutes). # # The option allows to adjust timestamps when converting system time to # local time and vice versa. The conversion is used when printing messages # to the log-file and by option "TaskX.Time" in the scheduler settings. # # The option is usually not needed if the time zone is set up correctly. # However, sometimes, especially when using a binary compiled on onother # platform (cross-compiling) the conversion between system and local time # may not work properly and requires adjustment. # # Values in the range -24..+24 are interpreted as hours, other values as minutes. # Example 1: set time correction to one hour: TimeCorrection=1; # Example 2: set time correction to one hour and a half: TimeCorrection=90. TimeCorrection=0 # See also option in section "PATHS" ############################################################################## ### DISPLAY (TERMINAL) ### # 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-sequences to move cursor; # curses - advanced interactive interface with the ability to edit # download queue and various output option. 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 console mode or remote client # mode (milliseconds). # # Min value 25. Bigger values reduce CPU usage (especially in curses-outputmode) # and network traffic in remote-client mode. UpdateInterval=200 ############################################################################## ### SCHEDULER ### # This section defines scheduler commands. # For each command create a set of options , , # and . # The following example shows how to throttle downloads in the daytime # by 100 KB/s and download at full speed overnights: # Time to execute the command (HH:MM). # # Multiple comma-separated values are accepted. # Asterix as hours-part means "every hour". # # Examples: "08:00", "00:00,06:00,12:00,18:00", "*:00", "*:00,*:30". # # NOTE: also see option . #Task1.Time=08:00 # Week days to execute the command (1-7). # # Comma separated list of week days numbers. # 1 is Monday. # Character '-' may be used to define ranges. # # Examples: "1-7", "1-5", "5,6", "1-5, 7". #Task1.WeekDays=1-7 # Command to be executed ( PauseDownload, UnpauseDownload, PauseScan, UnpauseScan, # DownloadRate, Process, ActivateServer, DeactivateServer, FetchFeed). # # Possible commands: # PauseDownload - pauses download; # UnpauseDownload - resumes download; # PauseScan - pauses scan of incoming nzb-directory; # UnpauseScan - resumes scan of incoming nzb-directory; # DownloadRate - sets download rate limit; # Process - executes external program; # ActivateServer - activate news-server; # DeactivateServer - deactivate news-server; # FetchFeed - fetch RSS feed. # # On start the program checks all tasks and determines current state # for download-pause, scan-pause, download-rate and active servers. #Task1.Command=PauseDownload # Parameters for the command if needed. # # Some scheduler commands require additional parameters: # DownloadRate - download rate limit to be set (kilobytes/sec). # Example: 1000; # Process - path to the program to execute and its parameters. # Example: /home/user/fetch.sh. # If filename or any parameter contains spaces it # must be surrounded with single quotation # marks. If filename/parameter contains single quotation marks, # each of them must be replaced with two single quotation # marks and the resulting filename/parameter must be # surrounded with single quotation marks. # Example: '/home/user/download/my scripts/task process.sh' 'world''s fun'. # In this example one parameter (world's fun) is passed # to the script (task process.sh). # ActivateServer - comma separated list of news server ids or server names. # Example: 1,3. # Example: my news server 1, my news server 2. # NOTE: server names should not have commas. # DeactivateServer - see ActivateServer. # FetchFeed - comma separated list of RSS feed ids or feed names. # Example: 1,3. # Example: bookmarks feed, another feed. # NOTE: feed names should not have commas. # NOTE: use feed id "0" to fetch all feeds. #Task1.Param= #Task2.Time=20:00 #Task2.WeekDays=1-7 #Task2.Command=UnpauseDownload #Task2.Param= ############################################################################## ### PAR CHECK/REPAIR ### # Whether and how par-verification must be performed (auto, force, manual). # # Auto - par-check is performed when needed. One par2-file is always # downloaded. Additional par2-files are downloaded if needed # for repair. Repair is performed if the option # is enabled; # Force - force par-check for every download (even undamaged). All # par2-files are always downloaded. Repair is performed if # the option is enabled; # Manual - par-check is skipped. One par2-file is always # downloaded. If a damaged download is detected, all # par2-files are downloaded but neithet par-check nor par-repair # take place. The download can be then repaired manually # (possibly on another, faster computer). ParCheck=auto # Automatic par-repair after par-verification (yes, no). # # If option is set to "Auto" or "Force" this option defines # if the download must be repaired when needed. The option can be # disabled if computer does not have enough CPU power, since repairing # may take too much resources and time on a slow computers. ParRepair=yes # What files should be scanned during par-verification (auto, limited, # full). # # Limited - scan only files belonging to the par-set; # Full - scan all files in the directory. This helps if the # files were renamed after creating of par-set; # Auto - a limited scan is performed first. If the par-checker # detects missing files, it scans other files in the # directory until all required files are found. # # NOTE: for par-check/repair NZBGet uses library libpar2. The widely # used version 0.2 of the library has few bugs, sometimes causing # a crash of the program. This is especially true when using "full" or # "auto" par-scan. NZBGet is supplied with patches addressing these # issues. Please apply the patches to libpar2 and recompile it. ParScan=auto # Check for renamed files (yes, no). # # Par-rename restores original file names using information stored # in par2-files. When enabled the par-rename is performed as a first # step of post-processing for every nzb-file having par2-files. # # Par-rename is very fast and is highly recommended, especially if # unpack is disabled. ParRename=yes # What to do if download health goes down below critical health (delete, # pause, none). # # Delete - delete nzb-file from queue. If option # is active the already downloaded files will be deleted too; # Pause - pause nzb-file; # None - do nothing (continue download). # # NOTE: for automatic duplicate handling option must be set to "Delete" # or "None". If it is set to "Pause" you will need to manually unpause # another duplicate (if any exists in queue). See also option . HealthCheck=delete # Maximum allowed time for par-repair (minutes). # # If you use NZBGet on a very slow computer like NAS-device, it may be good to # limit the time allowed for par-repair. NZBGet calculates the estimated time # required for par-repair. If the estimated value exceeds the limit defined # here, NZBGet cancels the repair. # # To avoid a false cancellation NZBGet compares the estimated time with # after the first 5 minutes of repairing, when the calculated # estimated time is more or less accurate. But in a case if is # set to a value smaller than 5 minutes, the comparison is made after the first # whole minute. # # Value "0" means unlimited. # # NOTE: The option limits only the time required for repairing. It doesn't # affect the first stage of parcheck - verification of files. However the # verification speed is constant, it doesn't depend on files integrity and # therefore it is not necessary to limit the time needed for the first stage. # # NOTE: This option requires an extended version of libpar2 (the original # version doesn't support the cancelling of repairing). Please refer to # NZBGet's README for info on how to apply the patch to libpar2. ParTimeLimit=0 # Pause download queue during check/repair (yes, no). # # Enable the option to give CPU more time for par-check/repair. That helps # to speed up check/repair on slow CPUs with fast connection (e.g. NAS-devices). # # NOTE: If parchecker needs additional par-files it temporarily unpauses # the queue. # # NOTE: See also options and . ParPauseQueue=no # Cleanup download queue after successful check/repair (yes, no). # # Enable this option for automatic deletion of unneeded (paused) par-files # from download queue after successful check/repair. ParCleanupQueue=yes # Files to delete after successful check/repair. # # List of file extensions or file names to delete after successful # check/repair. The entries must be separated with commas. The entries # can be file extensions or any text the file name may end with. # # Example: .par2, .sfv ExtCleanupDisk=.par2, .sfv, _brokenlog.txt ############################################################################## ### UNPACK ### # Unpack downloaded nzb-files (yes, no). # # Each download (nzb-file) has a post-processing parameter "Unpack". The option # is the default value assigned to this pp-parameter of the download # when it is added to queue. # # When nzb-file is added to queue it can have a category assigned to it. In this # case the option overrides the global option . # # If the download is damaged and could not be repaired using par-files # the unpacking is not performed. # # If the option is set to "Auto" the program tries to unpack # downloaded files first. If the unpacking fails the par-check/repair # is performed and the unpack is executed again. Unpack=yes # Pause download queue during unpack (yes, no). # # Enable the option to give CPU more time for unpacking. That helps # to speed up unpacking on slow CPUs. # # NOTE: See also options and . UnpackPauseQueue=no # Delete archive files after successful unpacking (yes, no). UnpackCleanupDisk=yes # Full path to unrar executable. # # Example: /usr/bin/unrar. # # If unrar is in your PATH you may leave the path part and set only # the executable name ("unrar" on POSIX or "unrar.exe" on Windows). UnrarCmd=unrar # Full path to 7-Zip executable. # # Example: /usr/bin/7z. # # If 7-Zip binary is in your PATH you may leave the path part and set only # the executable name ("7z" or "7za" on POSIX or "7z.exe" on Windows). SevenZipCmd=7z ############################################################################## ### POST-PROCESSING SCRIPTS ### # Default list of post-processing scripts to execute after the download # of nzb-file is completed and possibly par-checked/repaired and unpacked, # depending on other options. # # The scripts in the list must be separated with commas or semicolons. Only # filenames without path must be used. All scripts must be stored in directory # pointed by option . # # Example: Cleanup.sh, Move.sh, EMail.py. # # Each download (nzb-file) has its own list of post-processing scripts. The option # is the default value assigned to download when it is added to # queue. The list of post-processing scripts for a particular download can be # changed in the edit dialog in web-interface or using remote command "--edit/-E". # # When nzb-file is added to queue it can have a category assigned to it. In this # case the option (if not empty) overrides the # global option . # # NOTE: The script execution order is controlled by option , not # by their order in option . # # NOTE: Changing options and doesn't affect # already queued downloads. # # NOTE: For the list of interesting post-processing scripts see # http://nzbget.sourceforge.net/Catalog_of_post-processing_scripts. # # INFO FOR DEVELOPERS: # NOTE: This is a short documentation, for more information visit # http://nzbget.sourceforge.net/Post-processing_scripts. # # NZBGet passes following arguments to post-processing script as environment # variables: # NZBPP_DIRECTORY - path to destination dir for downloaded files; # NZBPP_NZBNAME - user-friendly name of processed nzb-file as it is displayed # by the program. The file path and extension are removed. # If download was renamed, this parameter reflects the new name; # NZBPP_NZBFILENAME - name of processed nzb-file. It includes file extension and also # may include full path; # NZBPP_FINALDIR - final destination path if set by one of previous pp-scripts; # NZBPP_CATEGORY - category assigned to nzb-file (can be empty string); # NZBPP_HEALTH - download health: an integer value in the range # from 0 (all articles failed) to 1000 (all articles # successfully downloaded); # NZBPP_CRITICALHEALTH - critical health for this nzb-file: an integer # value in the range 0-1000. The critical health # is calculated based on number and size of # par-files. If nzb-file doesn't have any par-files # the critical health is 1000 (100.0%). If a half # of nzb-file were par-files its critical health # would be 0. If NZBPP_HEALTH goes down below # NZBPP_CRITICALHEALTH the download becomes unrepairable; # NZBPP_PARSTATUS - result of par-check: # 0 = not checked: par-check is disabled or nzb-file does # not contain any par-files; # 1 = checked and failed to repair; # 2 = checked and successfully repaired; # 3 = checked and can be repaired but repair is disabled; # 4 = par-check needed but skipped (option ParCheck=manual); # NZBPP_UNPACKSTATUS - result of unpack: # 0 = unpack is disabled or was skipped due to nzb-file # parameters or due to errors during par-check; # 1 = unpack failed; # 2 = unpack successful; # 3 = write error (usually not enough disk space); # 4 = wrong password (only for rar5 archives); # NZBPP_HEALTHDELETED - indicates if nzb-file was deleted by health # check (1); # NZBPP_TOTALARTICLES - number of articles in nzb-file; # NZBPP_SUCCESSARTICLES - number of successfully downloaded articles; # NZBPP_FAILEDARTICLES - number of failed articles; # NZBPP_SERVERX_SUCCESSARTICLES - number of successfully downloaded # articles from ServerX (X is replaced with server # number, for example NZBPP_SERVER1_SUCCESSARTICLES); # NZBPP_SERVERX_FAILEDARTICLES - number of failed articles from ServerX. # # If the script defines own options they are also passed as environment # variables. These variables have prefix "NZBPO_" in their names. For # example, option "myoption" will be passed as environment variable # "NZBPO_myoption" and in addition in uppercase as "NZBPO_MYOPTION". # # If the script defines own post-processing parameters, they are also passed as # environment variables. These variables have prefix "NZBPR_" in their # names. For example, pp-parameter "myparam" will be passed as environment # variable "NZBPR_myparam" and in addition in uppercase as "NZBPR_MYPARAM". # # In addition to arguments, pp-options and pp-parameters NZBGet passes all # nzbget.conf-options to pp-script as environment variables. These # variables have prefix "NZBOP_" and are written in UPPER CASE. For Example # option "ParRepair" is passed as environment variable "NZBOP_PARREPAIR". The # dots in option names are replaced with underscores, for example # "SERVER1_HOST". For options with predefined possible values (yes/no, etc.) # the values are passed always in lower case. # # If the script moves files it can inform the program about new location # by printing special message into standard output (which is processed # by NZBGet): # echo "[NZB] FINALDIR=/path/to/moved/files"; # # To assign post-processing parameters: # echo "[NZB] NZBPR_myvar=my value"; # # The prefix "NZBPR_" will be removed. In this example a post-processing # parameter with name "myvar" and value "my value" will be associated # with nzb-file. # # Return value: NZBGet processes the exit code returned by the script: # 93 - post-process successful (status = SUCCESS); # 94 - post-process failed (status = FAILURE); # 95 - post-process skipped (status = NONE). Use this code when you script # terminates immediateley without doing any job and when this is not # a failure termination; # 92 - request NZBGet to do par-check/repair for current nzb-file. # # All other return codes are interpreted as failure (status = FAILURE). # # NOTE: This is a short documentation, for more information visit # http://nzbget.sourceforge.net/Post-processing_scripts. DefScript= # Execution order for scripts. # # If you assign multiple scripts to one nzb-file, they are executed in the # order defined by this option. Scripts not listed here are executed at # the end in their alphabetical order. # # The scripts in the list must be separated with commas or semicolons. Only # filenames without path must be used. All scripts must be stored in directory # pointed by option . # # Example: Cleanup.sh, Move.sh. ScriptOrder= # Pause download queue during executing of postprocess-script (yes, no). # # Enable the option to give CPU more time for postprocess-script. That helps # to speed up postprocess on slow CPUs with fast connection (e.g. NAS-devices). # # NOTE: See also options and . ScriptPauseQueue=no nzbget-12.0+dfsg/nzbget.cpp000066400000000000000000000466221226450633000156540ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 911 $ * $Date: 2013-11-24 20:29:52 +0100 (Sun, 24 Nov 2013) $ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #ifdef WIN32 #include #else #include #include #include #include #ifdef HAVE_SYS_PRCTL_H #include #endif #include #endif #include #include #include #include #ifndef DISABLE_PARCHECK #include #endif #ifdef HAVE_BACKTRACE #include #endif #include "nzbget.h" #include "ServerPool.h" #include "Log.h" #include "NZBFile.h" #include "Options.h" #include "Thread.h" #include "ColoredFrontend.h" #include "NCursesFrontend.h" #include "QueueCoordinator.h" #include "UrlCoordinator.h" #include "RemoteServer.h" #include "RemoteClient.h" #include "MessageBase.h" #include "DiskState.h" #include "PrePostProcessor.h" #include "ParChecker.h" #include "Scheduler.h" #include "Scanner.h" #include "FeedCoordinator.h" #include "Maintenance.h" #include "Util.h" #ifdef WIN32 #include "NTService.h" #endif // Prototypes void RunMain(); void Run(bool bReload); void Reload(); void Cleanup(); void ProcessClientRequest(); #ifndef WIN32 void InstallSignalHandlers(); void Daemonize(); void PrintBacktrace(); #ifdef HAVE_SYS_PRCTL_H void EnableDumpCore(); #endif #ifdef DEBUG void MakeSegFault(); #endif #endif #ifndef DISABLE_PARCHECK void DisableCout(); #endif Thread* g_pFrontend = NULL; Options* g_pOptions = NULL; ServerPool* g_pServerPool = NULL; QueueCoordinator* g_pQueueCoordinator = NULL; UrlCoordinator* g_pUrlCoordinator = NULL; RemoteServer* g_pRemoteServer = NULL; RemoteServer* g_pRemoteSecureServer = NULL; DownloadSpeedMeter* g_pDownloadSpeedMeter = NULL; DownloadQueueHolder* g_pDownloadQueueHolder = NULL; Log* g_pLog = NULL; PrePostProcessor* g_pPrePostProcessor = NULL; DiskState* g_pDiskState = NULL; Scheduler* g_pScheduler = NULL; Scanner* g_pScanner = NULL; FeedCoordinator* g_pFeedCoordinator = NULL; Maintenance* g_pMaintenance = NULL; int g_iArgumentCount; char* (*g_szEnvironmentVariables)[] = NULL; char* (*g_szArguments)[] = NULL; bool g_bReloading = true; /* * Main loop */ int main(int argc, char *argv[], char *argp[]) { #ifdef WIN32 #ifdef _DEBUG _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF #ifdef DEBUG_CRTMEMLEAKS | _CRTDBG_CHECK_CRT_DF | _CRTDBG_CHECK_ALWAYS_DF #endif ); #endif #endif Util::InitVersionRevision(); #ifdef WIN32 InstallUninstallServiceCheck(argc, argv); #endif #ifndef DISABLE_PARCHECK DisableCout(); #endif srand (time(NULL)); g_iArgumentCount = argc; g_szArguments = (char*(*)[])argv; g_szEnvironmentVariables = (char*(*)[])argp; #ifdef WIN32 for (int i=0; i < argc; i++) { if (!strcmp(argv[i], "-D")) { StartService(RunMain); return 0; } } #endif RunMain(); #ifdef WIN32 #ifdef _DEBUG _CrtDumpMemoryLeaks(); #endif #endif return 0; } void RunMain() { // we need to save and later restore current directory each time // the program is reloaded (RPC-Method "reload") in order for // config to properly load in a case relative paths are used // in command line char szCurDir[MAX_PATH + 1]; Util::GetCurrentDirectory(szCurDir, sizeof(szCurDir)); bool bReload = false; while (g_bReloading) { g_bReloading = false; Util::SetCurrentDirectory(szCurDir); Run(bReload); bReload = true; } } void Run(bool bReload) { g_pLog = new Log(); debug("nzbget %s", Util::VersionRevision()); if (!bReload) { Thread::Init(); } g_pServerPool = new ServerPool(); g_pScheduler = new Scheduler(); g_pQueueCoordinator = new QueueCoordinator(); g_pDownloadSpeedMeter = g_pQueueCoordinator; g_pDownloadQueueHolder = g_pQueueCoordinator; g_pUrlCoordinator = new UrlCoordinator(); g_pFeedCoordinator = new FeedCoordinator(); g_pMaintenance = new Maintenance(); debug("Reading options"); g_pOptions = new Options(g_iArgumentCount, *g_szArguments); #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"); g_pLog->ResetLog(); } g_pLog->InitOptions(); if (g_pOptions->GetDaemonMode()) { #ifdef WIN32 info("nzbget %s service-mode", Util::VersionRevision()); #else if (!bReload) { Daemonize(); } info("nzbget %s daemon-mode", Util::VersionRevision()); #endif } else if (g_pOptions->GetServerMode()) { info("nzbget %s server-mode", Util::VersionRevision()); } else if (g_pOptions->GetRemoteClientMode()) { info("nzbget %s remote-mode", Util::VersionRevision()); } if (!bReload) { Connection::Init(); } if (!g_pOptions->GetRemoteClientMode()) { g_pServerPool->InitConnections(); } #ifndef WIN32 #ifdef HAVE_SYS_PRCTL_H if (g_pOptions->GetDumpCore()) { EnableDumpCore(); } #endif #endif #ifndef WIN32 InstallSignalHandlers(); #ifdef DEBUG if (g_pOptions->GetTestBacktrace()) { MakeSegFault(); } #endif #endif // client request if (g_pOptions->GetClientOperation() != Options::opClientNoOperation) { ProcessClientRequest(); Cleanup(); return; } // Setup the network-server if (g_pOptions->GetServerMode()) { g_pRemoteServer = new RemoteServer(false); g_pRemoteServer->Start(); if (g_pOptions->GetSecureControl()) { g_pRemoteSecureServer = new RemoteServer(true); g_pRemoteSecureServer->Start(); } } // Creating PrePostProcessor if (!g_pOptions->GetRemoteClientMode()) { g_pScanner = new Scanner(); g_pPrePostProcessor = new PrePostProcessor(); } // Create the frontend if (!g_pOptions->GetDaemonMode()) { switch (g_pOptions->GetOutputMode()) { case Options::omNCurses: #ifndef DISABLE_CURSES g_pFrontend = new NCursesFrontend(); break; #endif case Options::omColored: g_pFrontend = new ColoredFrontend(); break; case Options::omLoggable: g_pFrontend = new LoggableFrontend(); break; } } // Starting a thread with the frontend if (g_pFrontend) { g_pFrontend->Start(); } // Starting QueueCoordinator and PrePostProcessor if (!g_pOptions->GetRemoteClientMode()) { // Standalone-mode if (!g_pOptions->GetServerMode()) { NZBFile* pNZBFile = NZBFile::Create(g_pOptions->GetArgFilename(), g_pOptions->GetAddCategory() ? g_pOptions->GetAddCategory() : ""); if (!pNZBFile) { abort("FATAL ERROR: Parsing NZB-document %s failed\n\n", g_pOptions->GetArgFilename() ? g_pOptions->GetArgFilename() : "N/A"); return; } g_pQueueCoordinator->AddNZBFileToQueue(pNZBFile, false); delete pNZBFile; } if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState = new DiskState(); } g_pQueueCoordinator->Start(); g_pUrlCoordinator->Start(); g_pPrePostProcessor->Start(); g_pFeedCoordinator->Start(); // enter main program-loop while (g_pQueueCoordinator->IsRunning() || g_pUrlCoordinator->IsRunning() || g_pPrePostProcessor->IsRunning() || g_pFeedCoordinator->IsRunning()) { if (!g_pOptions->GetServerMode() && !g_pQueueCoordinator->HasMoreJobs() && !g_pUrlCoordinator->HasMoreJobs() && !g_pPrePostProcessor->HasMoreJobs()) { // Standalone-mode: download completed if (!g_pQueueCoordinator->IsStopped()) { g_pQueueCoordinator->Stop(); } if (!g_pUrlCoordinator->IsStopped()) { g_pUrlCoordinator->Stop(); } if (!g_pPrePostProcessor->IsStopped()) { g_pPrePostProcessor->Stop(); } if (!g_pFeedCoordinator->IsStopped()) { g_pFeedCoordinator->Stop(); } } usleep(100 * 1000); } // main program-loop is terminated debug("QueueCoordinator stopped"); debug("UrlCoordinator stopped"); debug("PrePostProcessor stopped"); debug("FeedCoordinator stopped"); } // Stop network-server if (g_pRemoteServer) { debug("stopping RemoteServer"); g_pRemoteServer->Stop(); int iMaxWaitMSec = 1000; while (g_pRemoteServer->IsRunning() && iMaxWaitMSec > 0) { usleep(100 * 1000); iMaxWaitMSec -= 100; } if (g_pRemoteServer->IsRunning()) { debug("Killing RemoteServer"); g_pRemoteServer->Kill(); } debug("RemoteServer stopped"); } if (g_pRemoteSecureServer) { debug("stopping RemoteSecureServer"); g_pRemoteSecureServer->Stop(); int iMaxWaitMSec = 1000; while (g_pRemoteSecureServer->IsRunning() && iMaxWaitMSec > 0) { usleep(100 * 1000); iMaxWaitMSec -= 100; } if (g_pRemoteSecureServer->IsRunning()) { debug("Killing RemoteSecureServer"); g_pRemoteSecureServer->Kill(); } debug("RemoteSecureServer stopped"); } // Stop Frontend if (g_pFrontend) { if (!g_pOptions->GetRemoteClientMode()) { debug("Stopping Frontend"); g_pFrontend->Stop(); } while (g_pFrontend->IsRunning()) { usleep(50 * 1000); } debug("Frontend stopped"); } Cleanup(); } void ProcessClientRequest() { RemoteClient* Client = new RemoteClient(); switch (g_pOptions->GetClientOperation()) { case Options::opClientRequestListFiles: Client->RequestServerList(true, false, g_pOptions->GetMatchMode() == Options::mmRegEx ? g_pOptions->GetEditQueueText() : NULL); break; case Options::opClientRequestListGroups: Client->RequestServerList(false, true, g_pOptions->GetMatchMode() == Options::mmRegEx ? g_pOptions->GetEditQueueText() : NULL); break; case Options::opClientRequestListStatus: Client->RequestServerList(false, false, NULL); break; case Options::opClientRequestDownloadPause: Client->RequestServerPauseUnpause(true, eRemotePauseUnpauseActionDownload); break; case Options::opClientRequestDownloadUnpause: Client->RequestServerPauseUnpause(false, eRemotePauseUnpauseActionDownload); break; case Options::opClientRequestDownload2Pause: Client->RequestServerPauseUnpause(true, eRemotePauseUnpauseActionDownload2); break; case Options::opClientRequestDownload2Unpause: Client->RequestServerPauseUnpause(false, eRemotePauseUnpauseActionDownload2); break; case Options::opClientRequestSetRate: Client->RequestServerSetDownloadRate(g_pOptions->GetSetRate()); break; case Options::opClientRequestDumpDebug: Client->RequestServerDumpDebug(); break; case Options::opClientRequestEditQueue: Client->RequestServerEditQueue((eRemoteEditAction)g_pOptions->GetEditQueueAction(), g_pOptions->GetEditQueueOffset(), g_pOptions->GetEditQueueText(), g_pOptions->GetEditQueueIDList(), g_pOptions->GetEditQueueIDCount(), g_pOptions->GetEditQueueNameList(), (eRemoteMatchMode)g_pOptions->GetMatchMode(), true); break; case Options::opClientRequestLog: Client->RequestServerLog(g_pOptions->GetLogLines()); break; case Options::opClientRequestShutdown: Client->RequestServerShutdown(); break; case Options::opClientRequestReload: Client->RequestServerReload(); break; case Options::opClientRequestDownload: Client->RequestServerDownload(g_pOptions->GetArgFilename(), g_pOptions->GetAddCategory(), g_pOptions->GetAddTop(), g_pOptions->GetAddPaused(), g_pOptions->GetAddPriority()); break; case Options::opClientRequestVersion: Client->RequestServerVersion(); break; case Options::opClientRequestPostQueue: Client->RequestPostQueue(); break; case Options::opClientRequestWriteLog: Client->RequestWriteLog(g_pOptions->GetWriteLogKind(), g_pOptions->GetLastArg()); break; case Options::opClientRequestScanAsync: Client->RequestScan(false); break; case Options::opClientRequestScanSync: Client->RequestScan(true); break; case Options::opClientRequestPostPause: Client->RequestServerPauseUnpause(true, eRemotePauseUnpauseActionPostProcess); break; case Options::opClientRequestPostUnpause: Client->RequestServerPauseUnpause(false, eRemotePauseUnpauseActionPostProcess); break; case Options::opClientRequestScanPause: Client->RequestServerPauseUnpause(true, eRemotePauseUnpauseActionScan); break; case Options::opClientRequestScanUnpause: Client->RequestServerPauseUnpause(false, eRemotePauseUnpauseActionScan); break; case Options::opClientRequestHistory: Client->RequestHistory(); break; case Options::opClientRequestDownloadUrl: Client->RequestServerDownloadUrl(g_pOptions->GetLastArg(), g_pOptions->GetAddNZBFilename(), g_pOptions->GetAddCategory(), g_pOptions->GetAddTop(), g_pOptions->GetAddPaused(), g_pOptions->GetAddPriority()); break; case Options::opClientRequestUrlQueue: Client->RequestUrlQueue(); break; case Options::opClientNoOperation: break; } delete Client; } void ExitProc() { if (!g_bReloading) { info("Stopping, please wait..."); } if (g_pOptions->GetRemoteClientMode()) { if (g_pFrontend) { debug("Stopping Frontend"); g_pFrontend->Stop(); } } else { if (g_pQueueCoordinator) { debug("Stopping QueueCoordinator"); g_pQueueCoordinator->Stop(); g_pUrlCoordinator->Stop(); g_pPrePostProcessor->Stop(); g_pFeedCoordinator->Stop(); } } } void Reload() { g_bReloading = true; info("Reloading..."); ExitProc(); } #ifndef WIN32 #ifdef DEBUG typedef void(*sighandler)(int); std::vector SignalProcList; #endif /* * Signal handler */ void SignalProc(int iSignal) { switch (iSignal) { case SIGINT: signal(SIGINT, SIG_DFL); // Reset the signal handler ExitProc(); break; case SIGTERM: signal(SIGTERM, SIG_DFL); // Reset the signal handler ExitProc(); break; case SIGCHLD: // ignoring break; #ifdef DEBUG case SIGSEGV: signal(SIGSEGV, SIG_DFL); // Reset the signal handler PrintBacktrace(); break; #endif } } void InstallSignalHandlers() { signal(SIGINT, SignalProc); signal(SIGTERM, SignalProc); signal(SIGPIPE, SIG_IGN); #ifdef DEBUG signal(SIGSEGV, SignalProc); #endif #ifdef SIGCHLD_HANDLER // it could be necessary on some systems to activate a handler for SIGCHLD // however it make troubles on other systems and is deactivated by default signal(SIGCHLD, SignalProc); #endif } void PrintBacktrace() { #ifdef HAVE_BACKTRACE printf("Segmentation fault, tracing...\n"); void *array[100]; size_t size; char **strings; size_t i; size = backtrace(array, 100); strings = backtrace_symbols(array, size); // first trace to screen printf("Obtained %zd stack frames\n", size); for (i = 0; i < size; i++) { printf("%s\n", strings[i]); } // then trace to log error("Segmentation fault, tracing..."); error("Obtained %zd stack frames", size); for (i = 0; i < size; i++) { error("%s", strings[i]); } free(strings); #else error("Segmentation fault"); #endif } #ifdef DEBUG void MakeSegFault() { char* N = NULL; strcpy(N, ""); } #endif #ifdef HAVE_SYS_PRCTL_H /** * activates the creation of core-files */ void EnableDumpCore() { rlimit rlim; rlim.rlim_cur= RLIM_INFINITY; rlim.rlim_max= RLIM_INFINITY; setrlimit(RLIMIT_CORE, &rlim); prctl(PR_SET_DUMPABLE, 1); } #endif #endif void Cleanup() { debug("Cleaning up global objects"); debug("Deleting UrlCoordinator"); delete g_pUrlCoordinator; g_pUrlCoordinator = NULL; debug("UrlCoordinator deleted"); debug("Deleting RemoteServer"); delete g_pRemoteServer; g_pRemoteServer = NULL; debug("RemoteServer deleted"); debug("Deleting RemoteSecureServer"); delete g_pRemoteSecureServer; g_pRemoteSecureServer = NULL; debug("RemoteSecureServer deleted"); debug("Deleting PrePostProcessor"); delete g_pPrePostProcessor; g_pPrePostProcessor = NULL; delete g_pScanner; g_pScanner = NULL; debug("PrePostProcessor deleted"); debug("Deleting Frontend"); delete g_pFrontend; g_pFrontend = NULL; debug("Frontend deleted"); debug("Deleting QueueCoordinator"); delete g_pQueueCoordinator; g_pQueueCoordinator = NULL; debug("QueueCoordinator deleted"); debug("Deleting DiskState"); delete g_pDiskState; g_pDiskState = NULL; debug("DiskState deleted"); debug("Deleting Options"); if (g_pOptions) { if (g_pOptions->GetDaemonMode() && !g_bReloading) { info("Deleting lock file"); remove(g_pOptions->GetLockFile()); } delete g_pOptions; g_pOptions = NULL; } debug("Options deleted"); debug("Deleting ServerPool"); delete g_pServerPool; g_pServerPool = NULL; debug("ServerPool deleted"); debug("Deleting Scheduler"); delete g_pScheduler; g_pScheduler = NULL; debug("Scheduler deleted"); debug("Deleting FeedCoordinator"); delete g_pFeedCoordinator; g_pFeedCoordinator = NULL; debug("FeedCoordinator deleted"); debug("Deleting Maintenance"); delete g_pMaintenance; g_pMaintenance = NULL; debug("Maintenance deleted"); if (!g_bReloading) { Connection::Final(); Thread::Final(); } debug("Global objects cleaned up"); delete g_pLog; g_pLog = NULL; } #ifndef WIN32 void Daemonize() { int i, lfp; char str[10]; if (getppid() == 1) return; /* already a daemon */ i = fork(); if (i < 0) exit(1); /* fork error */ if (i > 0) exit(0); /* parent exits */ /* child (daemon) continues */ 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 */ 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) { fchown(lfp, pw->pw_uid, pw->pw_gid); /* change owner of lock file */ 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 */ signal(SIGCHLD, SIG_IGN); /* ignore child */ signal(SIGTSTP, SIG_IGN); /* ignore tty signals */ signal(SIGTTOU, SIG_IGN); signal(SIGTTIN, SIG_IGN); } #endif #ifndef DISABLE_PARCHECK class NullStreamBuf : public std::streambuf { public: int sputc ( char c ) { return (int) c; } } NullStreamBufInstance; void DisableCout() { // libpar2 prints messages to c++ standard output stream (std::cout). // However we do not want these messages to be printed. // Since we do not use std::cout in nzbget we just disable it. std::cout.rdbuf(&NullStreamBufInstance); } #endif nzbget-12.0+dfsg/nzbget.h000066400000000000000000000050471226450633000153150ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 722 $ * $Date: 2013-07-14 00:00:49 +0200 (Sun, 14 Jul 2013) $ * */ #ifndef NZBGET_H #define NZBGET_H #ifdef WIN32 // WIN32 #define snprintf _snprintf #ifndef strdup #define strdup _strdup #endif #define fdopen _fdopen #define ctime_r(timep, buf, bufsize) ctime_s(buf, bufsize, timep) #define localtime_r(time, tm) localtime_s(tm, time) #define strtok_r(str, delim, saveptr) strtok_s(str, delim, saveptr) #define strerror_r(errnum, buffer, size) strerror_s(buffer, size, errnum) #define int32_t __int32 #define mkdir(dir, flags) _mkdir(dir) #define rmdir _rmdir #define strcasecmp(a, b) _stricmp(a, b) #define strncasecmp(a, b, c) _strnicmp(a, b, c) #define ssize_t SSIZE_T #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) #define socklen_t int #define SHUT_WR 0x01 #define SHUT_RDWR 0x02 #define PATH_SEPARATOR '\\' #define ALT_PATH_SEPARATOR '/' #define LINE_ENDING "\r\n" #define pid_t int #define atoll _atoi64 #ifndef FSCTL_SET_SPARSE #define FSCTL_SET_SPARSE 590020 #endif #pragma warning(disable:4800) // 'type' : forcing value to bool 'true' or 'false' (performance warning) #pragma warning(disable:4267) // 'var' : conversion from 'size_t' to 'type', possible loss of data #else // POSIX #define closesocket(sock) close(sock) #define SOCKET int #define INVALID_SOCKET (-1) #define PATH_SEPARATOR '/' #define ALT_PATH_SEPARATOR '\\' #define MAX_PATH 1024 #define S_DIRMODE (S_IRWXU | S_IRWXG | S_IRWXO) #define LINE_ENDING "\n" #endif #ifndef SHUT_RDWR #define SHUT_RDWR 2 #endif #endif nzbget-12.0+dfsg/nzbget.sln000066400000000000000000000021521226450633000156540ustar00rootroot00000000000000 Microsoft Visual Studio Solution File, Format Version 9.00 # Visual C++ Express 2005 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nzbget", "nzbget.vcproj", "{41BFB691-0127-4391-9629-F1BA6740DDFE}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 Release (no TLS)|Win32 = Release (no TLS)|Win32 Release|Win32 = Release|Win32 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {41BFB691-0127-4391-9629-F1BA6740DDFE}.Debug|Win32.ActiveCfg = Debug|Win32 {41BFB691-0127-4391-9629-F1BA6740DDFE}.Debug|Win32.Build.0 = Debug|Win32 {41BFB691-0127-4391-9629-F1BA6740DDFE}.Release (no TLS)|Win32.ActiveCfg = Release (no TLS)|Win32 {41BFB691-0127-4391-9629-F1BA6740DDFE}.Release (no TLS)|Win32.Build.0 = Release (no TLS)|Win32 {41BFB691-0127-4391-9629-F1BA6740DDFE}.Release|Win32.ActiveCfg = Release|Win32 {41BFB691-0127-4391-9629-F1BA6740DDFE}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal nzbget-12.0+dfsg/nzbget.vcproj000066400000000000000000000250261226450633000163700ustar00rootroot00000000000000 nzbget-12.0+dfsg/nzbgetd000077500000000000000000000040751226450633000152360ustar00rootroot00000000000000#!/bin/sh # # Script used to start and stop the nzbget usenet service # # Copyright (C) 2009 orbisvicis # Copyright (C) 2009-2012 Andrey Prygunkov # # 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # # --- CONFIGURATION ----------------------------------------------- # Location of the nzbget executable NZBGET_BINARY="/usr/local/bin/nzbget" # Additional options, e. g. config file location: # NZBGET_OPTS="-c /mnt/hdd/tools/nzbget/conf/nzbget.conf" NZBGET_OPTS="" # ----------------------------------------------------------------- if [ -z "$1" ] ; then case `echo "$0" | sed 's:^.*/\(.*\):\1:g'` in S??*) rc="start" ;; K??*) rc="stop" ;; *) rc="usage" ;; esac else rc="$1" fi case "$rc" in start) "$NZBGET_BINARY" $NZBGET_OPTS -D ;; stop) "$NZBGET_BINARY" $NZBGET_OPTS -Q ;; restart) "$NZBGET_BINARY" $NZBGET_OPTS -Q sleep 10 # since stop is backgrounded "$NZBGET_BINARY" $NZBGET_OPTS -D ;; status) "$NZBGET_BINARY" $NZBGET_OPTS -L S ;; pstatus) retval=$(pgrep -l -f nzbget > /dev/null ; echo $?) if [ "$retval" = "0" ] ; then echo " ------- nzbget *is* running -------" ps -Ho user,pid,cmd:32,pcpu -C nzbget exit 0 else echo " ------- nzbget is *not* running -------" exit 0 fi ;; *) echo "Usage: $0 {start|stop|restart|status|pstatus|usage}" exit 1 esac exit 0 nzbget-12.0+dfsg/osx/000077500000000000000000000000001226450633000144565ustar00rootroot00000000000000nzbget-12.0+dfsg/osx/App_Prefix.pch000066400000000000000000000025371226450633000172160ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 807 $ * $Date: 2013-08-31 23:14:39 +0200 (Sat, 31 Aug 2013) $ * */ #ifdef __OBJC__ #import #endif #ifdef DEBUG # define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__); #else # define DLog(...) do {} while (0) #endif #define SuppressPerformSelectorLeakWarning(Stuff) \ do { \ _Pragma("clang diagnostic push") \ _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \ Stuff; \ _Pragma("clang diagnostic pop") \ } while (0) nzbget-12.0+dfsg/osx/DaemonController.h000066400000000000000000000041101226450633000200720ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 807 $ * $Date: 2013-08-31 23:14:39 +0200 (Sat, 31 Aug 2013) $ * */ #import @protocol DaemonControllerDelegate - (void)daemonConfigLoaded; - (void)daemonStatusUpdated; @end @interface DaemonController : NSObject { NSString* lockFilePath; NSDictionary* config; int restartCounter; int restartPid; NSTimer* updateTimer; int lastUptime; BOOL factoryReset; } @property (nonatomic, assign) NSTimeInterval updateInterval; @property (nonatomic, readonly) BOOL connected; @property (nonatomic, readonly) BOOL restarting; @property (nonatomic, readonly) BOOL recoveryMode; @property (nonatomic, readonly) NSString* configFilePath; @property (nonatomic, readonly) NSString* browserUrl; @property (nonatomic, readonly) NSDate* lastUpdate; @property (nonatomic, assign) id delegate; @property (nonatomic, readonly) NSDictionary* status; - (id)init; - (NSString*)valueForOption:(NSString*)option; - (void)start; - (void)stop; - (void)restartInRecoveryMode:(BOOL)recovery withFactoryReset:(BOOL)reset; - (NSString *)browserUrl; - (void)updateStatus; - (void)rpc:(NSString*)method success:(SEL)successCallback failure:(SEL)failureCallback; - (void)setUpdateInterval:(NSTimeInterval)updateInterval; @end nzbget-12.0+dfsg/osx/DaemonController.m000066400000000000000000000245771226450633000201220ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 807 $ * $Date: 2013-08-31 23:14:39 +0200 (Sat, 31 Aug 2013) $ * */ #include #import #import "DaemonController.h" #import "RPC.h" NSString* MAIN_DIR = @"${AppSupDir}"; NSString* LOCK_FILE = @"${AppSupDir}/nzbget.lock"; NSString* CONFIG_FILE = @"${AppSupDir}/nzbget.conf"; NSString* PPSCRIPTS_DIR = @"${AppSupDir}/ppscripts"; NSString* NZB_DIR = @"${AppSupDir}/nzb"; NSString* QUEUE_DIR = @"${AppSupDir}/queue"; NSString* TMP_DIR = @"${AppSupDir}/tmp"; @implementation DaemonController - (id)init { self = [super init]; _configFilePath = [self resolveAppSupDir:CONFIG_FILE]; lockFilePath = [self resolveAppSupDir:LOCK_FILE]; return self; } - (NSString *) bundlePath { return [[NSBundle mainBundle] pathForResource:@"daemon" ofType:nil]; } - (NSString *) resolveAppSupDir:(NSString *)dir { NSString *appSupPath = [@"${AppSupDir}/Application Support/NZBGet" stringByExpandingTildeInPath]; NSArray* paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); if (paths.count > 0) { appSupPath = [paths objectAtIndex:0]; appSupPath = [appSupPath stringByAppendingPathComponent:@"NZBGet"]; } dir = [dir stringByReplacingOccurrencesOfString:@"${AppSupDir}" withString:appSupPath options:NSCaseInsensitiveSearch range:NSMakeRange(0, dir.length)]; return dir; } - (void) checkDefaults { NSString* mainDir = [self resolveAppSupDir:MAIN_DIR]; if (![[NSFileManager defaultManager] fileExistsAtPath:mainDir]) { [[NSFileManager defaultManager] createDirectoryAtPath:mainDir withIntermediateDirectories:YES attributes:nil error:nil]; } NSString* bundlePath = [self bundlePath]; if (![[NSFileManager defaultManager] fileExistsAtPath:_configFilePath]) { NSString* configTemplate = [NSString stringWithFormat:@"%@/usr/local/share/nzbget/nzbget.conf", bundlePath]; [[NSFileManager defaultManager] copyItemAtPath:configTemplate toPath:_configFilePath error:nil]; } NSString* ppscriptsDir = [self resolveAppSupDir:PPSCRIPTS_DIR]; if (![[NSFileManager defaultManager] fileExistsAtPath:ppscriptsDir]) { NSString* ppscriptsTemplate = [NSString stringWithFormat:@"%@/usr/local/share/nzbget/ppscripts", bundlePath]; [[NSFileManager defaultManager] copyItemAtPath:ppscriptsTemplate toPath:ppscriptsDir error:nil]; } } - (int)readLockFilePid { if ([[NSFileManager defaultManager] fileExistsAtPath:lockFilePath]) { // Lock file exists // read pid from lock file int pid = [[NSString stringWithContentsOfFile:lockFilePath encoding:NSUTF8StringEncoding error:nil] intValue]; DLog(@"pid: %i", pid); // check if the process name is "nzbget" to avoid killing of other proceses char pathbuf[PROC_PIDPATHINFO_MAXSIZE]; int ret = proc_pidpath(pid, pathbuf, sizeof(pathbuf)); if (ret <= 0) { // error return 0; } DLog(@"proc %d: %s\n", pid, pathbuf); NSString* instancePath = [NSString stringWithUTF8String:pathbuf]; if ([instancePath hasSuffix:@".app/Contents/Resources/daemon/usr/local/bin/nzbget"]) { return pid; } } return 0; } - (void)killDaemonWithSignal:(int)signal { int pid = [self readLockFilePid]; if (pid > 0) { kill(pid, signal); [[NSFileManager defaultManager] removeItemAtPath:lockFilePath error:nil]; } } - (void)start { DLog(@"DaemonController->start"); [self checkDefaults]; [self killDaemonWithSignal:SIGKILL]; [self readConfigFile]; [self initRpcUrl]; [self initBrowserUrl]; NSString* bundlePath = [self bundlePath]; NSString* daemonPath = [NSString stringWithFormat:@"%@/usr/local/bin/nzbget", bundlePath]; NSString* optionWebDir = [NSString stringWithFormat:@"WebDir=%@/usr/local/share/nzbget/webui", bundlePath]; NSString* optionConfigTemplate = [NSString stringWithFormat:@"ConfigTemplate=%@/usr/local/share/nzbget/nzbget.conf", bundlePath]; NSString* optionLockFile = [NSString stringWithFormat:@"LockFile=%@", lockFilePath]; NSMutableArray* arguments = [NSMutableArray arrayWithObjects: @"-c", _configFilePath, @"-D", @"-o", optionWebDir, @"-o", optionConfigTemplate, @"-o", optionLockFile, nil]; if (_recoveryMode) { [arguments addObjectsFromArray: [NSArray arrayWithObjects: @"-o", @"ControlIP=127.0.0.1", @"-o", @"ControlPort=6789", @"-o", @"ControlPassword=", @"-o", @"SecureControl=no", nil ]]; } NSTask* task = [[NSTask alloc] init]; [task setLaunchPath: daemonPath]; [task setArguments: arguments]; [task launch]; _restarting = NO; [self scheduleNextUpdate]; } - (void)stop { DLog(@"DaemonController->stop"); [self killDaemonWithSignal:SIGTERM]; } - (void)restartInRecoveryMode:(BOOL)recovery withFactoryReset:(BOOL)reset { _recoveryMode = recovery; factoryReset = reset; _restarting = YES; restartPid = [self readLockFilePid]; [self stop]; // in timer wait for deletion of lockfile for 10 seconds, // after that call "start" which will kill the old process. restartCounter = 0; [self restartWait]; } - (void)restartWait { DLog(@"DaemonController->restartWait"); restartCounter++; char pathbuf[PROC_PIDPATHINFO_MAXSIZE]; int ret = proc_pidpath(restartPid, pathbuf, sizeof(pathbuf)); if (ret > 0 && restartCounter < 100) { DLog(@"restartWait: scheduleNextRestartWait"); [self scheduleNextRestartWait]; } else { DLog(@"restartWait: start"); if (factoryReset) { [self resetToFactoryDefaults]; } [self start]; } } - (void)resetToFactoryDefaults { DLog(@"DaemonController->resetToFactoryDefaults"); [[NSFileManager defaultManager] removeItemAtPath:_configFilePath error:nil]; [[NSFileManager defaultManager] removeItemAtPath:[self resolveAppSupDir:QUEUE_DIR] error:nil]; [[NSFileManager defaultManager] removeItemAtPath:[self resolveAppSupDir:PPSCRIPTS_DIR] error:nil]; [[NSFileManager defaultManager] removeItemAtPath:[self resolveAppSupDir:NZB_DIR] error:nil]; [[NSFileManager defaultManager] removeItemAtPath:[self resolveAppSupDir:TMP_DIR] error:nil]; } - (void)scheduleNextRestartWait { NSTimer* timer = [NSTimer timerWithTimeInterval:0.100 target:self selector:@selector(restartWait) userInfo:nil repeats:NO]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; } - (void)readConfigFile { DLog(@"DaemonController->readConfigFile"); NSString* str = [NSString stringWithContentsOfFile:_configFilePath encoding:NSUTF8StringEncoding error:nil]; NSArray* conf = [str componentsSeparatedByString: @"\n"]; config = [[NSMutableDictionary alloc] init]; for (NSString* opt in conf) { if ([opt hasPrefix:@"#"]) { continue; } NSRange pos = [opt rangeOfString:@"="]; if (pos.location != NSNotFound) { NSString* name = [opt substringToIndex:pos.location]; name = [name stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]]; NSString* value = [opt substringFromIndex:pos.location + 1]; value = [value stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]]; [config setValue:value forKey:[name uppercaseString]]; } } if (_recoveryMode) { [config setValue:@"localhost" forKey:@"CONTROLIP"]; [config setValue:@"6789" forKey:@"CONTROLPORT"]; [config setValue:@"" forKey:@"CONTROLPASSWORD"]; } [_delegate daemonConfigLoaded]; } - (NSString*)valueForOption:(NSString*)option { return [config valueForKey:[option uppercaseString]]; } - (void)initBrowserUrl { NSString* ip = [self valueForOption:@"ControlIP"]; if ([ip isEqualToString:@"0.0.0.0"] || [ip isEqualToString:@"127.0.0.1"]) { ip = @"localhost"; } NSString* port = [self valueForOption:@"ControlPort"]; _browserUrl = [NSString stringWithFormat:@"http://@%@:%@", ip, port]; } - (void)initRpcUrl { NSString* ip = [self valueForOption:@"ControlIP"]; if ([ip isEqualToString:@"0.0.0.0"]) { ip = @"127.0.0.1"; } NSString* port = [self valueForOption:@"ControlPort"]; NSString* username = [self valueForOption:@"ControlUsername"]; NSString* password = [self valueForOption:@"ControlPassword"]; NSString* RpcUrl = [NSString stringWithFormat:@"http://%@:%@/%@:%@/jsonrpc/", ip, port, username, password]; [RPC setRpcUrl:RpcUrl]; } - (void)setUpdateInterval:(NSTimeInterval)updateInterval { _updateInterval = updateInterval; if (_connected) { [updateTimer invalidate]; [self updateStatus]; } } - (void)scheduleNextUpdate { updateTimer = [NSTimer timerWithTimeInterval:_updateInterval target:self selector:@selector(updateStatus) userInfo:nil repeats:NO]; [[NSRunLoop currentRunLoop] addTimer:updateTimer forMode:NSRunLoopCommonModes]; } - (void)rpc:(NSString*)method success:(SEL)successCallback failure:(SEL)failureCallback { RPC* rpc = [[RPC alloc] initWithMethod:method receiver:self success:successCallback failure:failureCallback]; [rpc start]; } - (void)receiveStatus:(NSDictionary*)status { //DLog(@"receiveStatus"); if (_restarting) { return; } _connected = YES; _lastUpdate = [NSDate date]; _status = status; //DLog(@"response: %@", status); int uptime = [(NSNumber*)[status objectForKey:@"UpTimeSec"] integerValue]; if (lastUptime == 0) { lastUptime = uptime; } else if (lastUptime > uptime) { // daemon was reloaded (soft-restart) [self readConfigFile]; } [_delegate daemonStatusUpdated]; [self scheduleNextUpdate]; } - (void)failureStatus { DLog(@"failureStatus"); if (_restarting) { return; } _connected = NO; int pid = [self readLockFilePid]; if (pid == 0) { // Daemon is not running. Crashed? _restarting = YES; [_delegate daemonStatusUpdated]; [self start]; } else { [_delegate daemonStatusUpdated]; [self scheduleNextUpdate]; } } - (void)updateStatus { if (_restarting) { return; } [self rpc:@"status" success:@selector(receiveStatus:) failure:@selector(failureStatus)]; } @end nzbget-12.0+dfsg/osx/MainApp.h000066400000000000000000000045461226450633000161650ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 807 $ * $Date: 2013-08-31 23:14:39 +0200 (Sat, 31 Aug 2013) $ * */ #import #import "DaemonController.h" @interface MainApp : NSObject { IBOutlet NSMenu *statusMenu; NSStatusItem *statusItem; IBOutlet NSMenuItem *webuiItem; IBOutlet NSMenuItem *homePageItem; IBOutlet NSMenuItem *downloadsItem; IBOutlet NSMenuItem *forumItem; IBOutlet NSMenuItem *info1Item; IBOutlet NSMenuItem *info2Item; IBOutlet NSMenuItem *restartRecoveryItem; IBOutlet NSMenuItem *factoryResetItem; IBOutlet NSMenuItem *destDirItem; IBOutlet NSMenuItem *interDirItem; IBOutlet NSMenuItem *nzbDirItem; IBOutlet NSMenuItem *scriptDirItem; IBOutlet NSMenuItem *configFileItem; IBOutlet NSMenuItem *logFileItem; IBOutlet NSMenuItem *destDirSeparator; NSWindowController *welcomeDialog; NSWindowController *preferencesDialog; DaemonController *daemonController; int connectionAttempts; BOOL restarting; BOOL resetting; NSTimer* restartTimer; NSMutableArray* categoryItems; NSMutableArray* categoryDirs; } + (void)setupAppDefaults; - (void)setupDefaultsObserver; - (IBAction)quitClicked:(id)sender; - (IBAction)preferencesClicked:(id)sender; - (void)userDefaultsDidChange:(id)sender; - (IBAction)aboutClicked:(id)sender; + (BOOL)wasLaunchedAsLoginItem; - (IBAction)webuiClicked:(id)sender; - (IBAction)infoLinkClicked:(id)sender; - (IBAction)openConfigInTextEditClicked:(id)sender; - (IBAction)restartClicked:(id)sender; - (IBAction)showInFinderClicked:(id)sender; @end nzbget-12.0+dfsg/osx/MainApp.m000066400000000000000000000471151226450633000161710ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 930 $ * $Date: 2014-01-02 22:06:03 +0100 (Thu, 02 Jan 2014) $ * */ #import "MainApp.h" #import "PreferencesDialog.h" #import "WelcomeDialog.h" #import "PFMoveApplication.h" NSString *PreferencesContext = @"PreferencesContext"; const NSTimeInterval NORMAL_UPDATE_INTERVAL = 10.000; const NSTimeInterval MENUOPEN_UPDATE_INTERVAL = 1.000; const NSTimeInterval START_UPDATE_INTERVAL = 0.500; int main(int argc, char *argv[]) { return NSApplicationMain(argc, (const char **)argv); } /* * Signal handler */ void SignalProc(int iSignal) { switch (iSignal) { case SIGINT: case SIGTERM: signal(iSignal, SIG_DFL); // Reset the signal handler [NSApp terminate:nil]; break; } } // we install seignal handler in order to properly terminat app from Activity Mo1nitor void InstallSignalHandlers() { signal(SIGINT, SignalProc); signal(SIGTERM, SignalProc); signal(SIGPIPE, SIG_IGN); } @implementation MainApp - (void)applicationWillFinishLaunching:(NSNotification *)aNotification { [self checkOtherRunningInstances]; #ifndef DEBUG PFMoveToApplicationsFolderIfNecessary(); #endif } - (void)checkOtherRunningInstances { for (NSRunningApplication *runningApplication in [NSRunningApplication runningApplicationsWithBundleIdentifier:[[NSBundle mainBundle] bundleIdentifier]]) { if (![[runningApplication executableURL] isEqualTo:[[NSRunningApplication currentApplication] executableURL]]) { NSString *executablePath = [[runningApplication executableURL] path]; executablePath = [[[executablePath stringByDeletingLastPathComponent] stringByDeletingLastPathComponent] stringByDeletingLastPathComponent]; DLog(@"Switching to an already running instance: %@", executablePath); [[NSTask launchedTaskWithLaunchPath:@"/usr/bin/open" arguments:[NSArray arrayWithObjects:executablePath, @"--args", @"--second-instance", nil]] waitUntilExit]; exit(0); } } } - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { BOOL autoStartWebUI = [[NSUserDefaults standardUserDefaults] boolForKey:@"AutoStartWebUI"]; daemonController = [[DaemonController alloc] init]; daemonController.updateInterval = autoStartWebUI ? START_UPDATE_INTERVAL : NORMAL_UPDATE_INTERVAL; daemonController.delegate = self; [self setupDefaultsObserver]; [self userDefaultsDidChange:nil]; if (![MainApp wasLaunchedAsLoginItem]) { [self showWelcomeScreen]; } InstallSignalHandlers(); DLog(@"Start Daemon"); [daemonController start]; } - (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag { DLog(@"applicationShouldHandleReopen"); [self showAlreadyRunning]; return YES; } + (void)initialize { [self setupAppDefaults]; } - (void)awakeFromNib { DLog(@"awakeFromNib"); [statusMenu setDelegate:self]; } + (void)setupAppDefaults { NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys: @"YES", @"ShowInMenubar", @"NO", @"Autostart", @"YES", @"AutoStartWebUI", nil]; [userDefaults registerDefaults:appDefaults]; } - (void)setupDefaultsObserver { NSUserDefaultsController *sdc = [NSUserDefaultsController sharedUserDefaultsController]; [sdc addObserver:self forKeyPath:@"values.ShowInMenubar" options:0 context:(__bridge void *)(PreferencesContext)]; [sdc addObserver:self forKeyPath:@"values.AutoStartWebUI" options:0 context:(__bridge void *)(PreferencesContext)]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (context == (__bridge void *)(PreferencesContext)) { [self userDefaultsDidChange:nil]; } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } - (void)userDefaultsDidChange:(id)sender { DLog(@"userDefaultsDidChange: %@", sender); BOOL showInMenubar = [[NSUserDefaults standardUserDefaults] boolForKey:@"ShowInMenubar"]; if (showInMenubar != (statusItem != nil)) { if (showInMenubar) { statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength]; [statusItem setHighlightMode:YES]; [statusItem setMenu:statusMenu]; [statusItem setImage:[NSImage imageNamed:@"statusicon.png"]]; [statusItem setAlternateImage:[NSImage imageNamed:@"statusicon-inv.png"]]; } else { statusItem = nil; } } } - (IBAction)preferencesClicked:(id)sender { [self showPreferences]; } - (void)showPreferences { [NSApp activateIgnoringOtherApps:TRUE]; if (preferencesDialog) { return; } preferencesDialog = [[PreferencesDialog alloc] init]; [[preferencesDialog window] center]; [preferencesDialog showWindow:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(preferencesWillClose:) name:NSWindowWillCloseNotification object:[preferencesDialog window]]; } - (void)preferencesWillClose:(NSNotification *)notification { DLog(@"Pref Closed"); [[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowWillCloseNotification object:[preferencesDialog window]]; preferencesDialog = nil; [self userDefaultsDidChange:nil]; } - (IBAction)aboutClicked:(id)sender { [NSApp activateIgnoringOtherApps:TRUE]; [NSApp orderFrontStandardAboutPanel:nil]; } - (IBAction)quitClicked:(id)sender { DLog(@"Quit"); [NSApp terminate:nil]; } - (void)showWelcomeScreen { welcomeDialog = [[WelcomeDialog alloc] init]; [(WelcomeDialog*)welcomeDialog setMainDelegate:self]; [(WelcomeDialog*)welcomeDialog showDialog]; } - (void)showAlreadyRunning { BOOL showInMenubar = [[NSUserDefaults standardUserDefaults] boolForKey:@"ShowInMenubar"]; [NSApp activateIgnoringOtherApps:TRUE]; NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:NSLocalizedString(@"AlreadyRunning.MessageText", nil)]; [alert setInformativeText:NSLocalizedString(showInMenubar ? @"AlreadyRunning.InformativeTextWithIcon" : @"AlreadyRunning.InformativeTextWithoutIcon", nil)]; NSButton* webUIButton = [alert addButtonWithTitle:NSLocalizedString(@"AlreadyRunning.WebUI", nil)]; [alert addButtonWithTitle:NSLocalizedString(@"AlreadyRunning.Preferences", nil)]; [alert addButtonWithTitle:NSLocalizedString(@"AlreadyRunning.Quit", nil)]; [alert.window makeFirstResponder:webUIButton]; [webUIButton setKeyEquivalent:@"\r"]; switch ([alert runModal]) { case NSAlertFirstButtonReturn: [self showWebUI]; break; case NSAlertSecondButtonReturn: [self showPreferences]; break; case NSAlertThirdButtonReturn: [self quitClicked:nil]; break; } } + (BOOL)wasLaunchedByProcess:(NSString*)creator { BOOL wasLaunchedByProcess = NO; // Get our PSN OSStatus err; ProcessSerialNumber currPSN; err = GetCurrentProcess (&currPSN); if (!err) { // We don't use ProcessInformationCopyDictionary() because the 'ParentPSN' item in the dictionary // has endianness problems in 10.4, fixed in 10.5 however. ProcessInfoRec procInfo; bzero (&procInfo, sizeof (procInfo)); procInfo.processInfoLength = (UInt32)sizeof (ProcessInfoRec); err = GetProcessInformation (&currPSN, &procInfo); if (!err) { ProcessSerialNumber parentPSN = procInfo.processLauncher; // Get info on the launching process NSDictionary* parentDict = (__bridge NSDictionary*)ProcessInformationCopyDictionary (&parentPSN, kProcessDictionaryIncludeAllInformationMask); // Test the creator code of the launching app if (parentDict) { wasLaunchedByProcess = [[parentDict objectForKey:@"FileCreator"] isEqualToString:creator]; } } } return wasLaunchedByProcess; } + (BOOL)wasLaunchedAsLoginItem { // If the launching process was 'loginwindow', we were launched as a login item return [self wasLaunchedByProcess:@"lgnw"]; } - (IBAction)webuiClicked:(id)sender { if (daemonController.connected) { [self showWebUI]; } else { [NSApp activateIgnoringOtherApps:TRUE]; NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:NSLocalizedString(@"ShowWebUINoConnection.MessageText", nil)]; [alert setInformativeText:NSLocalizedString(@"ShowWebUINoConnection.InformativeText", nil)]; [alert setAlertStyle:NSWarningAlertStyle]; [alert runModal]; } } - (void)showWebUI { DLog(@"showWebUI"); [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString:daemonController.browserUrl]]; } - (IBAction)openConfigInTextEditClicked:(id)sender { NSString *configFile = [daemonController configFilePath]; [[NSWorkspace sharedWorkspace] openFile:configFile withApplication:@"TextEdit"]; } - (IBAction)restartClicked:(id)sender { if (sender == factoryResetItem) { [NSApp activateIgnoringOtherApps:TRUE]; NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:NSLocalizedString(@"FactoryReset.MessageText", nil)]; [alert setInformativeText:NSLocalizedString(@"FactoryReset.InformativeText", nil)]; [alert setAlertStyle:NSCriticalAlertStyle]; NSButton* cancelButton = [alert addButtonWithTitle:NSLocalizedString(@"FactoryReset.Cancel", nil)]; // we use middle invisible button to align the third RESET-button at left side [[alert addButtonWithTitle:@"Cancel"] setHidden:YES]; [alert addButtonWithTitle:NSLocalizedString(@"FactoryReset.Reset", nil)]; [alert.window makeFirstResponder:cancelButton]; [cancelButton setKeyEquivalent:@"\E"]; if ([alert runModal] != NSAlertThirdButtonReturn) { return; } } restarting = YES; resetting = sender == factoryResetItem; [self updateStatus]; [daemonController restartInRecoveryMode: sender == restartRecoveryItem withFactoryReset: sender == factoryResetItem]; daemonController.updateInterval = START_UPDATE_INTERVAL; restartTimer = [NSTimer timerWithTimeInterval:10.000 target:self selector:@selector(restartFailed) userInfo:nil repeats:NO]; [[NSRunLoop currentRunLoop] addTimer:restartTimer forMode:NSRunLoopCommonModes]; } - (void)welcomeContinue { DLog(@"welcomeContinue"); BOOL autoStartWebUI = [[NSUserDefaults standardUserDefaults] boolForKey:@"AutoStartWebUI"]; if (autoStartWebUI) { if (daemonController.connected) { if (daemonController.updateInterval == START_UPDATE_INTERVAL) { daemonController.updateInterval = NORMAL_UPDATE_INTERVAL; } [self showWebUI]; } else { // try again in 100 msec for max. 25 seconds, then give up connectionAttempts++; if (connectionAttempts < 250) { [self performSelector:@selector(welcomeContinue) withObject:nil afterDelay: 0.100]; } else { // show error message [self webuiClicked:nil]; } } } } - (void)applicationWillTerminate:(NSNotification *)aNotification { DLog(@"Stop Daemon"); [daemonController stop]; } - (void)menuWillOpen:(NSMenu *)menu { DLog(@"menuWillOpen"); daemonController.updateInterval = MENUOPEN_UPDATE_INTERVAL; } - (void)menuDidClose:(NSMenu *)menu { DLog(@"menuDidClose"); daemonController.updateInterval = NORMAL_UPDATE_INTERVAL; } - (IBAction)infoLinkClicked:(id)sender { NSString *url; if (sender == homePageItem) { url = NSLocalizedString(@"Menu.LinkHomePage", nil); } else if (sender == downloadsItem) { url = NSLocalizedString(@"Menu.LinkDownloads", nil); } else if (sender == forumItem) { url = NSLocalizedString(@"Menu.LinkForum", nil); } [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString:url]]; } - (void)restartFailed { if (restarting) { restarting = NO; resetting = NO; daemonController.updateInterval = NORMAL_UPDATE_INTERVAL; [NSApp activateIgnoringOtherApps:TRUE]; NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:NSLocalizedString(@"RestartNoConnection.MessageText", nil)]; [alert setInformativeText:NSLocalizedString(@"RestartNoConnection.InformativeText", nil)]; [alert setAlertStyle:NSWarningAlertStyle]; [alert runModal]; } } - (IBAction)showInFinderClicked:(id)sender { NSString* dir = nil; NSString* option = nil; if (sender == destDirItem) { option = @"DestDir"; } else if (sender == interDirItem) { option = @"InterDir"; } else if (sender == nzbDirItem) { option = @"NzbDir"; } else if (sender == scriptDirItem) { option = @"ScriptDir"; } else if (sender == logFileItem) { option = @"LogFile"; } else if (sender == configFileItem) { dir = [daemonController configFilePath]; } else if ([categoryItems containsObject:sender]) { int index = [categoryItems indexOfObject:sender]; dir = [categoryDirs objectAtIndex:index]; } else { return; } if (dir == nil) { NSString* mainDir = [[daemonController valueForOption:@"MainDir"] stringByExpandingTildeInPath]; dir = [[daemonController valueForOption:option] stringByExpandingTildeInPath]; dir = [dir stringByReplacingOccurrencesOfString:@"${MainDir}" withString:mainDir options:NSCaseInsensitiveSearch range:NSMakeRange(0, [dir length])]; } if (dir.length == 0 || ![[NSFileManager defaultManager] fileExistsAtPath:dir]) { [NSApp activateIgnoringOtherApps:TRUE]; NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:[NSString stringWithFormat:NSLocalizedString(@"CantShowInFinder.MessageText", nil), dir]]; [alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(option == nil ? @"CantShowInFinder.InformativeTextForCategory" : @"CantShowInFinder.InformativeTextWithOption", nil), option]]; [alert runModal]; return; } [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:@[[NSURL fileURLWithPath:dir isDirectory:YES]]]; } - (void)daemonStatusUpdated { if (restarting && daemonController.connected) { restarting = NO; [restartTimer invalidate]; [NSApp activateIgnoringOtherApps:TRUE]; NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:NSLocalizedString(resetting ? @"FactoryResetted.MessageText" : daemonController.recoveryMode ? @"RestartedRecoveryMode.MessageText" : @"Restarted.MessageText", nil)]; [alert setInformativeText:NSLocalizedString(resetting ? @"FactoryResetted.InformativeText" : daemonController.recoveryMode ? @"RestartedRecoveryMode.InformativeText" : @"Restarted.InformativeText", nil)]; [alert setAlertStyle:NSInformationalAlertStyle]; [alert addButtonWithTitle:NSLocalizedString(@"Restarted.OK", nil)]; [alert addButtonWithTitle:NSLocalizedString(@"Restarted.WebUI", nil)]; if ([alert runModal] == NSAlertSecondButtonReturn) { [self showWebUI]; } resetting = NO; } else { [self updateStatus]; } } - (void)updateStatus { //DLog(@"updateStatus"); NSString* info1 = @""; NSString* info2 = nil; NSDictionary* status = [daemonController status]; if (restarting || daemonController.restarting) { info1 = NSLocalizedString(@"Status.Restarting", nil); } else if (!daemonController.connected) { info1 = NSLocalizedString(@"Status.NoConnection", nil); } else if ([(NSNumber*)[status objectForKey:@"ServerStandBy"] integerValue] == 1) { if ([(NSNumber*)[status objectForKey:@"PostJobCount"] integerValue] > 0) { info1 = NSLocalizedString(@"Status.Post-Processing", nil); } else if ([(NSNumber*)[status objectForKey:@"UrlCount"] integerValue] > 0) { info1 = NSLocalizedString(@"Status.Fetching NZBs", nil); } else if ([(NSNumber*)[status objectForKey:@"FeedActive"] integerValue] == 1) { info1 = NSLocalizedString(@"Status.Fetching Feeds", nil); } else if ([(NSNumber*)[status objectForKey:@"DownloadPaused"] integerValue] == 1 || [(NSNumber*)[status objectForKey:@"Download2Paused"] integerValue] == 1) { info1 = NSLocalizedString(@"Status.Paused", nil); } else { info1 = NSLocalizedString(@"Status.Idle", nil); } } else { int speed = [(NSNumber*)[status objectForKey:@"DownloadRate"] integerValue]; info1 = [NSString stringWithFormat:NSLocalizedString(@"Status.Downloading", nil), speed / 1024]; if (speed > 0) { long long remaining = ([(NSNumber*)[status objectForKey:@"RemainingSizeHi"] integerValue] << 32) + [(NSNumber*)[status objectForKey:@"RemainingSizeLo"] integerValue]; int secondsLeft = remaining / speed; info2 = [NSString stringWithFormat:NSLocalizedString(@"Status.Left", nil), [self formatTimeLeft:secondsLeft]]; } } [info1Item setTitle:info1]; [info2Item setHidden:info2 == nil]; if (info2 != nil) { [info2Item setTitle:info2]; } } - (NSString*)formatTimeLeft:(int)sec { int days = floor(sec / 86400); int hours = floor((sec % 86400) / 3600); int minutes = floor((sec / 60) % 60); int seconds = floor(sec % 60); if (days > 10) { return [NSString stringWithFormat:NSLocalizedString(@"Left.Days", nil), days]; } if (days > 0) { return [NSString stringWithFormat:NSLocalizedString(@"Left.Days.Hours", nil), days, hours]; } if (hours > 10) { return [NSString stringWithFormat:NSLocalizedString(@"Left.Hours", nil), hours]; } if (hours > 0) { return [NSString stringWithFormat:NSLocalizedString(@"Left.Hours.Minutes", nil), hours, minutes]; } if (minutes > 10) { return [NSString stringWithFormat:NSLocalizedString(@"Left.Minutes", nil), minutes]; } if (minutes > 0) { return [NSString stringWithFormat:NSLocalizedString(@"Left.Minutes.Seconds", nil), minutes, seconds]; } return [NSString stringWithFormat:NSLocalizedString(@"Left.Seconds", nil), seconds]; } - (void)daemonConfigLoaded { DLog(@"config loaded"); [self updateCategoriesMenu]; } - (void)updateCategoriesMenu { NSMenu *submenu = destDirItem.parentItem.submenu; for (NSMenuItem* item in categoryItems) { [submenu removeItem:item]; } categoryItems = [NSMutableArray array]; categoryDirs = [NSMutableArray array]; NSString* mainDir = [[daemonController valueForOption:@"MainDir"] stringByExpandingTildeInPath]; NSString* destDir = [[daemonController valueForOption:@"DestDir"] stringByExpandingTildeInPath]; for (int i=1; ; i++) { NSString* catName = [daemonController valueForOption:[NSString stringWithFormat:@"Category%i.Name", i]]; NSString* catDir = [daemonController valueForOption:[NSString stringWithFormat:@"Category%i.DestDir", i]]; if (catName.length == 0) { break; } if (catDir.length == 0) { catDir = [destDir stringByAppendingPathComponent:catName]; } NSString* dir = [catDir stringByExpandingTildeInPath]; dir = [dir stringByReplacingOccurrencesOfString:@"${MainDir}" withString:mainDir options:NSCaseInsensitiveSearch range:NSMakeRange(0, dir.length)]; dir = [dir stringByReplacingOccurrencesOfString:@"${DestDir}" withString:destDir options:NSCaseInsensitiveSearch range:NSMakeRange(0, dir.length)]; NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[NSString stringWithFormat:@"Category%i: %@", i, catName] action:@selector(showInFinderClicked:) keyEquivalent:@""]; [item setTarget:self]; [submenu insertItem:item atIndex:[submenu indexOfItem:destDirSeparator]]; [categoryItems addObject:item]; [categoryDirs addObject:dir]; } } @end nzbget-12.0+dfsg/osx/MainApp.xib000066400000000000000000001202431226450633000165110ustar00rootroot00000000000000 1070 11G63 3084 1138.51 569.00 com.apple.InterfaceBuilder.CocoaPlugin 3084 YES NSCustomObject NSMenu NSMenuItem NSUserDefaultsController YES com.apple.InterfaceBuilder.CocoaPlugin PluginDependencyRecalculationVersion YES NSApplication FirstResponder NSApplication NSFontManager MainApp YES YES Starting 2147483647 NSImage NSMenuCheckmark NSImage NSMenuMixedState YES YES Left 2147483647 YES YES 2147483647 Show Web-Interface 2147483647 Show in Finder 2147483647 submenuAction: Show in Finder YES Default Destination 2147483647 YES YES 2147483647 Intermediate Files 2147483647 Incoming NZBs 2147483647 Post-Processing Scripts 2147483647 YES YES 2147483647 Config-File 2147483647 Log-File 2147483647 Info-Links 2147483647 submenuAction: Info-Links YES Home Page 2147483647 Downloads 2147483647 Forum 2147483647 Troubleshooting 2147483647 submenuAction: Troubleshooting YES Restart 2147483647 Restart in Recovery Mode 2147483647 Open Config in TextEdit 2147483647 YES YES 2147483647 Reset to Factory Defaults 2147483647 YES YES 2147483647 About NZBGet 2147483647 Preferences... 2147483647 YES YES 2147483647 Quit NZBGet 2147483647 YES YES delegate 824 statusMenu 831 quitClicked: 832 preferencesClicked: 850 aboutClicked: 856 webuiClicked: 865 webuiItem 866 homePageItem 874 downloadsItem 875 forumItem 876 infoLinkClicked: 877 infoLinkClicked: 878 infoLinkClicked: 879 infoItem 893 restartRecoveryItem 896 destDirItem 911 interDirItem 912 nzbDirItem 913 scriptDirItem 915 showInFinderClicked: 916 showInFinderClicked: 917 showInFinderClicked: 918 showInFinderClicked: 919 showInFinderClicked: 922 info2Item 925 info1Item 926 destDirSeparator 927 showInFinderClicked: 929 configFileItem 930 logFileItem 931 restartClicked: 940 openConfigInTextEditClicked: 941 restartClicked: 943 restartClicked: 944 factoryResetItem 945 YES 0 YES -2 File's Owner -1 First Responder -3 Application 373 823 827 YES 828 840 849 851 860 861 867 YES 868 YES 869 870 872 880 YES 881 YES 882 885 887 889 890 897 898 YES 899 YES 900 902 906 908 910 920 921 924 928 934 935 YES YES -1.IBPluginDependency -2.IBPluginDependency -3.IBPluginDependency 373.IBPluginDependency 823.IBPluginDependency 827.IBPluginDependency 828.IBPluginDependency 840.IBPluginDependency 849.IBPluginDependency 851.IBPluginDependency 860.IBPluginDependency 861.IBPluginDependency 867.IBPluginDependency 868.IBPluginDependency 869.IBPluginDependency 870.IBPluginDependency 872.IBPluginDependency 880.IBPluginDependency 881.IBPluginDependency 882.IBPluginDependency 885.IBPluginDependency 887.IBPluginDependency 889.IBPluginDependency 890.IBPluginDependency 897.IBPluginDependency 898.IBPluginDependency 899.IBPluginDependency 900.IBPluginDependency 902.IBPluginDependency 906.IBPluginDependency 908.IBPluginDependency 910.IBPluginDependency 920.IBPluginDependency 921.IBPluginDependency 924.IBPluginDependency 928.IBPluginDependency 934.IBPluginDependency 935.IBPluginDependency YES com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin YES YES 945 0 IBCocoaFramework com.apple.InterfaceBuilder.CocoaPlugin.macosx com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 YES 3 YES YES NSMenuCheckmark NSMenuMixedState YES {11, 11} {10, 3} nzbget-12.0+dfsg/osx/NZBGet-Info.plist000066400000000000000000000021041226450633000175120ustar00rootroot00000000000000 CFBundleDevelopmentRegion English CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIconFile mainicon.icns CFBundleIdentifier net.sourceforge.nzbget CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 12.0-testing-r831M LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion 10.7 LSUIElement NSHumanReadableCopyright Copyright © 2007-2013 Andrey Prygunkov NSMainNibFile MainApp NSPrincipalClass NSApplication nzbget-12.0+dfsg/osx/NZBGet.xcodeproj/000077500000000000000000000000001226450633000175435ustar00rootroot00000000000000nzbget-12.0+dfsg/osx/NZBGet.xcodeproj/project.pbxproj000066400000000000000000000517001226450633000226220ustar00rootroot00000000000000// !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; F20FC6E217C6BAAE00C392AC /* PFMoveApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = F20FC6DF17C6B9FC00C392AC /* PFMoveApplication.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; F20FC6E417C6BC8D00C392AC /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F20FC6E317C6BC8D00C392AC /* Security.framework */; }; F21D369B13BF387F00E6D821 /* PreferencesDialog.m in Sources */ = {isa = PBXBuildFile; fileRef = F21D369A13BF387F00E6D821 /* PreferencesDialog.m */; }; F26CBA8B136DE86A00DCB596 /* MainApp.m in Sources */ = {isa = PBXBuildFile; fileRef = F26CBA8A136DE86A00DCB596 /* MainApp.m */; }; F26D959317C0E81800E58E5D /* Welcome.rtf in Resources */ = {isa = PBXBuildFile; fileRef = F26D959217C0E81800E58E5D /* Welcome.rtf */; }; F26D959517C0E86300E58E5D /* MainApp.xib in Resources */ = {isa = PBXBuildFile; fileRef = F26D959417C0E86300E58E5D /* MainApp.xib */; }; F26D959717C0E87E00E58E5D /* PreferencesDialog.xib in Resources */ = {isa = PBXBuildFile; fileRef = F26D959617C0E87E00E58E5D /* PreferencesDialog.xib */; }; F26D959917C0E88700E58E5D /* WelcomeDialog.xib in Resources */ = {isa = PBXBuildFile; fileRef = F26D959817C0E88700E58E5D /* WelcomeDialog.xib */; }; F26D959B17C0E89D00E58E5D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F26D959A17C0E89D00E58E5D /* Localizable.strings */; }; F29ABB2617BFC61B0023A423 /* DaemonController.m in Sources */ = {isa = PBXBuildFile; fileRef = F29ABB2417BFC03D0023A423 /* DaemonController.m */; }; F29ABB2B17C00E150023A423 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29B97324FDCFA39411CA2CEA /* AppKit.framework */; }; F29ABB2C17C00E190023A423 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29B97325FDCFA39411CA2CEA /* Foundation.framework */; }; F2A55D3717C4CA9000D6FFE1 /* daemon in Resources */ = {isa = PBXBuildFile; fileRef = F2A55D3617C4CA9000D6FFE1 /* daemon */; }; F2A55D3B17C4CAF800D6FFE1 /* tools in Resources */ = {isa = PBXBuildFile; fileRef = F2A55D3A17C4CAF800D6FFE1 /* tools */; }; F2A6E11017C8E42300D910CB /* statusicon-inv@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F2A6E10E17C8E42300D910CB /* statusicon-inv@2x.png */; }; F2A6E11117C8E42300D910CB /* statusicon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F2A6E10F17C8E42300D910CB /* statusicon@2x.png */; }; F2BBD9C613E083160037473A /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = F2FC0F8B13BF595700D834E3 /* Credits.rtf */; }; F2CD856817C282080019D2CA /* RPC.m in Sources */ = {isa = PBXBuildFile; fileRef = F2CD856517C254A90019D2CA /* RPC.m */; }; F2CD856B17C282820019D2CA /* WebClient.m in Sources */ = {isa = PBXBuildFile; fileRef = F2CD856A17C282800019D2CA /* WebClient.m */; }; F2D2A2F113CBA680000824B4 /* WelcomeDialog.m in Sources */ = {isa = PBXBuildFile; fileRef = F2D2A2EF13CBA680000824B4 /* WelcomeDialog.m */; }; F2F9804A17C9081D004623D6 /* licenses in Resources */ = {isa = PBXBuildFile; fileRef = F2F9804917C9081D004623D6 /* licenses */; }; F2FCD73B13BB9CE900FC81F5 /* mainicon.icns in Resources */ = {isa = PBXBuildFile; fileRef = F2FCD73A13BB9CE900FC81F5 /* mainicon.icns */; }; F2FCD7D913BBDAED00FC81F5 /* statusicon.png in Resources */ = {isa = PBXBuildFile; fileRef = F2FCD7D813BBDAED00FC81F5 /* statusicon.png */; }; F2FCD84D13BCFD0900FC81F5 /* statusicon-inv.png in Resources */ = {isa = PBXBuildFile; fileRef = F2FCD84C13BCFD0900FC81F5 /* statusicon-inv.png */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; 256AC3F00F4B6AF500CF3369 /* App_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = App_Prefix.pch; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 8D1107310486CEB800E47090 /* NZBGet-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "NZBGet-Info.plist"; sourceTree = ""; }; 8D1107320486CEB800E47090 /* NZBGet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NZBGet.app; sourceTree = BUILT_PRODUCTS_DIR; }; F20FC6DE17C6B9FC00C392AC /* PFMoveApplication.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFMoveApplication.h; sourceTree = ""; }; F20FC6DF17C6B9FC00C392AC /* PFMoveApplication.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFMoveApplication.m; sourceTree = ""; }; F20FC6E317C6BC8D00C392AC /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/System/Library/Frameworks/Security.framework; sourceTree = DEVELOPER_DIR; }; F21D369913BF387F00E6D821 /* PreferencesDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PreferencesDialog.h; sourceTree = ""; }; F21D369A13BF387F00E6D821 /* PreferencesDialog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PreferencesDialog.m; sourceTree = ""; }; F26CBA89136DE86A00DCB596 /* MainApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MainApp.h; sourceTree = ""; }; F26CBA8A136DE86A00DCB596 /* MainApp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MainApp.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; F26D959217C0E81800E58E5D /* Welcome.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; name = Welcome.rtf; path = Resources/Welcome.rtf; sourceTree = ""; }; F26D959417C0E86300E58E5D /* MainApp.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainApp.xib; sourceTree = ""; }; F26D959617C0E87E00E58E5D /* PreferencesDialog.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PreferencesDialog.xib; sourceTree = ""; }; F26D959817C0E88700E58E5D /* WelcomeDialog.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = WelcomeDialog.xib; sourceTree = ""; }; F26D959A17C0E89D00E58E5D /* Localizable.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = Localizable.strings; path = Resources/Localizable.strings; sourceTree = ""; }; F29ABB2317BFC03D0023A423 /* DaemonController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DaemonController.h; sourceTree = ""; }; F29ABB2417BFC03D0023A423 /* DaemonController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DaemonController.m; sourceTree = ""; }; F2A55D3617C4CA9000D6FFE1 /* daemon */ = {isa = PBXFileReference; lastKnownFileType = folder; name = daemon; path = Resources/daemon; sourceTree = ""; }; F2A55D3A17C4CAF800D6FFE1 /* tools */ = {isa = PBXFileReference; lastKnownFileType = folder; name = tools; path = Resources/tools; sourceTree = ""; }; F2A6E10E17C8E42300D910CB /* statusicon-inv@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "statusicon-inv@2x.png"; path = "Images/statusicon-inv@2x.png"; sourceTree = ""; }; F2A6E10F17C8E42300D910CB /* statusicon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "statusicon@2x.png"; path = "Images/statusicon@2x.png"; sourceTree = ""; }; F2CD856417C254A90019D2CA /* RPC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RPC.h; sourceTree = ""; }; F2CD856517C254A90019D2CA /* RPC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RPC.m; sourceTree = ""; }; F2CD856917C282800019D2CA /* WebClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WebClient.h; sourceTree = ""; }; F2CD856A17C282800019D2CA /* WebClient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WebClient.m; sourceTree = ""; }; F2D2A2EE13CBA680000824B4 /* WelcomeDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WelcomeDialog.h; sourceTree = ""; }; F2D2A2EF13CBA680000824B4 /* WelcomeDialog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WelcomeDialog.m; sourceTree = ""; }; F2F9804917C9081D004623D6 /* licenses */ = {isa = PBXFileReference; lastKnownFileType = folder; name = licenses; path = Resources/licenses; sourceTree = ""; }; F2FC0F8B13BF595700D834E3 /* Credits.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = Credits.rtf; path = Resources/Credits.rtf; sourceTree = ""; }; F2FCD73A13BB9CE900FC81F5 /* mainicon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = mainicon.icns; path = Images/mainicon.icns; sourceTree = ""; }; F2FCD7D813BBDAED00FC81F5 /* statusicon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = statusicon.png; path = Images/statusicon.png; sourceTree = ""; }; F2FCD84C13BCFD0900FC81F5 /* statusicon-inv.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "statusicon-inv.png"; path = "Images/statusicon-inv.png"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 8D11072E0486CEB800E47090 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( F20FC6E417C6BC8D00C392AC /* Security.framework in Frameworks */, 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, F29ABB2B17C00E150023A423 /* AppKit.framework in Frameworks */, F29ABB2C17C00E190023A423 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 19C28FACFE9D520D11CA2CBB /* Products */ = { isa = PBXGroup; children = ( 8D1107320486CEB800E47090 /* NZBGet.app */, ); name = Products; sourceTree = ""; }; 29B97314FDCFA39411CA2CEA /* NZBGet */ = { isa = PBXGroup; children = ( F26CBA8C136DE87500DCB596 /* Main */, F29ABB2517BFC0450023A423 /* DaemonController */, F2D2A2F213CBA7C8000824B4 /* WelcomeDialog */, F21D343013BE339300E6D821 /* Preferences */, F20FC6E017C6BA0100C392AC /* LetsMove */, 29B97315FDCFA39411CA2CEA /* Other Sources */, 29B97317FDCFA39411CA2CEA /* Resources */, 29B97323FDCFA39411CA2CEA /* Frameworks */, 19C28FACFE9D520D11CA2CBB /* Products */, ); name = "NZBGet"; sourceTree = ""; }; 29B97315FDCFA39411CA2CEA /* Other Sources */ = { isa = PBXGroup; children = ( 256AC3F00F4B6AF500CF3369 /* App_Prefix.pch */, ); name = "Other Sources"; sourceTree = ""; }; 29B97317FDCFA39411CA2CEA /* Resources */ = { isa = PBXGroup; children = ( F2F9804917C9081D004623D6 /* licenses */, F2A55D3617C4CA9000D6FFE1 /* daemon */, F2A55D3A17C4CAF800D6FFE1 /* tools */, F2D370AE13C0859A002C0573 /* Images */, F26D959A17C0E89D00E58E5D /* Localizable.strings */, F26D959217C0E81800E58E5D /* Welcome.rtf */, F2FC0F8B13BF595700D834E3 /* Credits.rtf */, 8D1107310486CEB800E47090 /* NZBGet-Info.plist */, ); name = Resources; sourceTree = ""; }; 29B97323FDCFA39411CA2CEA /* Frameworks */ = { isa = PBXGroup; children = ( 29B97324FDCFA39411CA2CEA /* AppKit.framework */, 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, 29B97325FDCFA39411CA2CEA /* Foundation.framework */, F20FC6E317C6BC8D00C392AC /* Security.framework */, ); name = Frameworks; sourceTree = ""; }; F20FC6E017C6BA0100C392AC /* LetsMove */ = { isa = PBXGroup; children = ( F20FC6DE17C6B9FC00C392AC /* PFMoveApplication.h */, F20FC6DF17C6B9FC00C392AC /* PFMoveApplication.m */, ); name = LetsMove; sourceTree = ""; }; F21D343013BE339300E6D821 /* Preferences */ = { isa = PBXGroup; children = ( F26D959617C0E87E00E58E5D /* PreferencesDialog.xib */, F21D369913BF387F00E6D821 /* PreferencesDialog.h */, F21D369A13BF387F00E6D821 /* PreferencesDialog.m */, ); name = Preferences; sourceTree = ""; }; F26CBA8C136DE87500DCB596 /* Main */ = { isa = PBXGroup; children = ( F26D959417C0E86300E58E5D /* MainApp.xib */, F26CBA89136DE86A00DCB596 /* MainApp.h */, F26CBA8A136DE86A00DCB596 /* MainApp.m */, ); name = Main; sourceTree = ""; }; F29ABB2517BFC0450023A423 /* DaemonController */ = { isa = PBXGroup; children = ( F2CD856917C282800019D2CA /* WebClient.h */, F2CD856A17C282800019D2CA /* WebClient.m */, F2CD856417C254A90019D2CA /* RPC.h */, F2CD856517C254A90019D2CA /* RPC.m */, F29ABB2317BFC03D0023A423 /* DaemonController.h */, F29ABB2417BFC03D0023A423 /* DaemonController.m */, ); name = DaemonController; sourceTree = ""; }; F2D2A2F213CBA7C8000824B4 /* WelcomeDialog */ = { isa = PBXGroup; children = ( F26D959817C0E88700E58E5D /* WelcomeDialog.xib */, F2D2A2EE13CBA680000824B4 /* WelcomeDialog.h */, F2D2A2EF13CBA680000824B4 /* WelcomeDialog.m */, ); name = WelcomeDialog; sourceTree = ""; }; F2D370AE13C0859A002C0573 /* Images */ = { isa = PBXGroup; children = ( F2FCD73A13BB9CE900FC81F5 /* mainicon.icns */, F2A6E10E17C8E42300D910CB /* statusicon-inv@2x.png */, F2A6E10F17C8E42300D910CB /* statusicon@2x.png */, F2FCD84C13BCFD0900FC81F5 /* statusicon-inv.png */, F2FCD7D813BBDAED00FC81F5 /* statusicon.png */, ); name = Images; path = Resources; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 8D1107260486CEB800E47090 /* NZBGet */ = { isa = PBXNativeTarget; buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "NZBGet" */; buildPhases = ( 8D1107290486CEB800E47090 /* Resources */, 8D11072C0486CEB800E47090 /* Sources */, 8D11072E0486CEB800E47090 /* Frameworks */, ); buildRules = ( ); dependencies = ( ); name = NZBGet; productInstallPath = "$(HOME)/Applications"; productName = "NZBGet"; productReference = 8D1107320486CEB800E47090 /* NZBGet.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 29B97313FDCFA39411CA2CEA /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 0460; ORGANIZATIONNAME = "Andrey Prygunkov"; }; buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "NZBGet" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 1; knownRegions = ( English, Japanese, French, German, Russian, de, ru, ); mainGroup = 29B97314FDCFA39411CA2CEA /* NZBGet */; projectDirPath = ""; projectRoot = ""; targets = ( 8D1107260486CEB800E47090 /* NZBGet */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 8D1107290486CEB800E47090 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( F2FCD73B13BB9CE900FC81F5 /* mainicon.icns in Resources */, F2FCD7D913BBDAED00FC81F5 /* statusicon.png in Resources */, F2FCD84D13BCFD0900FC81F5 /* statusicon-inv.png in Resources */, F2BBD9C613E083160037473A /* Credits.rtf in Resources */, F26D959317C0E81800E58E5D /* Welcome.rtf in Resources */, F26D959517C0E86300E58E5D /* MainApp.xib in Resources */, F26D959717C0E87E00E58E5D /* PreferencesDialog.xib in Resources */, F26D959917C0E88700E58E5D /* WelcomeDialog.xib in Resources */, F26D959B17C0E89D00E58E5D /* Localizable.strings in Resources */, F2A55D3717C4CA9000D6FFE1 /* daemon in Resources */, F2A55D3B17C4CAF800D6FFE1 /* tools in Resources */, F2A6E11017C8E42300D910CB /* statusicon-inv@2x.png in Resources */, F2A6E11117C8E42300D910CB /* statusicon@2x.png in Resources */, F2F9804A17C9081D004623D6 /* licenses in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 8D11072C0486CEB800E47090 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F26CBA8B136DE86A00DCB596 /* MainApp.m in Sources */, F21D369B13BF387F00E6D821 /* PreferencesDialog.m in Sources */, F2D2A2F113CBA680000824B4 /* WelcomeDialog.m in Sources */, F29ABB2617BFC61B0023A423 /* DaemonController.m in Sources */, F2CD856817C282080019D2CA /* RPC.m in Sources */, F2CD856B17C282820019D2CA /* WebClient.m in Sources */, F20FC6E217C6BAAE00C392AC /* PFMoveApplication.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ C01FCF4B08A954540054247B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ENABLE_OBJC_ARC = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/Frameworks/ApplicationServices.framework/Frameworks\"", ); GCC_DYNAMIC_NO_PIC = NO; GCC_MODEL_TUNING = G5; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = App_Prefix.pch; "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = DEBUG; HEADER_SEARCH_PATHS = ""; INFOPLIST_FILE = "NZBGet-Info.plist"; INSTALL_PATH = "$(HOME)/Applications"; MACOSX_DEPLOYMENT_TARGET = 10.7; OTHER_LDFLAGS = ""; PRODUCT_NAME = NZBGet; SDKROOT = macosx10.7; VALID_ARCHS = x86_64; }; name = Debug; }; C01FCF4C08A954540054247B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ENABLE_OBJC_ARC = YES; CODE_SIGN_IDENTITY = ""; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/Frameworks/ApplicationServices.framework/Frameworks\"", ); GCC_MODEL_TUNING = G5; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = App_Prefix.pch; INFOPLIST_FILE = "NZBGet-Info.plist"; INSTALL_PATH = "$(HOME)/Applications"; MACOSX_DEPLOYMENT_TARGET = 10.7; PRODUCT_NAME = NZBGet; SDKROOT = macosx10.7; VALID_ARCHS = x86_64; }; name = Release; }; C01FCF4F08A954540054247B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_OPTIMIZATION_LEVEL = 0; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = ""; SDKROOT = macosx10.6; }; name = Debug; }; C01FCF5008A954540054247B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; CODE_SIGN_IDENTITY = ""; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; PRODUCT_NAME = ""; SDKROOT = macosx10.6; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "NZBGet" */ = { isa = XCConfigurationList; buildConfigurations = ( C01FCF4B08A954540054247B /* Debug */, C01FCF4C08A954540054247B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; C01FCF4E08A954540054247B /* Build configuration list for PBXProject "NZBGet" */ = { isa = XCConfigurationList; buildConfigurations = ( C01FCF4F08A954540054247B /* Debug */, C01FCF5008A954540054247B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; } nzbget-12.0+dfsg/osx/PFMoveApplication.h000066400000000000000000000004141226450633000201460ustar00rootroot00000000000000// // PFMoveApplication.h, version 1.8 // LetsMove // // Created by Andy Kim at Potion Factory LLC on 9/17/09 // // The contents of this file are dedicated to the public domain. #import void PFMoveToApplicationsFolderIfNecessary(void); nzbget-12.0+dfsg/osx/PFMoveApplication.m000066400000000000000000000432021226450633000201550ustar00rootroot00000000000000// // PFMoveApplication.m, version 1.8 // LetsMove // // Created by Andy Kim at Potion Factory LLC on 9/17/09 // // The contents of this file are dedicated to the public domain. #import "PFMoveApplication.h" // Andrey Prygunkov (NZBGet): // all references to "NSString+SymlinksAndAliases.h" removed because // it has a BSD like license which might not be compatible with GPLv2. // This makes the function a little bit less robust not properly handle some rare cases. //#import "NSString+SymlinksAndAliases.h" #import #import // Strings // These are macros to be able to use custom i18n tools #define _I10NS(nsstr) NSLocalizedStringFromTable(nsstr, @"MoveApplication", nil) #define kStrMoveApplicationCouldNotMove _I10NS(@"Could not move to Applications folder") #define kStrMoveApplicationQuestionTitle _I10NS(@"Move to Applications folder?") #define kStrMoveApplicationQuestionTitleHome _I10NS(@"Move to Applications folder in your Home folder?") #define kStrMoveApplicationQuestionMessage _I10NS(@"NZBGet can move itself to the Applications folder if you'd like.") #define kStrMoveApplicationButtonMove _I10NS(@"Move to Applications Folder") #define kStrMoveApplicationButtonDoNotMove _I10NS(@"Do Not Move") #define kStrMoveApplicationQuestionInfoWillRequirePasswd _I10NS(@"Note that this will require an administrator password.") #define kStrMoveApplicationQuestionInfoInDownloadsFolder _I10NS(@"This will keep your Downloads folder uncluttered.") // Needs to be defined for compiling under 10.4 SDK #ifndef NSAppKitVersionNumber10_4 #define NSAppKitVersionNumber10_4 824 #endif // Needs to be defined for compiling under 10.5 SDK #ifndef NSAppKitVersionNumber10_5 #define NSAppKitVersionNumber10_5 949 #endif // By default, we use a small control/font for the suppression button. // If you prefer to use the system default (to match your other alerts), // set this to 0. #define PFUseSmallAlertSuppressCheckbox 1 static NSString *AlertSuppressKey = @"moveToApplicationsFolderAlertSuppress"; // Helper functions static NSString *PreferredInstallLocation(BOOL *isUserDirectory); static BOOL IsInApplicationsFolder(NSString *path); static BOOL IsInDownloadsFolder(NSString *path); static BOOL IsLaunchedFromDMG(); static BOOL Trash(NSString *path); static BOOL AuthorizedInstall(NSString *srcPath, NSString *dstPath, BOOL *canceled); static BOOL CopyBundle(NSString *srcPath, NSString *dstPath); static void Relaunch(); // Main worker function void PFMoveToApplicationsFolderIfNecessary(void) { // Skip if user suppressed the alert before if ([[NSUserDefaults standardUserDefaults] boolForKey:AlertSuppressKey]) return; // Path of the bundle NSString *bundlePath = [[NSBundle mainBundle] bundlePath]; // Skip if the application is already in some Applications folder if (IsInApplicationsFolder(bundlePath)) return; // File Manager NSFileManager *fm = [NSFileManager defaultManager]; BOOL isLaunchedFromDMG = IsLaunchedFromDMG(); // Since we are good to go, get the preferred installation directory. BOOL installToUserApplications = NO; NSString *applicationsDirectory = PreferredInstallLocation(&installToUserApplications); NSString *bundleName = [bundlePath lastPathComponent]; NSString *destinationPath = [applicationsDirectory stringByAppendingPathComponent:bundleName]; // Check if we need admin password to write to the Applications directory BOOL needAuthorization = ([fm isWritableFileAtPath:applicationsDirectory] == NO); // Check if the destination bundle is already there but not writable needAuthorization |= ([fm fileExistsAtPath:destinationPath] && ![fm isWritableFileAtPath:destinationPath]); // Setup the alert NSAlert *alert = [[[NSAlert alloc] init] autorelease]; { NSString *informativeText = nil; [alert setMessageText:(installToUserApplications ? kStrMoveApplicationQuestionTitleHome : kStrMoveApplicationQuestionTitle)]; informativeText = kStrMoveApplicationQuestionMessage; if (needAuthorization) { informativeText = [informativeText stringByAppendingString:@" "]; informativeText = [informativeText stringByAppendingString:kStrMoveApplicationQuestionInfoWillRequirePasswd]; } else if (IsInDownloadsFolder(bundlePath)) { // Don't mention this stuff if we need authentication. The informative text is long enough as it is in that case. informativeText = [informativeText stringByAppendingString:@" "]; informativeText = [informativeText stringByAppendingString:kStrMoveApplicationQuestionInfoInDownloadsFolder]; } [alert setInformativeText:informativeText]; // Add accept button [alert addButtonWithTitle:kStrMoveApplicationButtonMove]; // Add deny button NSButton *cancelButton = [alert addButtonWithTitle:kStrMoveApplicationButtonDoNotMove]; [cancelButton setKeyEquivalent:@"\e"]; #if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4 if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_4) { // Setup suppression button [alert setShowsSuppressionButton:YES]; if (PFUseSmallAlertSuppressCheckbox) { [[[alert suppressionButton] cell] setControlSize:NSSmallControlSize]; [[[alert suppressionButton] cell] setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; } } #endif } // Activate app -- work-around for focus issues related to "scary file from internet" OS dialog. if (![NSApp isActive]) { [NSApp activateIgnoringOtherApps:YES]; } if ([alert runModal] == NSAlertFirstButtonReturn) { DLog(@"INFO -- Moving myself to the Applications folder"); // Move if (needAuthorization) { BOOL authorizationCanceled; if (!AuthorizedInstall(bundlePath, destinationPath, &authorizationCanceled)) { if (authorizationCanceled) { DLog(@"INFO -- Not moving because user canceled authorization"); return; } else { DLog(@"ERROR -- Could not copy myself to /Applications with authorization"); goto fail; } } } else { // If a copy already exists in the Applications folder, put it in the Trash if ([fm fileExistsAtPath:destinationPath]) { // But first, make sure that it's not running BOOL destinationIsRunning = NO; // Use the shell to determine if the app is already running on systems 10.5 or lower if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_5) { NSString *script = [NSString stringWithFormat:@"ps ax -o comm | grep '%@/' | grep -v grep >/dev/null", destinationPath]; NSTask *task = [NSTask launchedTaskWithLaunchPath:@"/bin/sh" arguments:[NSArray arrayWithObjects:@"-c", script, nil]]; [task waitUntilExit]; // If the task terminated with status 0, it means that the final grep produced 1 or more lines of output. // Which means that the app is already running destinationIsRunning = ([task terminationStatus] == 0); } // Use the new API on 10.6 or higher else { for (NSRunningApplication *runningApplication in [[NSWorkspace sharedWorkspace] runningApplications]) { NSString *executablePath = [[runningApplication executableURL] path]; if ([executablePath hasPrefix:destinationPath]) { destinationIsRunning = YES; break; } } } if (destinationIsRunning) { // Give the running app focus and terminate myself DLog(@"INFO -- Switching to an already running version"); [[NSTask launchedTaskWithLaunchPath:@"/usr/bin/open" arguments:[NSArray arrayWithObject:destinationPath]] waitUntilExit]; exit(0); } else { if (!Trash([applicationsDirectory stringByAppendingPathComponent:bundleName])) goto fail; } } if (!CopyBundle(bundlePath, destinationPath)) { DLog(@"ERROR -- Could not copy myself to %@", destinationPath); goto fail; } } // Trash the original app. It's okay if this fails. // NOTE: This final delete does not work if the source bundle is in a network mounted volume. // Calling rm or file manager's delete method doesn't work either. It's unlikely to happen // but it'd be great if someone could fix this. if (!isLaunchedFromDMG && !Trash(bundlePath)) { DLog(@"WARNING -- Could not delete application after moving it to Applications folder"); } // Relaunch. Relaunch(destinationPath); } else { if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_4) { // Save the alert suppress preference if checked #if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4 if ([[alert suppressionButton] state] == NSOnState) { [[NSUserDefaults standardUserDefaults] setBool:YES forKey:AlertSuppressKey]; } #endif } else { // Always suppress after the first decline on 10.4 since there is no suppression checkbox [[NSUserDefaults standardUserDefaults] setBool:YES forKey:AlertSuppressKey]; } } return; fail: { // Show failure message alert = [[[NSAlert alloc] init] autorelease]; [alert setMessageText:kStrMoveApplicationCouldNotMove]; [alert runModal]; } } #pragma mark - #pragma mark Helper Functions static NSString *PreferredInstallLocation(BOOL *isUserDirectory) { // Return the preferred install location. // Assume that if the user has a ~/Applications folder, they'd prefer their // applications to go there. NSFileManager *fm = [NSFileManager defaultManager]; NSArray *userApplicationsDirs = NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSUserDomainMask, YES); if ([userApplicationsDirs count] > 0) { NSString *userApplicationsDir = [userApplicationsDirs objectAtIndex:0]; BOOL isDirectory; if ([fm fileExistsAtPath:userApplicationsDir isDirectory:&isDirectory] && isDirectory) { // User Applications directory exists. Get the directory contents. NSArray *contents = [fm contentsOfDirectoryAtPath:userApplicationsDir error:NULL]; // Check if there is at least one ".app" inside the directory. for (NSString *contentsPath in contents) { if ([[contentsPath pathExtension] isEqualToString:@"app"]) { if (isUserDirectory) *isUserDirectory = YES; //return [userApplicationsDir stringByResolvingSymlinksAndAliases]; return userApplicationsDir; } } } } // No user Applications directory in use. Return the machine local Applications directory if (isUserDirectory) *isUserDirectory = NO; //return [[NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSLocalDomainMask, YES) lastObject] stringByResolvingSymlinksAndAliases]; return [NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSLocalDomainMask, YES) lastObject]; } static BOOL IsInApplicationsFolder(NSString *path) { // Check all the normal Application directories NSEnumerator *e = [NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSAllDomainsMask, YES) objectEnumerator]; NSString *appDirPath = nil; while ((appDirPath = [e nextObject])) { if ([path hasPrefix:appDirPath]) return YES; } // Also, handle the case that the user has some other Application directory (perhaps on a separate data partition). if ([[path pathComponents] containsObject:@"Applications"]) { return YES; } return NO; } static BOOL IsInDownloadsFolder(NSString *path) { #if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4 // 10.5 or higher has NSDownloadsDirectory if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_4) { NSEnumerator *e = [NSSearchPathForDirectoriesInDomains(NSDownloadsDirectory, NSAllDomainsMask, YES) objectEnumerator]; NSString *downloadsDirPath = nil; while ((downloadsDirPath = [e nextObject])) { if ([path hasPrefix:downloadsDirPath]) return YES; } return NO; } #endif // 10.4 return [[[path stringByDeletingLastPathComponent] lastPathComponent] isEqualToString:@"Downloads"]; } static BOOL IsLaunchedFromDMG() { // Guess if we have launched from a disk image NSString *bundlePath = [[NSBundle mainBundle] bundlePath]; NSFileManager *fm = [NSFileManager defaultManager]; BOOL bundlePathIsWritable = [fm isWritableFileAtPath:bundlePath]; return [bundlePath hasPrefix:@"/Volumes/"] && !bundlePathIsWritable; } static BOOL Trash(NSString *path) { if ([[NSWorkspace sharedWorkspace] performFileOperation:NSWorkspaceRecycleOperation source:[path stringByDeletingLastPathComponent] destination:@"" files:[NSArray arrayWithObject:[path lastPathComponent]] tag:NULL]) { return YES; } else { DLog(@"ERROR -- Could not trash '%@'", path); return NO; } } static BOOL AuthorizedInstall(NSString *srcPath, NSString *dstPath, BOOL *canceled) { if (canceled) *canceled = NO; // Make sure that the destination path is an app bundle. We're essentially running 'sudo rm -rf' // so we really don't want to fuck this up. if (![dstPath hasSuffix:@".app"]) return NO; // Do some more checks if ([[dstPath stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] length] == 0) return NO; if ([[srcPath stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] length] == 0) return NO; int pid, status; AuthorizationRef myAuthorizationRef; // Get the authorization OSStatus err = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &myAuthorizationRef); if (err != errAuthorizationSuccess) return NO; AuthorizationItem myItems = {kAuthorizationRightExecute, 0, NULL, 0}; AuthorizationRights myRights = {1, &myItems}; AuthorizationFlags myFlags = kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights; err = AuthorizationCopyRights(myAuthorizationRef, &myRights, NULL, myFlags, NULL); if (err != errAuthorizationSuccess) { if (err == errAuthorizationCanceled && canceled) *canceled = YES; goto fail; } static OSStatus (*security_AuthorizationExecuteWithPrivileges)(AuthorizationRef authorization, const char *pathToTool, AuthorizationFlags options, char * const *arguments, FILE **communicationsPipe) = NULL; if (!security_AuthorizationExecuteWithPrivileges) { // On 10.7, AuthorizationExecuteWithPrivileges is deprecated. We want to still use it since there's no // good alternative (without requiring code signing). We'll look up the function through dyld and fail // if it is no longer accessible. If Apple removes the function entirely this will fail gracefully. If // they keep the function and throw some sort of exception, this won't fail gracefully, but that's a // risk we'll have to take for now. security_AuthorizationExecuteWithPrivileges = dlsym(RTLD_DEFAULT, "AuthorizationExecuteWithPrivileges"); } if (!security_AuthorizationExecuteWithPrivileges) { goto fail; } // Delete the destination { char *args[] = {"-rf", (char *)[dstPath fileSystemRepresentation], NULL}; err = security_AuthorizationExecuteWithPrivileges(myAuthorizationRef, "/bin/rm", kAuthorizationFlagDefaults, args, NULL); if (err != errAuthorizationSuccess) goto fail; // Wait until it's done pid = wait(&status); if (pid == -1 || !WIFEXITED(status)) goto fail; // We don't care about exit status as the destination most likely does not exist } // Copy { char *args[] = {"-pR", (char *)[srcPath fileSystemRepresentation], (char *)[dstPath fileSystemRepresentation], NULL}; err = security_AuthorizationExecuteWithPrivileges(myAuthorizationRef, "/bin/cp", kAuthorizationFlagDefaults, args, NULL); if (err != errAuthorizationSuccess) goto fail; // Wait until it's done pid = wait(&status); if (pid == -1 || !WIFEXITED(status) || WEXITSTATUS(status)) goto fail; } AuthorizationFree(myAuthorizationRef, kAuthorizationFlagDefaults); return YES; fail: AuthorizationFree(myAuthorizationRef, kAuthorizationFlagDefaults); return NO; } static BOOL CopyBundle(NSString *srcPath, NSString *dstPath) { NSFileManager *fm = [NSFileManager defaultManager]; #if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4 // 10.5 or higher if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_4) { NSError *error = nil; if ([fm copyItemAtPath:srcPath toPath:dstPath error:&error]) { return YES; } else { DLog(@"ERROR -- Could not copy '%@' to '%@' (%@)", srcPath, dstPath, error); } } #endif #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4 if ([fm copyPath:srcPath toPath:dstPath handler:nil]) { return YES; } else { DLog(@"ERROR -- Could not copy '%@' to '%@'", srcPath, dstPath); } #endif return NO; } static void Relaunch(NSString *destinationPath) { // The shell script waits until the original app process terminates. // This is done so that the relaunched app opens as the front-most app. int pid = [[NSProcessInfo processInfo] processIdentifier]; // Command run just before running open /final/path NSString *preOpenCmd = @""; // OS X >=10.5: // Before we launch the new app, clear xattr:com.apple.quarantine to avoid // duplicate "scary file from the internet" dialog. #if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4 if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_5) { // Add the -r flag on 10.6 preOpenCmd = [NSString stringWithFormat:@"/usr/bin/xattr -d -r com.apple.quarantine '%@';", destinationPath]; } else if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_4) { preOpenCmd = [NSString stringWithFormat:@"/usr/bin/xattr -d com.apple.quarantine '%@';", destinationPath]; } #endif NSString *script = [NSString stringWithFormat:@"(while [ `ps -p %d | wc -l` -gt 1 ]; do sleep 0.1; done; %@ open '%@') &", pid, preOpenCmd, destinationPath]; [NSTask launchedTaskWithLaunchPath:@"/bin/sh" arguments:[NSArray arrayWithObjects:@"-c", script, nil]]; // Launched from within a DMG? -- unmount (if no files are open after 5 seconds, // otherwise leave it mounted). if (IsLaunchedFromDMG()) { script = [NSString stringWithFormat:@"(sleep 5 && hdiutil detach '%@') &", [[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent]]; [NSTask launchedTaskWithLaunchPath:@"/bin/sh" arguments:[NSArray arrayWithObjects:@"-c", script, nil]]; } exit(0); } nzbget-12.0+dfsg/osx/PreferencesDialog.h000066400000000000000000000023511226450633000202110ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 807 $ * $Date: 2013-08-31 23:14:39 +0200 (Sat, 31 Aug 2013) $ * */ #import @interface PreferencesDialog : NSWindowController { IBOutlet NSButton *autostartButton; IBOutlet NSButton *showStatusIconButton; IBOutlet NSTextField *generalText; IBOutlet NSTextField *appearanceText; IBOutlet NSButton *autoShowWebUI; } - (IBAction)autostartButtonClicked:(id)sender; @end nzbget-12.0+dfsg/osx/PreferencesDialog.m000066400000000000000000000104761226450633000202250ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 807 $ * $Date: 2013-08-31 23:14:39 +0200 (Sat, 31 Aug 2013) $ * */ #import "PreferencesDialog.h" @implementation PreferencesDialog - (id)init { return [super initWithWindowNibName:@"PreferencesDialog"]; } - (void)windowDidLoad { } - (void)enableLoginItemWithLoginItemsReference:(LSSharedFileListRef )theLoginItemsRefs ForPath:(NSString *)appPath { // We call LSSharedFileListInsertItemURL to insert the item at the bottom of Login Items list. CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:appPath]; LSSharedFileListItemRef item = LSSharedFileListInsertItemURL(theLoginItemsRefs, kLSSharedFileListItemLast, NULL, NULL, url, NULL, NULL); if (item) CFRelease(item); } - (void)disableLoginItemWithLoginItemsReference:(LSSharedFileListRef )theLoginItemsRefs ForPath:(NSString *)appPath { UInt32 seedValue; CFURLRef thePath; // We're going to grab the contents of the shared file list (LSSharedFileListItemRef objects) // and pop it in an array so we can iterate through it to find our item. CFArrayRef loginItemsArray = LSSharedFileListCopySnapshot(theLoginItemsRefs, &seedValue); for (id item in (__bridge NSArray *)loginItemsArray) { LSSharedFileListItemRef itemRef = (__bridge LSSharedFileListItemRef)item; if (LSSharedFileListItemResolve(itemRef, 0, (CFURLRef*) &thePath, NULL) == noErr) { if ([[(__bridge NSURL *)thePath path] hasPrefix:appPath]) { LSSharedFileListItemRemove(theLoginItemsRefs, itemRef); // Deleting the item } // Docs for LSSharedFileListItemResolve say we're responsible // for releasing the CFURLRef that is returned CFRelease(thePath); } } CFRelease(loginItemsArray); } - (BOOL)loginItemExistsWithLoginItemReference:(LSSharedFileListRef)theLoginItemsRefs ForPath:(NSString *)appPath { BOOL found = NO; UInt32 seedValue; CFURLRef thePath; // We're going to grab the contents of the shared file list (LSSharedFileListItemRef objects) // and pop it in an array so we can iterate through it to find our item. CFArrayRef loginItemsArray = LSSharedFileListCopySnapshot(theLoginItemsRefs, &seedValue); for (id item in (__bridge NSArray *)loginItemsArray) { LSSharedFileListItemRef itemRef = (__bridge LSSharedFileListItemRef)item; if (LSSharedFileListItemResolve(itemRef, 0, (CFURLRef*) &thePath, NULL) == noErr) { if ([[(__bridge NSURL *)thePath path] hasPrefix:appPath]) { found = YES; break; } // Docs for LSSharedFileListItemResolve say we're responsible // for releasing the CFURLRef that is returned CFRelease(thePath); } } CFRelease(loginItemsArray); return found; } - (void)awakeFromNib { // This will retrieve the path for the application // For example, /Applications/test.app NSString * appPath = [[NSBundle mainBundle] bundlePath]; LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL); if ([self loginItemExistsWithLoginItemReference:loginItems ForPath:appPath]) { [autostartButton setState:NSOnState]; } CFRelease(loginItems); } - (IBAction)autostartButtonClicked:(id)sender { NSString * appPath = [[NSBundle mainBundle] bundlePath]; // Create a reference to the shared file list. LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL); if (loginItems) { if ([sender state] == NSOnState) [self enableLoginItemWithLoginItemsReference:loginItems ForPath:appPath]; else [self disableLoginItemWithLoginItemsReference:loginItems ForPath:appPath]; } CFRelease(loginItems); } @end nzbget-12.0+dfsg/osx/PreferencesDialog.xib000066400000000000000000000636401226450633000205540ustar00rootroot00000000000000 1070 11G63 3084 1138.51 569.00 com.apple.InterfaceBuilder.CocoaPlugin 3084 YES NSButton NSButtonCell NSCustomObject NSImageCell NSImageView NSTextField NSTextFieldCell NSUserDefaultsController NSView NSWindowTemplate YES com.apple.InterfaceBuilder.CocoaPlugin PluginDependencyRecalculationVersion YES PreferencesDialog FirstResponder NSApplication 3 2 {{196, 436}, {357, 155}} 544735232 NZBGet Preferences NSWindow 256 YES 268 {{67, 119}, {213, 18}} YES 67239424 0 Start at login LucidaGrande 13 1044 1211912703 2 NSImage NSSwitch NSSwitch 200 25 268 {{67, 99}, {213, 18}} YES 67239424 0 Show in menubar 1211912703 2 200 25 268 {{67, 79}, {213, 18}} YES 67239424 0 Show Web-Interface on start 1211912703 2 200 25 268 YES YES Apple PDF pasteboard type Apple PICT pasteboard type Apple PNG pasteboard type NSFilenamesPboardType NeXT Encapsulated PostScript v1.2 pasteboard type NeXT TIFF v4.0 pasteboard type {{20, 30}, {32, 32}} 2 YES 134348288 33554432 NSImage NSInfo 0 3 0 NO YES 268 {{66, 20}, {244, 42}} _NS:1535 YES 67108864 4325376 Only OSX-specific options are located here. For all other options see Settings page in Web-Interface. LucidaGrande 11 3100 _NS:1535 6 System controlColor 3 MC42NjY2NjY2NjY3AA 6 System controlTextColor 3 MAA {357, 155} {{0, 0}, {1440, 878}} {10000000000000, 10000000000000} YES YES YES window 11 autostartButtonClicked: 24 autostartButton 25 showStatusIconButton 28 autoShowWebUI 32 delegate 12 value: values.ShowInMenubar value: values.ShowInMenubar value values.ShowInMenubar NSValidatesImmediately 2 23 value: values.AutoStartWebUI value: values.AutoStartWebUI value values.AutoStartWebUI NSValidatesImmediately 2 34 YES 0 YES -2 File's Owner -1 First Responder -3 Application 1 YES 2 YES 3 YES 4 8 YES 9 14 29 YES 30 35 YES 36 37 YES 38 YES YES -1.IBPluginDependency -2.IBPluginDependency -3.IBPluginDependency 1.IBPluginDependency 1.IBWindowTemplateEditedContentRect 1.NSWindowTemplate.visibleAtLaunch 14.IBPluginDependency 2.IBPluginDependency 2.IBUserGuides 29.IBPluginDependency 3.IBPluginDependency 30.IBPluginDependency 35.IBAttributePlaceholdersKey 35.IBPluginDependency 36.IBPluginDependency 37.IBPluginDependency 38.IBPluginDependency 4.IBPluginDependency 8.IBPluginDependency 9.IBPluginDependency YES com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{322, 782}, {298, 74}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin YES com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin AccessibilityDescription AccessibilityDescription information com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin YES YES 38 YES PreferencesDialog NSWindowController autostartButtonClicked: id autostartButtonClicked: autostartButtonClicked: id YES YES appearanceText autoShowWebUI autostartButton generalText showStatusIconButton YES NSTextField NSButton NSButton NSTextField NSButton YES YES appearanceText autoShowWebUI autostartButton generalText showStatusIconButton YES appearanceText NSTextField autoShowWebUI NSButton autostartButton NSButton generalText NSTextField showStatusIconButton NSButton IBProjectSource ./Classes/PreferencesDialog.h 0 IBCocoaFramework com.apple.InterfaceBuilder.CocoaPlugin.macosx com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 YES 3 YES YES NSInfo NSSwitch YES {32, 32} {15, 15} nzbget-12.0+dfsg/osx/RPC.h000066400000000000000000000022421226450633000152530ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 807 $ * $Date: 2013-08-31 23:14:39 +0200 (Sat, 31 Aug 2013) $ * */ #import #import "WebClient.h" @interface RPC : WebClient { } - (id)initWithMethod:(NSString*)method receiver:(id)receiver success:(SEL)successCallback failure:(SEL)failureCallback; + (void)setRpcUrl:(NSString*)url; @end nzbget-12.0+dfsg/osx/RPC.m000066400000000000000000000034761226450633000152720ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 807 $ * $Date: 2013-08-31 23:14:39 +0200 (Sat, 31 Aug 2013) $ * */ #import #import "RPC.h" @implementation RPC NSString* rpcUrl; - (id)initWithMethod:(NSString*)method receiver:(id)receiver success:(SEL)successCallback failure:(SEL)failureCallback { NSString* urlStr = [rpcUrl stringByAppendingString:method]; self = [super initWithURLString:urlStr receiver:receiver success:successCallback failure:failureCallback]; return self; } + (void)setRpcUrl:(NSString*)url { rpcUrl = url; } - (void)success { NSError *error = nil; id dataObj = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; if (error || ![dataObj isKindOfClass:[NSDictionary class]]) { /* JSON was malformed, act appropriately here */ failureCode = 999; [self failure]; } id result = [dataObj valueForKey:@"result"]; SuppressPerformSelectorLeakWarning([_receiver performSelector:_successCallback withObject:result];); } @end nzbget-12.0+dfsg/osx/Resources/000077500000000000000000000000001226450633000164305ustar00rootroot00000000000000nzbget-12.0+dfsg/osx/Resources/Credits.rtf000066400000000000000000000012651226450633000205460ustar00rootroot00000000000000{\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf510 {\fonttbl\f0\fnil\fcharset0 LucidaGrande;} {\colortbl;\red255\green255\blue255;\red0\green0\blue255;} \vieww10800\viewh8400\viewkind0 \deftab720 \pard\pardeftab720\sa260\qc \f0\fs22 \cf0 Lightweight binary newsgrabber\ \pard\pardeftab720\qc \cf0 NZBGet is free software; use it under the\ terms of the {\field{\*\fldinst{HYPERLINK "http://www.gnu.org/licenses/gpl-2.0.html"}}{\fldrslt GNU General Public License}}.\ \ The package also includes other software;\ see folder "licenses" inside the package.\ \ \pard\pardeftab720\qc {\field{\*\fldinst{HYPERLINK "http://nzbget.sourceforge.net"}}{\fldrslt \cf2 \ul \ulc2 nzbget.sourceforge.net}}}nzbget-12.0+dfsg/osx/Resources/Images/000077500000000000000000000000001226450633000176355ustar00rootroot00000000000000nzbget-12.0+dfsg/osx/Resources/Images/mainicon.icns000066400000000000000000010407431226450633000223210ustar00rootroot00000000000000icnsAãTOC Xic08¹\ic13¹\ic12ic07Elil32²l8mkic11 is32«s8mkic14WÂic08¹\‰PNG  IHDR\r¨f pHYs  šœ@IDATxì½ ˜]Gu.Z=iènI-ɲ&[²%Y°-Ûxc3™ÁŽÁ f „!Ü ¼÷€{¹N $“ä~77<&“„›Ç„|Æ vlƒ‡`06¶°­ÁÖ<ÏRïÿת¿víÝçt·¤îÖ‘ÝÕ½÷ªZµjÕZ«j­ª½Ï>û„0‘&,0a LX`ÂxúY éé§òÓRã#ç§¥µžFJéÄx™¨!UmÔq› 9]ê Õ¨©¾ÄO¯šáƇõU§®ÍÑZ°VU\µáê«ôåq²ÀXO–qRã)ÕÍPc2T]ÕÃÑVë‡sÒáêóþ‡¢ª.ç1‘ T'Á8t9ÑEfzö¯‡gÓjÝá–³îG”­:ìá–óNªmUW¯ú 8F¨Nž1êf‚mfz6¯…Ïqµò#űûœ6§n6wÊZù‘âØAN«káêѪÍe î¤åîŸìêÙ¸^¸zc]^¯|­:á4ÉQs¨Ñ¬bª=«e’ËIÅBeAâéØÕ²œ}8ÈvLy{ἦþY˜;¨prú¡ êÔ¾Z^¨,(¼ ñyª–óº‰üaX`¤â0X>íHkÙ°Šc9?d$:±™õÌ«m^×ñ9ÍPyÕ¡™%–uÁ|­$Ç’#ªLZ:qŽÏËÃåYσ‰Ä3ŸÄ —j9¡œ–u¹Óö¡œã˜¯…SñÉ!óL©â”'ÌÓpåœv"_Çœiä¨Ú«V¹êlr^áU–ÃÖƒtlÖÉéU®GO<û¨BÉH<“Ê‚ŽuGc^Ž%§”Ê9sÈ|½CA€4‚õè‰g_‚y¿@—d$ži¸²SMœY : L ’r[åy°\ë¨:cî̤gYŽÃVàÙ–G=|µ^}‘>—…x¦Ç|­$§$MÕ!åØräZŽÝ‹v ’¾žõ<ØgªoAÉ•C4K‰x¥ýâËŸsùsgΜ¹€‰Ôd+9“SV˱¼Ø–öBT_éÙÌÝ ªÏJbÝÏ­TSÙ‘ƒyËÆ%ÛJ$†ÀkÇAúÃv\EÜvûm·¿÷½ïý·'Ÿ|r(éü ‚ * *8(ä¢(hw@Aò€À2“‰íÙR>¢ž~ÀÇïé§w®qnƒjžåzŽO¼œ°–ÃËÉåø >ç9Ï™óŽw¼ã™H+,\°´«kæú5®ÈÝuн¹ËJI8}c •G ­N"+zŽ. c–äD"$G!“7²šÒ)R9+«Ø”í¬.ʘsñxæ4¸¯°å‰'žXuï½÷þòoÿöo~×]wmA ½Þ¡0T@ÐLšzIc9Ò§IUw• 󃎞¯øtv–éܹã˹‰Ÿë qSp³®óýïÿ¯yÍk®ÃM»Eîq³ŽiëN„)¡ŽHJ“CÕÇæÄ‘ÆƒåsófbÄR–·,NZɳ*uG¶Lòfw*ÄâðèLpß  Tƒ'uÛ²yË_ýêWÿõÓŸþôâf㾃³n Ì3 ¯ÀÀó Õ{έL¨Äú§]*Æêé¥z®wž—£WËñó?wxåéðrzƒÓ§OŸú7ó7/{ö³Ÿ})îÌŸÄwnjÌ7ÿ/,Ï)/€R Eµç|ž–C$±æ‰<+Y ¶Z“„—î vNFƈԤÄĪ’ *ɪx©‚¿&‘*êM»¬’ц7·lÙ²þλî¼ëï|ç÷öìÙs5 9T^;„üÒ@y*ªó*#k‰e¥û¹ï|ï{ß{býúõ¼‰È]@Á€;í òA­ Àî”êåUÿ”„Åø?%Õ+)•몑Ú¨S’+©©uƒ“n`ŠVtÞ8[öYpQ¼´p~ý{öîÝõ³ŸÝý/¿öÚÄs º<T`YA€8êåÁP ZÙ$ÍóIô§Z¦°üSM³BŸ\Ç?´+T ¬µ# Ä ¦ ËL‚^z ÝÖO!…¢*¹^Ê ÊñéèÄÉéµâç«}rzÐOþó?ÿóg_wÝ+¯Y¼hÑYœšçæùTAž[v x°«ªÇ i¶ÑùýÎ:D È—FT3œ»¥µ-ða¿xàÚµk>O7_·n]ذaCØ´‰Ž¾9lݺ5à1^жx@ÀN€ýVÔû¯þˆpèëëxœ8à±c sçÎ ø4#,\¸bAÀýŽ€§7&xJ0ô÷ ÄÔüìו¡= #P\ß-`#]ı¶S,Sv'Ò”V¯^õð׿öõï~øÿð§(æA@ù|WÀrfž€ÉzË`Žcþ)‘2k?%ô¡¹NÊòÈŸù|«Ï k|­ö¼coÎgšzÕU/^øå/}ñ;:;go‰nÊIî96˜¤©‚µîÐÌÉ©™Oó MüR(Ì0+àP½aVðÛo¿-ÜqÇÝïÜ¡Õ8žò€A9V¬X.»ì²pùå—‡¹sO x2iˆ³B–ŠUDšËÞíht–ÎQ1 öîÙ³ãõoxßþð‡?\‡àÅï'ðP`>ß 0¯ËB:¿. ØCu7@œRže›·j$Á¥ +äôÄå«~¾âËùífè´â[Àg÷+Þõ®w_¿hÑÉg“¡­ ˜‰É¿1%|VÄ {B%q<$L9 x¥ÂWÃÖ¶6lÉÛž£ ?»ûîpÏ=÷Ø*Ï~ÇŽæP\ÕéX–¸cÀWìËEx’ÑvÜ\tÑEáâ‹/Æ¥ÄâÐÝÝp/Ú$·Œ;»Ç½h-³!ã ¬§|ÖUù½ZzÕêÇýÙ¿üì7nºé¦a#~ŒÈ – èüùŽ€ev ÝóÚ ku9dþ¸N…uk5’Q‹\'zŠŸù|Õç6_«=aZñ‘ŸŠ/Ýœ€'ÕÞ~Î9ç\FGåêÃÁ³ÁxâºïÛx«Ò 1€¢¨ps+;“”Ž¿×ë;wí ?øþÂ-ß¾%¬ÁöŸ(|smï„ošÓó2ášk® /|á ÂŒ]aÚ´in»¡«DG÷Á£U`3˜c2K‚†xa-—ˆHK€]Ò]o{ÛÛ¾øÐCmEBíjíj41aÛäÖ=nÅ7Ás”'”ã+T?w~®øtþtüèG?zûùçŸÿ<~ÖWzy-FݲE™óÀQ8'§/fGuåÇ×räI“ÿü˷¿þë¿®ð¼AÇÔ¶ÍÙ¤6 æ$íúrËÈZž‡6¾r–ó©rÐÜ%püðò—_‹ãáv½8,™îÆ]ß.ªyÓJÏ à[…BâbIרq¸ûî»o¿êª«þÕ Õ#ß èþ@5È*‚ì‰*åy᎘ìuÜH\Tò ²–y9?^+?—SµnîÉñ§Þpà çáx{GÇ ç8¼œÀdìþàT×Zß5-¬Iq2²6¬æ{÷ï k±½ÿö·o ßüço®Ž¼ ŸosÑŧ¦yP~>˜ ‡àÁ;m{›&j- Â*üÔ€‰7Ó½ñ8ýè¶6B»í ÙÆ1°ÇFkvT'Ú“ŸBp—ƒOSÂÕW_á2¡7{q3Ñl)ÇŽæf‡”X7½ Œn  4‹9§Ý¿oß®ýÉŸ|îÓŸúÔý¨Ðn@Á€A@€0ß p¸uI 5â w\¥hªãJf ›ËÎ¼Žªókå§ hÕϯñéüSyà…ÿ-ÁÇhøHÚ<¸8{du®@1‰Î#¾¨E$Ї/뇿üËφo}ë[6é¹Ò±Ó·CÑiè¼޾Ç©8aÒOÏÍ!éÆìF@X‹cŽÇ ñ^X‚Ïêí?ü>iOî ¦L™®ÅŽà=ïzWèëï³QôøäÔämãåAÖW nœ£90ð1f?^b²ú´ÓNûc (%/ xÔ ùn@ 8ÌùPçyT)šéø6J™Ë¬^7ù´â¶ãYý—¼úÕ¯~s[Û$¹4Ç,Í©³áu',;ãr`ת€š l7sý}÷ݾsëwÂw¿÷]”°íg÷‡ŸšÏ‚“Ÿ×ZNG¯”a¬‰+{¥؆nwê{AáþþÐÿmÄR2(öööX0xÑ‹^^ú’‡óÎ?ßv*ü8’ =ihcPaÛbշ΀$•OÒK65==¾ò_ùê{ÞýžC‘€Á@A€0ß p?D­èüÌsÈu°)(TÂ1ßð©±fÑðæÊåUž0_õ™¯®úZñ ¹ÚÛªúòÓgßúÝ[?Ç §í0¬¢š; ¦|ó¿ïßñ‘áô£"q@©sþD£=Ù7æ½’SO=5¼èE/ ¯|å«`ïÃÑ›m"Xc0(í€ôƒªsz;|íxï¾ðw_¼á†ß»­ù’îòAþI_€JP1)ãJ”çÓ€g·D V)—Sy9=IµÝ§óç×ú r|®üí¸ËÞþãÿøÝç_pÁóm3Éa‘¯ïÀ˜sûêÇ‘=k"“†‹Êíáœoü¶®xö~¤ŸÑCš– @>žð;ª<ýRß#Øü| ôý'Œ‹u—O;Ò¦ öiÜpØ À®´=Ÿ6„ÆK.¹Äî”ÇÃí™cÞ/!l¸†ÕÜ9Ü{÷=·½ðE/ü[Ü&Ø R]êÒ€@A@— Ü01—A ZÊ”hÔS.£òôæk­ú Õ-¿9ÿË^ö²S>ûÙÏþþìÙ³ºÓ*ûDr/÷ÅÊÇ3.\$÷„;朔ø¶_ÀK,Â7¾þ <¬ó®û[ñŒ¼æ€ˆ+Ò"$µ½¦%´\üôtúŠER±ïì ¾ÛöpãŸ=ºÌ€¿fz+ò ý¸Àï)¼üÚß ¯|Õ«ì±ç@§I’õ|ú´þo´¼ÂÛ¶uÛz¼ƒà“?øÁÖ€œ»€üF¡vµ. ´àÑHÓ¸òHøÄ'?ð¢K|Œ7É>Fãsþ 9/òSj¹[üK1µO’ª™€¹pŸ`à§°ÑtXpxø=ò„–±Ì/<ñÉÂ%K–¼w!,[º 4‘S̤ÀfôrÕ##Zäž8|Zó¹~ðƒº$¨î¸àê¯ƒŽŸ³!¥<ŸI&ia*2H6AVçŽÏ¼œ?ÿxO[~^ë[ÀóóïÅÓ|WÈ99dÊ->qœ„6JÅÉê…äÄäðû¿÷{vßÝÍ ;<p±ö䑵Øæ_ØZ¯ÅŠ6)WÚNœ†°À]ìVŒÌýpü¾# Ó3бñÃ9¼ß²xÑ)á#ý(>ii³Žl„ãŠÏK=ޱ%ó|¶å@9ÎêRuS¸ÿþûï¸âŠ+>‚|'P½7 K‚¡‚»´‘Aâ&EµF "¹‰ÏŸ×ú<¸êkå·»û(›ãã:Þ—¿ô¥ž´ð  Wp25wCãÁƒ€Ý@=«¹êtã•[¾ýíð¾ðÃqâ/çÄ@¾ü`Û›ñÌþ™˜Zí¹ Ævât8à¼Aàk­¡Ånò)À¶¼ß‚ñ±ñoxÃífáä)“äâ¨dÀP¿È”v±.Q þ‰'ŸXù†×¿þÓ<ð«MhÆ„ù%Aþq¡vÕ›ƒÜ(5lH6‘¤ s™”¯åüùÍ>­úÚò·_zé¥'ýË·þåm“'Mµçô©˜VyË&ïw§¶¢ã<8 kLŒ'žXþû>€oäá‹.tn®öøãªÏM_¿­úÈöÏÂ×`Ÿ/õ¼ˆâN¤Ñ¶@óqïä¾Öм#~ÓŽ‹Ãob¸ìre„/e­Gk‚ü!·ƒùÍA:¿v'³¥¼!Žå‰«h#¥l˜ÒUŸ«~uË/ÇǦ;t~à¸ð3ŸùÌÇðà žã÷Y«3íÏÑ`P0çå€3œÑ²[ÍÜmþü_ßþú¯þÚè€övlÏÉcåb\›_‡-Ék±5=mÂùi«±H§`œ.ÆB‹@Ûòp«90ÇŠŽ¬`øñ~¶ïØpé‡€àŸ P&sz?Aâ|O¨ñ%‘üx·íµ¯}íø¸p-¾S€¢ÓœDvÄɦ[FMî “)ä†Q¾–ókÛ¯Ïöy­ÏÜÿ#ùÈeÿã†>ÔÒÚÒf‹ :,›ƒßEa8Ž‚œÞóv«_>ø¡ß¬|ÄÚðrцM Ò°ŒÙ€ |O°ù¿âRa1&!ïVM¤±µfÄÀüþÐ{YwhêCÀÝŒÐÏŸ` °à`$l,ðÛù†3Ï8#LŸ¯v€ˆãÍ—˹[G|¬7ò-x Ë•Ï{Þeà»÷’ðƒS“RuÀ‰×åg›.¢Ú.«ßl£’Ë¡üpί§úRÀ³öoÆÍ›WÒ;9À6È4½•iXfˆðzŽŸ&1\M¾øÅ/…ïàa£­ˆ ò£>î&ú—àÞÏ[áÅÇD:vÀ}ùöì ­k'Ù½Û ðF!nñ¹&â^€¯!_ÿš×bì°ƒ`´° Îå™ã܈˜Hã4øÇG„·¼öú×þˆx9P½$¨õ >"lØËFÙÄQÐhÔ¼á—oûsç·•ÿ›ßüæ›®¼òŠWѿݩeáàÄÓ­‘JÏî7‡-Û¶…?úÃ? ¿¸ÿ~›0”D´œ–r`^yœÿ*`¸™HÇÖƒžø:ñÉøÁfÜ(܇ûœ>ñÓÞ dXµjUøÙÏ~Vœž½‘ó"MºèìUE¼Þ©—.Y²üüóΟôOÿôO+A«æjæSÄKÌç圦ÚNuÇ6‚0’A+?ó NÌW¯ù«Îßñï?üáÿ}Þyç=_§£»—ÃqÉ ñ×^§ ¼WEJò--°er¸í6êxÃË=/ú[úBï彡ÿüÔg"5ª¦ÞÞ¦þ¬#4ã²À&’>)ànãˆG¿¾øp“8ôáÆ.C;„% éãC b3Ì’ôž{ï½í%/yÉçQ䓃ùn€ŸYä;Þä„ျN<æ™â$Lбã|>Ö;™WPÎOHÙèü<ªó¥k~¼îÿ‚ó¿€ötÇG24KG¤v6àˆ „|~ÿ+_ùrøò—¿ŒåÍ$4²džA¸¾SûBÏ[…ghìD7ͽ‹{¡³†–­­¡u—Ã. èÀýó»ï½7

jeüZø¨Ùë¾®Þ°é¾Ð¾¦#´ôðS ³Þvá§ÓN;m¹=TDAí;œ\œ±œ^Hé¦ ¯b:b2­»ó®»ø°Pœˆƒ ©U[Š«õ@\ÁTcÇ3H±Òñsç×gýºæ×M¿vܬ¹ø~È „) 3ÊÁ-çéŽwú?sÓMáÁ´vÐÜ@víYx(ì|Ûö0Ðɱ9¾Óh9ûH¬p<„þöþ°gÅ®0õÉöжÏ/ ô5c^àUaáÑG ç­8Ï ÚÓfNîï̸u|? @á£Å‹ñâÇqs¿EÀŠü`£¼¬<§#óLšš‚ŽÃó± Õ•_[ÿüºŸ+Þ·èŸøÄÇðÆ6:5“ݬól2«®ýÍœ0!¿Å÷×7ßžÀm¤¨JY›™}í {®ÝU˜^<˜iR_ZN¥Ñ<ê÷d5ÇEP€=öž¹'4õ6‡öMS17°ˆ÷ø“fü¡–U«W…³Î: ¿ÇˆïäF> œ£i\$¸™xÖ¥Ï~æí·ß~÷† øe!µÌïú'|lIplÒx€h%YÏV}­ü”!wþÒƒ>øŒ>n°|Ú¿ØC#aÚ›ùÜŽ…Óû—{hZ:Æ¡ƒ‡Â'ñÒ¼ šcm,X[¿Á³çÊ]aÿ¥¼q{|¥ºŽO+WÑVí0ø7z08pòþÐ7©/´¯ï€Ùâ׊1+öà”y_àüóγ'EÍŒqËo3Q¶à„C²,`+"Æ«^óêç~ÿûß¿kË–-ü@ÎO¨¼±@™©š+A§£óx)"¨m?¡îøç×ýùM¿Ž[o½õººfÌ7§ÇÉÌ-Hë)oDàÃV|“ïïþîïÂ&ü,¶9n<2€$Û^µ%<“n5v:bg—µ GûÈMVwNóÍ=Ì;ºéfÂó”“A ‹ÈZ|üì³ÏF™X›}VÏE&¦zö¤)!ŽÖ––¶ç=ÿùËþæóŸ¿+¢ˆÎ&gX@áÄZЈÇâ4^@Š*°oú¼_×ý ¶õÇ—/ÞW>]H3ÑÜÇÝvÌ{Ùmè7óð¡þÍŸÿ¼½§Ï¼_=“ù¾É}aËk6…ž…|>£±Ó ç§.:ꉞ×3?I}Öê£N]#Þé=aï¢=aúê®ÐÜÇ/!ÐáqY°w7.ð=~­ØætõÇÊãŸ~6,q&š5f̘>û…/|Ñ\<-È_#ÒÊ/HRå#'kÎÓX^ꀙ±RBÓP+¿œ?¿ãŸVÿ?û³?{!^ýZ ©m~= X`€ÙìÛü€|5Ô·þùŸÃ#‡ÌZbg£(<“ V~BöWoëßÁëþ«^tÕëéåœH„ …„Dص<¬jufl„‚þ^¼­÷ÿÄ`À¡Šõ-æÖ_ó$emŒ¥saòa–x²Z^Îp¦»Úex‘ÌëëÑ”Œ 0ž¢‰ìX”¼tžbJĬáˆGª´uTN`TãvêÅ=}'î ³VÏN«?þŠñã¸)xÞyøÑRWʔԼ3‰£Òº"ãôåw:§un¹í'·é~€VùR?›ò™¢ä&CfÕG—«€¤¢À:ù´úOY¶lÙì×¼æú·ÓA|¥÷jÁÀjÌùr@çÿû¿ÿÿðÝn88záxèÃvºõٛÅübÖ±OÔ‹–d‰¥2!Si¥-Q—¯´NHÚGªÌ2Ïâ^äIxò©UŽøŒ•ekõ›äg›²¼¦yâŸ1¡²²l–ì&šq‚{çí ë.x"™˜Œ‡ _ÿú×솠/V\¨ !ç)“—ß.Åw–€ÓßôÆÅ‹ÏBY;€ê.€¾ÁCV$ æG=v9d>w~=ô“€ö¯þã?~ïrïì§“ËúŠ.›œŸN†¸.û÷ÿQغe+ÞúŠaáÓ\èÉžèBÝös¶†çìuƒ C›j(ËÔ-kƸži*ˆ^|ª¼TG¨zƒO'!²Š«UÎèÄÇ s©y®UqìÁœ òTmÇr¤-glÏØàó‹ò@.¾e˜o¾ãŽ;1[Üé5?lþrÊ2@€;ØÌO™<¥?'÷ßÁE¸3è2@— ô™ªU€rÔYX‰fÒ°JöA§¯»õÇ{W_xá3¯‚,àte@ð¹J#’-Öxxú¯ø•}¹§ßïW;6&Åþ¹ûÂúç­cñ˜¦AŽŸ[‡ùT†Ž±lº«NõÔ"Ç•ðl „­®¤Ãfy‘Ÿåq¢ùHªDžÄ?£%RÁꛤ'ej¬2!’ñN§2 ²ƒ bâ*<žiÏüÝaú¦éaÊA,ØÚo6‡õë×ámÃÓà ø*1'!Ÿ¢Ù|Ô˜Yˆ„ÄÓ§OŸ9÷Ĺ{ðÅ´5Àäïà„W™ê±…´´<ñ£šFs@!™rHþ:éxpûÃcê5W_ýfÍÓòöÙŸÖŒw»ví ?Â{ß§LEsÌfÝücÏÝÝaÍËVí±Mƒœ_âÈ:©Ì1.F:Y/Õ#SmÃ:s>å<ÜùéärtËGR§ôBž§ªžP¼(‹ãu¶Zof29>çcòWuˆò¥Ö@²V5èU[²­c ~þCáPû! ZºöŸÚljù¬ÜÔ*b«m¸Sód »éá:P_Ó9ê`–¢cÛÅôÖ.¢¬òöìi•H«ÛÅÙ"ìØBô¹ò¢‡ÃþN~“”—ž>^œ‹xQ­_–ÅyË·ÅÌÜRO«ð2ŽuííÓ¿ô¥/¿H]ëúŠüF~D(K![ʳ|D‰L&åÃA>|u#ç·m?ðSÏ<óÌ9øŠåóÌÙ5>ãœ⬒Cë×U·ßv{8Ô›0Œ+ˆ¸YQâS[+_ük7”`¥ãŸ¥Ü•¼;¨r|UN›ä¤ä&’;§ÜË–­¶m rS[¦dÔâ™(û¸Å2+2rKå¼®^°F~Jc‘áÆ$ ¹¸üç˜{ôl}}¸ÉîKÝ}ÏÝеz/ÀË~Ð¥Äí@›ëçŸÞex#¤ À`@_Ƀ@~/ · ÈJcù°ÒÑ€¼3 F~<˜§Ð<ò•ß¶ÿÿ‹¿x;oüqgƒ¢9<·I…ãsÛd®vìØVþf%˜‚-¸“9#.'Ó†s×ÙW|×TšlˆÉó¬­ø˜´FWÂWéXŽnntÈ#§1¾bKšN¶C‘’ÓÊ^0½R9jIýe’¥zÏk7à»³Ê çN›Åz!ÆöNê «O_m÷Ÿ(ŒÍMœüñ°s§_¢ÙèûbÏ™n’Ž“žs ÝäÉ“¦|üã*yídþ˜" ?¢UrÿBñèm¢`L‚ä)¡µú§kÿß}Ç;V,?ãŒËü Ú¦áóÍñ™ÇÑÛÝð£ŸäïNÙ`ðþ=sw‡Mgn´ºñ<¹”±Gi-˜ b¨ŸçIg“>N Ô™ Ôž´µèµ„Š®Á _¢¸Nƒå/ë”_˜²y ^¹þÑ>¦nŸp_«›Ó“ËÖ†í'ìð €¾õ|À¿ýÛ÷m7 O¶l>ÃÙí™íx-0p޾zì’7½éMgBJ­þ ôíäOòWY@ðˆÃ#aÀÎ%!yñ`žëFÑl2¾é7õM¿ýæëyÑïÎî[#–i }äg7ùÀé!Ü\¡gèmÆxþjÏê G7ã› ¡wH-y0 z ePg<á”!=r><¢Ëùmlc,bÿ–o¼“]P~Ó!êiåLîToCk´¼Ö7ŠÌ6¦]FKžn+òÍtÏóÆ)#‹ìÊs½˜‹v? rðž¿­Ê‹2Vû¼Žóû-n¶ÛÅâgAóþ­¿ó;¯€ðcAùüˆ>EMs?£JÄñ8¢t¤ Ú¡Êä§hE¨(F8?ݽpáÂùgûàÐP~Præ­dÞø»¿ãFõ|‹«òØpúúp¨“ߢ¿ärWú“æDÇÉ[rhÃgm4Ñ2:µ'TÞ"^>ÉÍ(N¯ 3–”mÂcÙL&1²ÔÑí†ÌPÎ6Åæ&Ò§Á'·/íãv1 4)AjŽW$-p°ý@xbÉšÄÎIGé<©ÚË弿­‚9¿ýàÜ'ÞƒÃI'Ÿ|Æ%—\2˜ü2@—ϹOÉg¥1šXª–…ŠÙDÃT²cäE(a)¼0¥ðfß?fij‡|´ 0ÃÐn7˜o—¾÷Ýïù[Y83ø×Þ°îO€ýø¥Òd¢–L‚–wŠºÎOÚHOuJt>…'€§hE#È>1a½¢ÌrfŠkù,ðÕ æÖ€96ŒG5r²_U§=öNÇ[†œ!7/n»í'6¯¹«µ­Ýór‡ç<·¹nÐ"D¸é¦›n€jÚ(ÈäW´ }Mþ†ì‘§£ „IÃD^RP[Säƒüà³ÛÛÛñM(::îtGÐïQˆÆÐV ßäÛ¶Õ~¤Ÿ±rñ^«ÒšsÖ(;.Ð];v%1.¶¯™8¬M„äc-² a“™Hæ½½Hl« ö õ1¥ +äMrSªüÔ·†«­ÙA¶®äS²e Éö -Ÿ2üÍé@•âþƒÁîÝ{ÂŽíø½ Ê ù®| ò†7§°³ë½ÿÏ{/B•œ_;hÜ·ä·²F‚|dIŒFFíTêŒ%æóƒüèøŠZTĶÿ/~ñU×XÄã͸íq£ÀéùÇHÔÚÚfOVÑ(œ5Ö!!Ž]sv†]'îDÅø¤Ò䑿‚“//šT9Â&§M·”ÄDcÌ,˜![0Ù$Ïiݰg» Î™Sš.ÔIzQŸ\§œ>«#½§2¿rÛH‘õ—êS{ÚSÏÑ…ÛgoÛgmƒ^èÔþÙy¸ÿ¿þ…5î|!ïwq÷›í€mîÏñ5¿uÍ‹ÐP»úù!}ŒÌ™?ât$€åJòÒ6EÀœïø[<þ‚³LJs~ a["@s|’ÔC=v퀓ƒ³ÅÔèüý­}ááKøàÔ1HÒX"`Ò™ÌÄ /ÈšlR¦É\¢%ëB>¢÷¤õÑR«Ñ‡??ï¾Ð×Ú‹.!É„Oªðû®|CcSÜf½Ïy®p1päÿI'´ü¹Ï}îÉ®Þ.@>ÆtPë‘™ÃI‡ªäQH‚iëoÎa&}ìÆ?@'·»ý6 qշ݆Ţ¡GÆ^üD3?óoÅ{þ|÷wÐoã)B_¿19>)Mi-Èîó¼ÄÉq\ ˜Š ê(á½ÄY2‘ƼUX¥Ó°Bt©®Ñ2}sD*!¹+B¢Šz°VGR:×=æ“ýÄF4‚Ä+?L‹±€}X ž\°Ö”Óý*ÎãU«W‡üDwú“>y‚É" ÞŒõnภH>„¼üŠ~FmóÅ”d‰„*s¸ ç%ȃB RPî(ü¤W¾ò•˦͘¾ÐWy¸t‡ÇàÓ ’a¨Òºuë¾=ûâxº.vgÑ`õé«@=>ióçÝb’ÙŸ,À:Õ©l‚¸äƒêãä<ÌJôÀÚ]â–KÓPùâF˜ëŸ"–t1ÛD»@' jÀ'í")fzƒ>¤,ËL‚È*PØãÃV™ÕGº¬QŒ*\¹÷$ u:°?~¤¿PÅžˆ°@@iâÖ?÷ƒ®®®yxùè© Ô%4}‰>EßÒB+Ÿ#[c x؉ 7©³2Oäüéàú믿 7Gšïø 1ü»c Ò†CCŽy_¸÷ž{ì+4Š¥n´GÎ}(ô·ÒLcŸÒ$‘†ì2ÏWEÈëb^“ÑHUo:1QùŒ­hX•€H§#š-g]=+´Ÿ‰‹szà»7u[`õ ï£ÿ`èÙŠï“XÑqä“‚êGðL;Gn¨T£é rа¿Ö™­îˆ(»ˆ1c‰Œ¨_41I˰ŽÈ‚F}š4œD2^ÌºŽ‘­áp* ‚7!ÇWûJñ-ÈçS~yúýἕXvûKã<æÏǧ|ÎàŸ@>ÝA«oˆ´Kìšð+ØW~ç;ßy rÉùåSú²¿($Ó™Ub¹b‘¡5;œ ÎÈ‘yZùó`[üPÇäsÏ=÷*:¿E=“æ·í È[Dp—{ôÑߨÇ~Ô€Mì¹L„SAço$vÌ“K‚nªÚÆžýË)™%:“¼v[sj'æ¼T³Ò5däÁ.¬™ÂúÎs:ì0 b}Æ ÙÄ9 )ŽªQçU<ˤá$Z–# æbËëÌ931€6~IV"pXÈРԙآl?Ë­6™ÁœyF×a[ÕKñçïJ¡Ìë8ÎcÖÏ]–¯=#têŒ;^ºa‹K¼2@gøEÁwa¡‹å\'d©Û…^xEkkë—ñ, ¿uÄ€^žï¸"Òÿ¨yÔ ¹ÃHl|$É%.oý( E­›o¾oøu…¨”ÝýÎo·B8šH@‘(¬]‹0Â8¼öçACá?¬_´Î¾ôCÚ±LÉùóN(‡'T©¬ @MVÔ»VÄÅz«c!kÏ¢êI¦öDRiü›YXÅ|,[äåYÏ”ÿÞù£ “xx)"Ø>!˜Ã‘dŒ(ÇG$F¤%ЧˆdÛ˜ ç4Y=±F›J½hœ.ò}„CŒ¨:>bøèÉ+a_LrLg.j”qÿþ}ö[JŸvŶ ¢_°Yg~Ây+Î{6Þ78 ¹à%€‚}-: rž*Ú =4$“‘$1'­kãíåøŒLÒvϽüòES§Lé2Å€´øFŨ(ƃÆ÷:*gþ|ÈÞ¹R3ÆãÁs jüRUSö œO¤L ³°ÙÄ3šHo”‰WÜæZ›ÈK¬Ô>Öm«`lë—äLûc6Û6£hòe퉲œ.YlEŽeËG0`ÅBÈE9¼ó,¤¯Idõ¤ñîÒ™e᨟x‘ ¶1ZÓSL¤)ñ$½I†ŠÄÓóFm¸ØÎúªÐ¡Xp­UWªÁè¥ÿ\~˜ù<æN  o~ì±ÇÌ8é9ÿmUÄ9ùD D´w´OÖ³žÅéør~ù˜vôAY¨d-æë¦‘2sAuÌÛ¼îu¯»øfsr*kŠAᤠG=–·nݺuÓDîüì “¼?éµmöV”Æ>¥I¦®¤%')§Ê¬Ïó‘>Ÿ°†202àÁb¢!6áYO„OTÒ°È–tºRŠt^‹–#½è¬ ÛYÛØ¿\Àð±åÙ^傟µÞ³£(3jËlÖ£š“xYs9t„'oÚvýoœá°Cµ72«âT7ZP}”á@Ï@èºrF8å#'c|£;I7MßÛ@IDATˆD©8¼I<Î ÒgdF‡0 ZMRÕlaŒr>žOu(JÆ Ø*D8ºˆ¾–¾ð؉†Ó¶ÂááÜ|¨þ°fíê°hÑ"³…Ô¡l¼oÈEÐw °þ.¸à‚‹ð*ñïvwãçŠ=ÈùõLµ“†#Rd¤€L™Ô¡¶ Éùk›3çÄήé]'ÑÃmÅ’ Ú=@º9/ ˜zq=´aý»ùg¯3öøR¬°–TŽW’vêÏÊ.£P&Z* ®Ú†uæ´ÐÐ Ë©A¬ËÊ1ËÉn=%Zdb{â“sU=Ûz¡'ùKÒ™zvô&},ÈCLéçjÑvTÚ°¦«éiÞ+b@ÚÃ*Z¶„ÆÊ<"ÉïpBØ#¤àÊÙß:ötò§ÔÇ6Ñ襔´5* ’˜ åÔ2¯Cµ¶ý%¾V !lÏrBóŒÈѽÄHÕÓ–+H¥ÃÁSLó$êH 5õƒ>ühÑP,[ ëP’8Ë;Êi€4i‰Q[‡^'«.N‘—Ú%ÚÈ#2.è™C›Aó¡LqÄ¥]í¸é‡Å²áã óþÀþæé}ªúó2ôü^Ìä×½öµç ¹œŸ>'ç'kù¤uƒòˆ —ÈIPÑùµúS øyïëô?•‘rf^SŽƒ‰ÛÿM7!/ÆŒÞÅË~aø±<¥Á–V#é,›ˆF®¶„±ŽYj˜¬Å’ê†A¦Xgy c…ƒ5c&/lf7úÆ<¥·›ð”‘«us»PE…ýÍZ Ætõzd‰b1˪¥l/š„‚M³ön÷H%|dŸäŠåÑ÷-ºz³3Ìraû¶íɶP¢–¦à¿Ÿbùꫯy)0ù%€|N~H®ògi%Hn5“Ôªª±‚a çŸþœ®™]‹hDýA#Ï"ÂY y:û~AÖðHh˜¦Ð‹;§ëæ>YKžÑÇå*oNŠ®R9v[Å—¤áp¡þ-§¶¤±|¬GQÎmœ×X8¢y&®†àÄáãâœÒ[%¤ôBÙ2”îI'`ìL<“Õ¹%ä財U¹ÅA¹Ñp)W*{Eä–¨Æ#³föêÐÝŠK7ŒŸ‚æ°u;>↹_Ð?´hr <.âå¡|}¸V€zÎ_ÕºZN*Ît"m~¨sE¢¶ë®»î™¦ =?pubÛŽw9¨ã·£ÒLÞŒ±nÎØ;?\J2&‘M•KD^H-E¡x²X›Æ±NŽ¼Ú“m6yš¸tOåvŽi¨sz#1½‰y3]"ÊN?ê5! Üm$[AÊg‹3H·GudQÀ2/á5–ÃÑÉ=ѵ]¸|à•ªlÃ30–ð‘ ž•ñÝzD?âÒùòk¯=´º ßÉå›ì@š:ï!Îl4’$†ê„0­üÈ›0øÁ4išJ™y-C<”â­AÀMø™¯ÖV|,‚m-o‚x¤Ç3'¬‰<ãC#­ã¤K“-á 1|5Šh£Ò¢Šíͪ3ºH :Ãq F¼ e0ë OÂLÙƒPW_Ñ¥£i›.1Ñ.…nEN6eYg§ßrÝa•ò«áȉ×ÌDÀD±û]׿–ûÍ ®ò¶Òà ô 3€ßôËh.œø1g)Ǭùôˆ4ñpZä̘·©¨ `QiΜ–zô€0|Eg×6FoKíÁûþéÆ'^ÞòàÖ®-À]JSÏ:F?‚ÈÚ4R¹ ˤQ@¶ÇH+è«”q‹tÞØÈ,˜Ä”'µ#µœƒtU3ÙF:bEÏãšóK„/ÓÓ».¶õP$Ó%Y8a¸SpÍ6¬·3‘·hŠ1T{ÇöÞŽ6Œuâ•`Ö†ÄÂ{ÃQ=oš/yщÀžÞžÐ‡—…Ø<°Õž®£­?làÛ«Ÿ;Þb¤!ƒ}Pþ˜K¯¼ ȧᦓêPgŠ<çÎÛ'çÐÉéüîììÐ Ì»ÿ@B!²¯ü¦Ãs’ûgžMa>.9f DÊ–e ü¨Á¿i"¢Ê„JN,|lc¼b›b5§ÈO;ÑyêÄé’<4×p#–ˆMF—ì]wÿM;Q;db^õF‹“ëÎsÔ½Ãä¶%“ñu„*{eQŸÊYÆúËÊy–9iåìG s\ø°êõc1ìƒ/øÂ =è?èÛþè/,G8­sÚl|9¨rüÜå—´„ŽaU¨7dN23BÒ 2O(LÛ 7Üðb—UÛÄ8½¯þÜÏ0OÝ›ð¦Ô({bÙ ø›0Îx$jÀ$ˆ¬™]eA#ÒpÄA¥¾\Œ%MÖX,VÁ¢=Ug¿FR¢'u±žy_Qi@–71˜3B9s_è%ÝMŸ¤'ë©#§zLÒ7ÓQm©·¡U'>äCh+©h¼­Œ“ÚdõêÚ`ŸIV";šÂC'÷÷ìÙ9ÜgÒ–?®úæ7X?mqÍ…Ï|æ³!^¾ý—Ê/©MU£j9i8TP£*TGŒBvà‘Fìþ;¸°ˆWqåçêÏ?üsðàAû•_Üýó ÀI€cWÇØ¿ê›F®•,P©¢ªm¯r„$/¸2GŒ;@ ˆÇa>:0LêN9Êè"&:Ÿ/Ó[±ñN¸ èv޶ ü&:!qž<À©Ö‘ñdÂH2sÞFõ‘(‚dk£M%Y{â2žFbí 9*ÍÆ¤¸cÊŽÒGßü† ¿ ËÄÅÀýVäÇæüÆ9óÓg̘‹W‹ñtüäƒÈË/©Q´J‚@ÕNCµÈªvμEŸK/½t1Wq X$äQæö…Ãç“"„½{öÂñ[L2“\ø)ÀÖNÜü“Ø“QË©ÿÔ‡ú‚ÁMj•³kâm8Ò—'%?kéœDŸø¢m}³ž¼Œ ò5rÃîÂŽdÄR_ãŸáÀÄ5Ù¥e&‡Ù'Òm ¢-€c+oi^Šöqæ´YAUb£&H» %ú2ôQ$Nó¦L|ä%ðÝÜÁ×ÛùêOÈGƒùâPû íÿ]7 jæSÐü Š\|ñÅ Á€Îoþ(_$”š„Ê#k©Z®9¡)qd®”§­K–,á[ñ¥&Ò®û¹ à0Úö…Ð#Û¾ý{C ¶±v폒ùÆãóÎ?t5ÂäÂÏå&š%gSV”ƒê Qp£ÎZÕ½ 0D*™£°§Ø&:€=iG'Ã#·¥#Ÿ6*`D8Î)ý0H¥__¡©`¡g l Ç`oüÌt7›Ø©ÂQŦ̕K‘Æx!_‡W¹øŽ\×±ÎnæÚ܇Lü(üà!¼ò¾bq× ŸñÝ4ð ð3øÛ|H§ÕŸ£,¤†yžJ˜ÖÌÔJd2TRcBåÕ;¶cÙÒ¥ËýA”£`Ñ üÜßËÄõ‡îî›ødƲíßÞ_U¯$M¢S%ÍÞD.¤IxŸ^ôåb¢,ÕûöWE­êÎ(¶ˆ•¶ò³¡Êà¨vî¬+·!ŸÙ¯À[Ïm×Õ„ËÒ>~ GBY;È‚oçõïõm¤ËP>Sž-ø"™:.W%‚,ö“ú#%pø¶bûSL&»iIoË“ÈQ^Œõƈ„‘1rVŠú°Ê ·a©^öúŒD},Á€‰ ÊíªüÅNéË”18êÓÖ©[ÀnDL <kŸÐÐ@âð›¾1 š &´É²ü´Ó–@ˆä1/¿$¡Žae.™1æÑ†²Ü:wÞÜ¥6}mûâó@ÉÑ Ž®Æë6„ªP9Ô­›¾¥qHÒ$ë ÖHù*”W“­KfŒ¢-s^Gjª\$2G6ÊT.Ú¥ Û:癨ЮcE;¼¢á½…M\”Õž2 ʳޘtÈTëÙ&vLÙÌI*¶·dƒJUïgч÷å-yF[3!ê@¨~g¯ã9bØÚ¨ÞÉ—ˆ¯ÍÙk¼ÝØŸŸèXXl€A Ÿ?ÿ1½pb_‹C†>‚’á\öù ,†„ù ê—Æ4‚u¢/•Ä ‡Ì—¢ÏÌ™³Ná ‹‰œ»~$÷¼»ÎÏoÿQa:¿]à2€H6L²ëɤNš³äzPVNdKi†û„æ@Y•ð zkÖù0‹x"KÎþ¹¸—9x¢Î(ÁÇ[8ŽôÅ%DÑ·çXç“Æùyq°ÉùxÿÎ×òÄã0]H{öq³žÑ(B釲lâ»2&Î%ò: dmØýûɪLãL2ë!6‰"Ķä)TÏ"e¶$¹ŒèÔÎû*ø²>âœtLÏ«¦=îÂB` ð•¾>~­öb Åá7é=Èg‹èìÙ'ð= %?D™ÚåŠe ‰ÈS½ 3Š–eÒ*Ú˜—?çòù”—°lÌø'@"íß·ÏïüÛ=0³Ñ—‚¦mp‚1:»dƒ™SÊ”jiœ*‹Œ9FQ,rh/%¾ ˆÖpZ›ÀdNÀ o›1a+1¤Ÿ G,qäES²d“Šx&æ­Îåp$&û·xkÖÂ9‘ñ¶ˆ7ŽÖ›g­ƒØêYc2ªc@Iá,œÓ²äDÏ"“Ê e–é‹Tç2Þ)ãİàà¹zø ÝÉ*­†-®oº% aŒð#-ø„Œ;óp¡oy@p¨ñ¸è¢‹æ¢:ôO–)±d‡Nõ[IuAÒ*(ßüü<ÿ ¥¨Eè p'À- #7üöZä&¬ødíÏÿã×Z…žþØÉ'i‘`œ**—ºÊÁú¢·"JK%LÁ\Õ£ »›»ñíÀƒÐ vïñâX¾ä¿¥é;ÃAzó/\*\yå•g‚X‹±ü‘R+Oy¥…`ŽcÞˆ-O9!Q,ëPY¶,]ºôLÞ©T¤ráQ­é‚ã&h¨ 1ƒÂ6 i„¾¦±{ñgœv”½’ªªVª‡(–&J²1 –Er± Mdsªœ¯DˆNá[z¶sW*xÐÆëØ ›²?KV`wÞš“¯¬sÙXgr ¯¶€&g¬÷Ý™$p>âKfFk\Ù¨ž­¼ò3¹8Ñ#¿“=P¶2I©H e2,Ó§¾¢þƒèˈ¡K’±BUUGXìmî Z¢ýéÅ÷¸í§rx:–ùæüq]~ÚòeèJ;€Ü'eEBu¥bÑ$2"­ :l9ñÄ—›I@]ûû5 µ¡à½¼û?î¨4wœd|¿¤2®ÉV¦6L‚^*M^¯·éYà#7¤V6fóÕFŒ öEŽuΕ̘ÒÄš4½œ)ä&|ä œyçä¼=à²/&òó6ÎY”ŽK²q "Ê×kœÑ/×$KIõëõâBYÌÉ)¶Š0â"q$1:?Ñ"n–#2Qvƒ^‘ꌪÐH|²Ö†*l$=œ²è$–Çðµw[ K÷úŸ°Oâ. n  Ç熚þæ-˜¿âÕ òUš¦0O9oÚñ:¾šª r&bl`ÆŒ“ñûeSl“bÛ|gÅ)í0×FTã·žšqãÁÀ'…×onÇ+’%EÍ âÔF2RQŸ¨Þ.oz*MÜÈRØŠtœÈ…Í‘"ˆÖ1 „½ÿ¹/xÔ·ŽÎ›ö} hR#§6.-â]ð4yŒ_”-æù\Aódð±æ`Ù4MüYὑ'‚øÌ?d$‡½yFK˜zêd&a\èë& ¤¬±lµ’] ™ÓQ]ðFÁ> »D–2 Ey\ðØï úˆY‘C¡V¢&*ÙU‰W­G†Û0y}8©û$ëÀ|õÁšø :†—Ñ_¸Xš=i3Ps|ø»›Ó¦M›„LjÍÎ!ÊÕ¬«A­ •Å@0¯ëŽ–… Nƒlø9X4A £“Ÿ\pÃC2äW€íc¿¸~Ø ƒcC€(+Ä-Ìg‚Æ-*k!' õô3‰¼DLQ"%´‰9w£}„lD<ÉI ؽ©'¬ùO„ƒá¡‘±J±ßÄž]#øwØÖ2MŽw™ùÜÁô+¦…%7-6ÇuáÝMk府¢¾Ñ"ìžwˆÌNÄ©26vºÜÒ$–FCR½²¶Ä3Y¢õÞÆÛ;Uq¦4.W;šÜ¦)øzðôÅð€ûÀ‰x@§äÂ@·²Ë'føfa—‚¿w lV>)¬*"Փص@ªÌ2b$ÈšgÏžÝc·æ×&lã×,>T†½ò#ÀhB»þƒB|(bÌ“¤¤Œ#î”8 õÛWƺçȼRß@V3…‚—S©™voì[çg'I8< ¤Ì5*ö‹¢€Ýü†.§)Q¿òK?3Úd¶ÈF…Áúû»ñQ ¼›ƒ„hc¨ÈÇ‹†¶½¹e«'43>„äaA”-#ÏÔGµ¡•­uÍšÑDnšÌÁîŠ2Ñø³´ PØ/ã–ÓLÀ07Þ–™]3ñ;ñC®ü¨:Ñ‘‡J&ÍñQ68£««[Ã99ŵ``âC`s~n_ð'#­ÎÃ5¶<¯ƒÆ*¹;Å+'…§AU@°–x£Ò,7˜ÆÊ ÈÂ8?—Z¼ÐÞȧ­Ù Í›±oôÔ€ãÌÓ ‰Ë’´²ÔD ] -ÝJ¢'4LB{iÌ„P|„Îùx¾~Í`ÚÑÇhÙµ!u0÷Ÿ~8»•éüLô'À–ææ–ÓgT@u'@‡Tr¸`ýF&bÆ6–Ÿ1}:hÂ-<Œ#Gá˜üÁ æ¹oA¹×6PÒnþ¡9Þ‰oE³T5 ÊÑÌ5E*ÕÉÑ©©«¼€"½Ð6ÑSŸ´‰_Â;½]“¯2!Pð:\LQlÄÔ¿_·cð’ÒSòK+“–q«ˆDe*ì%~ÖË,²÷¢ÉàÙ¢]Ñ›ÕTŠâ3Vp{Þ lÏŸOÈáý#tØ’Ãß‹°l¡÷(u~PÜ\“<_R¥VqÎùäø1ß2oÞ<{é‚B`Ù?¿d?Ì Ðé)'5Ž“ŽM("R KÇ:ƒ¥v6%SÂljžØfí™;8spvžÚ¢ólÇöD¨<²¼Q×ȉÀ$ÄÉa¡ / mç*k_´!Ö¥©EKÒˆWA6Å´­­úã·ÊœÞåóJ~† oaUf1)³ ‡²WQË‘ÙÎì—µ!E³ 4óÀ]4/S°T`Š\l]•"uMŠÑCîÀÀ?ç rÝoå/þ4`îK1Ïé¸ãŽ÷ƒuAš¥zÔ°làHÁk¥œ¸ÊœmLÒéÓ§Ïdc»¡gûÐ,nuÖŸD3ØÈK¥w¶ýK@(V9ÕŸ©&iO ŽT§D)D„ŽW-›)ïE‰ÓVsŒö³[uGCÃ’‡Ý4#ìš³·î:ÏÆ:÷ïÓ%åŠÎY±‚]ï²–Ê1qR€-|ŽÏJ¤¸ÓFNI¤JÌ×Obáô‘®@Ѱ«œ(ŽTŽ:ªüŽ6.‚îdDÛøwhÐ?þ-Œ>ÿ§Tv™ ãÁÿö]û’¶ÐNA ä‚Ñ NåNå;? #n-N ›L«‹¾Êô*UéËxÔŠ  E8†pwË.xPÎ.‘©dô'[D±¡rçG»lÃAaÞèèèà›$}UÚzx£*¨!¡6R¾yò”)Ó(mYPaAi±xeøÒûƒ‚ÌóóÌÞ&þžáØ$ŸBƒy—& 5É’&Xrž‹ÕеàW%«+¤\Ñ:qŒÎ…“{â)3ÀhLäÑÜËè£e¿½—zǐ˵‰êûÍMÈ«`›ôŠ"ÚÓ§—ÎÔOö«¥+{ªÔ«ù™, ,q1XS³¶D:V…^<o¶,~ŒH‘¸{¦íz–Ç0uêT¾!˜©ÀÁ CÈ$襬ÚW~HÂ:&^•X€ù)@.<ó:F$êp@ÌsȼE<<•ÎÎí¥³@”Ò¶ÿŒT&<&qvóÏvàÒ7†;€zÚÓ€ƒ±>IKæ$!ä¤ÂE;–ŠT”œ‚åWÐ)WÔ‹£»;ëSàÕF5ÇÃM@“JrÊzpD!@Ù¨Ð4®ÿ™}É¡¶sÚ‚Ca%æd½ EÑÉ"1€lÃxjL<©ÒXÄ&Dª.ÊDbµTô»Àå0_ƒ;ãæü²´Ý4! t&-´X€ùkÁ”N~̼Ž 7ÒG«ÌÌ$øØbRܧ$¡(hz(kŒv#ÁÆ|ÓѱØT)Ê&UV„±‘LQ@/ÜÓ*çD(1¦}ræ¢[29?Nf9ºãyNü"J}³hVƒÍýc@ÊÊñ÷ï}P#Ù@Zñp‡¶‰ma1OÍ-á´h€ŽOv±*ÕËmܶ¤W*¸å2xmQ'êVjÕ<'9Ê<~æð“šðÁ_š —«fÔ7uÈG秦üdÈüÏI6gªBÇÖ9+rÔ©NèZL±«ÇéÓ#’C_ü1TO%ìæ•)®Y!¼ÿ\$e|zhW¹ö˜;VF)lî­…÷)©RNœm/Á'~É»hÃ<1&Wˆãa`óYîZhVèåuësò»mý̶EŽ%Y¦ bNاõózâyxR®ŠÞ{ãYæÜM̆­ã øýŸ¨tɆ¹mܺ²°k캲Fxqn,Ó- k¸r®E=|¹õÑ”èü†_ ‚œiaå vFs1 VÉgirÿÊuEÌÔ%ª[.)”Ÿô€ÛÆh_—ÿ¸UP8*28Õ¯qÚ¢•çxf›rrŒð¾¦%Òjê»NA|Ñœ1Z)æ³²ASÜÑFéÊN/‘©kh/·œ×x>ǨEY+ë©U¹E¹ä}í•«‡W}µ—?z9].ºã£ÇŠ€\»ÏnÝ¢Ì €|1gêÊS6S ²#½`¬ü ?NVüYx‚Pqà}¹°M-œ¹…·.):þY<‰†«—×9¦ÖDa ñ^'eŠ!Q½÷å| W8ZÍÏ…—²h3ê9:=˜Êùí¼à2V˜8õãÒ .ºü;Út”` ¯¹yÀ^¢€QŒÒ·ÆÒgœ|»‰½p¼w²ç`ÃÕ¯©E+Wõºò@°äÜÊ8¹¹·Ð—(ËÈåaííAƒBmEnª8d:ÒKcŒ3°¯ù[ã`ÞÖùèü°Õç³ÿ¾‡¥ó{°>†o¼*)µi‘uHyk%â6µâm[KUŠHŸÐyÞ¤¥8uvNŒãá€7¶]g­Ì…æEÎC‚´^]x‡ª•m4ÞsŒsêê¹ÌËKe[ ÆTùŒN™>À`cK¿@ÖvÑÑ—L–Ì·$/iúùk"E:"‘GÄ\ÐäêíëÅAá/Þ˜Ð÷,PF0£ôèÆ'€µÍá„¶àÝg”4é\æ‘IV« µæánO>tŸÚ,1ùíPáßx¤ "§‰x'÷3 xì\r×Yâ»=Ê:¹EŠÕÞhIè1zû’•3›‘XœÕ+[oÏΡÂuƒ1¥f^̮ё¡Z°?¶Ý0%±E’«?:Œ‡n¨šoѧto ùÞž^ÿIáZ Pœ‘€*;3 Þ`Ê_2p“ѽŒ³)AÁíIA³ttêŠÉŒPå{ŒÊÎlÊ8ø±6“¯ìÐ^áÄtîÁôf‘ÌJYŸh[ÏûYœÌÀØæH&ECf=ºÙ¼°pfö £Kg…FÚDzº&y¹°!i {§X™)OZ§ÊYÖ{ÉúʲY%%(©Ô}±‹ ˜—Ð[÷º…„Eø)s,{Æ@(k˜ªÐ±uÎù=6JUë:ë ¿»»ûÀ”©ø1ÈXcÃiBº 6˜ñŽ%W~„:ùÖ~Ä>Ü>ïä’U{%V“²ZWXÔk¢nø~ÃÎÛw†}ãµNm¸¨A,k适|k€•¹b»4wb°Qo÷=À¦i b?ßÂë举‡‹LÍb2Ú5w°±óèÛÛzwä»>T5`bpgòs´´´÷ÞqÛØ5¥=*¼5iª)Æ—Ù±…l©Jqñ6(]g=ˆJ­¢ì¢­Â‚ì¨s­Øpà6cÀ>þP(ÿÐ/»æ%so &×Ùá§„üŠ[÷H®ÙáÓpHfL9dÞ.ó<¸ßI¶j‹QÑùmço{C^ªX%MÍ×ÜDÕ‡ëÄ£œØ¿”)Xׯª^ÓÃÛ9-/Û¦,šV}lmèÙŒ/5í?­V€ )­ØYp •ÙDÌ AÈH@ÖEgb5?þéÝÝøà¡+W"(šÄ8E ¤—ªPfl…y§ð¤ym6ñ[»°5æ[‡[Q?Falí@ w@PmíäÜÁ ŠP+½V¥ñ„ô¾„óG/?-‹r[8”í¸¤˜œWÝ݇öAVRò0ŸŒyqH¸Aép|v} ;ƒYäjI6tm­£…k˜d—Âth€¾üR¯õøgꀀþ¥ "Šûöíç{™¥,Eb^ËÃ&±«E(ÆbX-÷ïÝ»w7…£»'çG@ YÙ`z#0·9¦$zeäkÃßø§l°“FÄ1ÕO>ªel;yþäpö?œ&Íç÷2&Òx[`ÖKf†³áü;¿80ƒnÔÝ4þQêR¹ý(×FOÀv‰ºo4ãW´»Ý‹¹€LîK޾å‹+×{üØîî(R­Õß84“©|Æïk=Δ@» Dàà#ƒv@÷ ìÜþ¹ƒMD15jp¥¸L rØkµ°é¬lmìä´lî^Ó¹¢3,¿yYh=ápî¯\'rC[à„WÌ Kÿü#â5sžüf41Žç¹<>ezzM€È©RŒØ1óûçÇ1¤Æ}<\¡y§?÷#®ú®K¼€u ¿ ÈŸÕ2_ŒÒóÈ}5×(ÏƒÌ®Ä wãsÜ8€-KŸ{=;|›âžq§€bÕ÷;PàÄ1b8½FPï$¢¼Ö6M’w¸cF¶±Np ²Ño/¢À”Jë xÖóŒl¾ú³K¸¦}ì‚þ…Õ>£,M3hY•%¥Ê€>Í D×å3ÂÒÿ}ÊÄAÙêá ¯š–á+ç™[Ù]~°[—;0*œtMl©]†WëR½cO8Ùý¼Í°0âéÚä?vóœžU÷+Vã0€-[·òA 9ßRÕbƒ"kälx×®]fC´Ú’œA£€F_ºdé3ˆä#¿l’L²UÏB Œ—…¢(2¨Ñ UáÁ¹sæúèZ{Hu‘hUf»àlH@wâ% Œþ<Øp\ÁøkÀ_¯µ&«¨Ô2¢u SyNîÜŠi—æœ<;71^hÆF¼zD˜ú/p @íè·´†Iœ€!Ñ»¦ë2òe^Ònû2Ò?^³"‡—YùJyã%lG>V ªAüp@í¬® ¸«F‡¢iK%ç œöۀʊ1Â.X°`=°â €’m’SÌiÄ1¯ò äPÁ„ª”ž‡øÁ-[·.§€&rôZl€¼–¡88°Ÿ»—i#Ó¾%8ãÐ Ó{Æ×ýàMOÈ£Q•lS µ2­S’XªÅ³ø™ü ˜$tß7aÀ ¸jÒyÔ›G†Éÿ€‘·úJ³©˜,"jWñJWµ<˜ŠNC刮x’¢oSÃLŸÝ°=°Ú†Ý@Oá(oýŒ6E”6…†™Áø7mÞ¼ ÙÌö2HÃ'-Ò´óБP.ÄCâe•o|î¹%2xœRs9@Á#$„v7€ÆBOgUáŒCg[%½~R+²ŠÚu¢Ø1Èšœ@âw#gžò|YLu¨ƒeœyçó‡£Â¸¿[,(Õô|CFÿQk˜ü!ìö£——>RÔ” ½JÅ–jè?¿n)[ùu)Ë—x"‚ÄŒ»˜Úãñ³«Î5à €¦@ãOö‚šÀ`Î?³#:à6¬_`à²G&ç¢I]¤· ]9fPFBVD‰t0~`åÊ•OAb ´{@>p?¼1Þ Þ ÀíÀ¸þ§ýónÀiøëÓ࢖ªlwCOd!E˜Jˆ ìD4tBÄ8@êdÔǸ¿&ÿÓ$r>¯Ãè?nÅŒˆÏƒÄ[ÄQ±KyG" F.ý ZŽ$ýéŒ[^žåœSþüz1£{>»šD{,œQu†¾Ù<¾îKTÏA“”‡}~Ж0=l¼/E @îd“„ÌÉ è± ç®€ ®Šäu¬¡ÍÛ÷ÌÁfÌ$³ÁCa'6:ì (l9Ÿ}BãyŒ?<¡‚x½LbGPg`Uja¬¶ãËSÀo˜•QÊLŒGYþÄ[êˆLõ&,þ¾Ÿ> õЛ€Æ?éïã´ŸA—¦IÓ4d&”i“„ÈãiiÆfyJt–A3¨R*ÀêHÕ”¨Fw—“Ÿ°dýŸ¥c\S¶aý¯Y4å´ÙdÖT‚ ¬Zµj¤HöqÙ%›¢£Ka+9€Jº -¯@¸Íüñz Ð÷˜Ç²Â÷Ç‚Cؾm»;4]3:€™‡gv)ì±0´»ÅVã±2ïZÅšqÀ_êTH—#‰å”:£J$o,/–1æF…Ñ×긫?I⣯wã÷ƒ†ïŠ)iŠqÆpd:+k>/DR4!)ÕË-•®¯¡@oŸ]ìÇ gUÆÞo·þÐõͶnÅ«ý¨Ó·Òp­Œ¢P7´1Î.\¸¤| {$kŽSVÒ: •@‘Ùëwª §á7/´~ýúø:“;âði<€2¾wßÞ€o úÈO5ÄÀÔÃÓ»ÕË8æ³„É ò JÏ ÚºF.‡Rè6VˆÒØpÏË3"±LEfþH ä¿=1†?Ncÿ|t˜ô~Îúp?ˆ}€º r¤ ƒN –\SQ;º>¢*OL–®ËË*–-fÂX@±œœ¥‡ðéÕ3Jƒöø| o‘›­ ~ý)Q´í§!mÇöí6mÚ´¢§ÿ²Iµ€P¸$/Æ;¼ À b.B9y dñâÅ÷3dµœæ±ˆ".‡@È€¤e/=g㪰!ÖË!f»z K…ô(v‰j ³ÿÒf¡:¥73ñ—:®'0®2­cÇ|ãÿz\˜Gp²‡±1:Œ×8W^l;!µSR]Ikt F·è¥HRU‰›¤Îb)‹#ª0ÊQHí•è„jÜ¢Œè÷¼ÿ¿~Ý:s‚¬Ìúd2[!”† -š ’Œ?·AÙ%³(› Ji»3P&¬ŠlôG"á[n¹åçnôÉôc#­Â‘ÀæÍV&§?¶ €2†T #ÂHÕÕ7Pê´ ’ÅM€Ø)جK)Ýât¥'z‰Q†^‘JùRó[]‡Ã˜ëG‡1oÅrà$ ãþzL˜ã/­}ä—~“vL…ˆ•?•"Ýz)í–[ba"BIÏ^z*|F‘ñ+_„^îÑœùaèë2~>³~ÃsþýÚMŒr»ÍøòBáÿÖ[o½IûqÈ Èe—”>¶˜‡b\ôgyHÈJ‰›ñîÇçÁðn®çÌÐÙ„¨u.ôÕâN~Tļ•ìË€êpyÕ(ªBÞºT]EbJµV3&¶L,ÑÙZïBL+ïhˆ%Z© ÝþšøÞña>|q².qÆ¿}¬dl¿Fþ¤6Ú” ü3¨ßX¤`¬L‹l–œ-cÅüeŒÎÞ×ç—Ô^…qä÷š÷ãö±[chôT ëÀiXfoÂ78ø Ú]îrã—[[Œ›]Í”‰P‡*“çÜ¿y󿥯E°F —5ÙÍ ²ÆÏ—0 ø Ú ¸,¼À„êÍ“›dl +b« ˉhYG4§°b)ÝÊò†+ì)=UêTòð`¨²+^…wß'®“O–@ãçèφF·ñ\/h­)š‚“N-҉׊éT—éÕòÙÉUÑÎf–ϹKgåË®V)ñرÖ^îwÿ±þoÃúÿÀ¾ø ¬Ÿi:l”ýY_Æ=ümܸiRiø-f sÿ?MøÛqaÌŸrù¢6£eѸ#É‘µÕA©mž”3”Òˆ¹f‘^`qºŸSŽOGôÔ/CÏ ¿Ss1Þþkòë'@c_µz¥ €>@RÀh/ “ЇÃÜ9s2\SÚœìŒ40/ÈqUâ væÄ¤Ì‚?Ú÷Ýw?‚~ì3alNð“njpCÐ>€œ†o{æªÃëkÞ˜gë{<t_Í0èÓÓ$TJc‡££‹ÆXê‚Yy¢‘Á —+’Xe”\7è÷Lpïç`çö­/À6ìÎä°gÿãȯóº½÷Ýw§ÿEЙ ü]¶ª+@\GñîÇ—Jwíܹc­ùÁmÞ -$´É?[Ë+¸wV­\jÊ6¾ÐÆ…ñ`êݼ=[Å Ô:™ 'Ÿ³@g4ë¤LfSS°ÎÈŽG K/¥[gVgé¦"c2V;U×TÛ7ñ‡\ÖR"ö3lâûLJÑ€|A;Ø®ÔÈY¦3k'Nlwl{y:Œ)¥«ë8™i¥¼VuaYXkb1íÒD¬©?dY{åƒ? xÿŸ»þðŠ¡¶¶6,[ºÌMþ´,ìÃpJg¢XV¯Ã-@ÞþKvqÚgd—M÷ì@º ]9åW*œK€$½Õ³Ï.™cŒ| -´ÌlŒYéˆÓ>õ~J׋£…PwG_Pûp‡@Á,t0Ò+9qIyHdçó®dÝ0O3* s•ò:/ÏÈÀ<8ŒÆÒb„Y_=5ð3Ùý-Lxï¸0ö1í7ÃwéMfœ¬é83xç‹SãÄ£Öœƒ—òq( óZ`…Q{J+B±*-§õ þº‡ºêZkÛðÄ‚'¬ÿûÒ"€†A¢ðäí†62ÏÍ´Õ @Ë€ÜɬƒÙÁrºpƒÈ©NÆLeiˆ0®t¥¥N;¯è‚VNì¸âU!ŽØ'´žF¾¶[‘²ulÚÿ'ºÏ•΋:.›åXC’æ½]Ìbmdû‰¸.äs Ò#‹ñ‰žôí\J,£*„™4bïXêÃKüá½û¿rå ìdà\§CjîÈsûí·ß ¿@nsš°@»ŽÆ°dŽü:äè•ìØÛÖ¶gÅÊ•Rxûã D3鬃Jˆ£ W¯7âÊú][7â2ó.Áµµÿ‹uõ}  YPÌHÞ ¼Æc‘ÝÍGzÒÊÊ00GÌO’:¨ófÇòb7*ü(&¿4<äÒ㿘ú/“Ø·_Ní¡Ôl‹¢%s3UP©¹‰ DEÈ1e±¦7åONÀÓŒ'ç±³‹¢„^†¯ô:«\ƒûùòåË!Ï0ÝF ä7ÜDäÌÙãX*<Œ…Gÿâ @¶ÈÖHÝjYw@±@Æu¨â¢؇M‹Ÿbzãûà–#°†1h\Ÿ·'õ„A(è¢Úß øëíº#[U 2f¥ ’/âì¤e0K#G?²–:3¸-¯€»ÔMâa½Vw,Œ¬‘òU×â5ê/N-5“Ô÷½g¾âÓg"”‰mHr '¤†ØNàÒ•ñ2ÉÒK<Ò‡•G2ƒt!h4/+•S*Ȳ؉õ±ü<ŸRc½éú‹ÞC}÷wê/‰Üüð§?ù‡·g­ï»•³2ˆ€qóζqè¾_Ýw7RðØlý+¿å¶‚pÊBlaF) ÝqÊ¢Âsã—àÔĦ)‹-Z¹wOÛ6o¤È¢ÌØÙ®W}Á|¬‰°ögçÐ^?š8»ætfêõ:Z'Èš«Óš yš$MÐòYk¬ËÌ:cÊSJ÷:"ËÀa±ÔqcÁ1MEð›ùt§¼¼ïâ÷ûÆü¾oøÉ½8›Üjã’ØÛÆužôî­ML,Y¬+‹„X¹²òœÊ:bz–– RH+Õ/†žƒgÔ†Ö ñþ ÑøáGy&Á¶Ð&¢-0Žé,ñšðާŸyŠïþkêOHÓ€v';š´¢"Hë4t×亜^+Ï_ÞiîìX¶|ùÞDÊà ö†#–”€‹Œ5ÿÚõkñµ í˜!•–5Àÿ¸á­6 'SgPk+U¢4u:våDË2hŒ’¯2¯¥Zfveªˆ0­Ÿ³ºboa­¡fpM˜òÑÉø’!–·/Nü¾Q×µšŒ¬ÏtGñu¨/2Ž`¦ Üú: l‹ñFÃK|Æ/>2¦¶3/bË•Ó<ÕÒ½ÞTš§Ä|éz‹¿‡áÛšÿ }ýËYÎj·oßV¯=£¿[“ìTr² n_òì³áKÚ;  _Æ/“HÚˆÍF:mUw€ Q¡yeÄsG oµ÷Ë_þò­¼âÞ0¿Eãtl8îIDAT^¾ÿOvÆK§.–/_a·J¨4 PT-¾Ÿý»µ¯ðøñ8§³»ø…KbdiÞ:\ÛDK\Hbô¼)ɉ7¦ ÄØÙIM<@=γ2uþÚ–Ú0íãSÂàӃػaê¿bÏ#$©)N²ìX7Ú`ŽQk3xL~&'cfFPSœijgÌGB!E¨lÏOJ!¤rR­Vv«×¢¯tu¨«ª3[÷ý­j´Ë@·vÙÙ/¡]G$›Aüú7¾qäôŸ _N@#?¨ \PôNá‘:ÆJxÈûP |&`N¿_¸÷Ù%K~ÉLöuS69¬­8òdÀŸzj±­“hÿún ½ç ë_jñ×! ”MA8`çN€Ø¡cFAF gg,¥[L<„e]|ËŒˆ|‰—¥ 3ôX7´.ÌúÊ©¡å‚ÞÛ˜Œßék½w$¯ÉOhô%Ã7E3DéŠ0ÁÛf)ˆ²Æ›·-áà²Ä 2£h9šIÔIÞt™¯‡ûê僮´ÑŸw´ìÀ3 .Äe¢Ànà”=m’9ÕÚÃAòégž~åk¿F~9Ú zí8ÀJTa% OeÂã†_b/-ÆFX4v½C‰dz†ƒ€s=¿„磩8\F*¯µzT¸°ÏQ÷QH#o±êË 2£;\Yz^qÅM^@i4czÉ(¼Ì, ¬?äuœy<_ݰš0ãfl þNÏߘþ)<Žü&¬ùóP&CLP;c[©—/,ݨeº$Ùõj˜µ5–˜ð’®b›­l–9K%Gz–¹z\ÜpI];Ú¦ýìÇ50þû~u?žð;hbÚ,2Ñøùú·Ù… 8`.‡ç<8ç~) ¨é¿\Ù[¨VQێĨ`U”CâšPHû™7ïÙ;wá¡4,ÊhöŽV"‡ $´ßŒ}5^ܵs—áT÷¸~º¾ñ­x. ^2ô=4aU­w±Ø'*]ÝÐ;3’Mù ã-B¤1™í/ãKyˆÄÎN#.V+?òy ˨ uÃjí‰ÁÁ³{îÊÌ/Ì#^C6gC™dl¨—! E*£ybl‡§$~E]_l B^HÄ“>‹é–ÁóÄÜåù•arðzODyßÿm-\û³ïB?è»üE¬+–#s‹ºd ®´dHÇ#òëçÏŸ¿ òä†/›Ò À3ºÐÒVvÙ¤£q*TÐ i:’;NYxì½í¶Ûn¶©—¦@¸Âe3‚˜Æ/-zrQ¨­«3ç"©I*ò5 }÷\@ê$R)[-ÜZÞqGµÑŽü Ò’ Ñ73£ìØ,ÍâäeÈ -=‰ß™È—@YNÃÈú03Æiƒ;†€ÉØô›†ã޵)YY/Ä)Õ«1ùž8Áœ´Œ‡©ò´”)*›LYÞ¤ßBº—ÇzbÉey¬&è&Õê„>ÿï¦×EãgMpƵõá‰ùóí—ÒH‹áèï¶qÕ£…ðï|ç+‹Æ/`KjÄeüù @¹ª%Gêr•²B¿P’‡?!>c¼|ó¦MOZfk-¨ÌiT6œ¸>{¼ àcP¤0Ä~HÊ|qÃåxKà;´WÚ­sê,Q.Ë”á©+e4µA0ñ¨ÆJ¼L#=¦Èù,%e¥Ùe|Tsø†À‘{ðì£ßœö±)aä5¥¬F«»LJ莫LnDŒ7“3æ%¿u3Ö˜I†k‰CbQÒeNK×3Ëד(ïïŠÆ«löj3ÜÅÚ¼icxì±ÇP gn4z j.`É xÚÆMŸyúé§ùÓ_zøGN@vE;S)‚ ¥KOñN‘#u*,¯„8Í™‡œ€¦+r{ûíoF±Íc@ |ËÉ=bTh® ÃxY¢¯?ÎAúÁ’7Å  ±¦1üUÓÛ%CŸÀÔiŠ-fíèŒ6*ž‰#^vVòð4¹YŽÈã¹Éï|Œ[ÇgÒ,ÍËtBNŸÕàÉào'ðÙéað¬#_Ìú÷¡¿c˜:­Õ¯š]Vµ+ºl¡ÚDƒøHÉgÖ(ñ‹Yíe\e¨íF#ˆ×O¬—¤,M8¯Ao‡w}w\݈>ë·²¹õõ«ûï õ nè&W©Ÿ» þ0q`xNàÑ{ §F¿ìIöUtlZÞên7õh@^ñü }ÓSQ`ÍÌ üìg?›‹ßØÂÞÄ?t‡G4ѽøx:?–`oP×<*àéõ³Ãiµ³‘¡ïBê<.šWœáÖ©IÍh•pë‚ò”:¨•>±–R½z7²b ¸HÂáŽÉ%oSŸÛžêÇveú'§â¹‚¡V\Yݱü$\Ç*5CNGLGYŽ9 I² ÜY¬¢4j7l EžŒ×y@ \ ‚ž®Ÿ1ôÎéôú3 gÚR•‹ývó–ÍaÃú Öf¶Ûú=û9e4˜ãò÷´m½çž»ç!U£?m‡GnS6†‚&˜·ä# GãTƒ*¶æ€Hè™åµ’7çÃoðƳýœ€™'*ƒWÎQ+‚äŸüä'føvKÐ6UèY«Ã›¯O߆ԉ W ­ãGÑÈT†Ó8Ý”¬s«òˆÏpç¡É[ç¶²ÇòåüìiŠ‹/ÆËë¤(‡µÊ)ÊǘÉèÐJgÜB,[Qñ1žxˆ[®Í3¥ëæÑ^;ÿ^Ë›}‚(ܯâ€õ½ï}—@B»±Û o}Þ€ÉM®o~ó›Ÿñ ÊŽrÛ¢1H Eè©Ý8‹Pñ¬œ‡<’¦)<-žyæ™Õ›7oZd“¹¡^:_ ðNŠá¿LˆðSâøÂ9]¹Q±“ê&‡kûnCР êül­BNü•ÑÄ¡uVvlÄ‹|Š›q(Ÿîx²Pg2²öåOåg8›°pÚOít&0óÆéXóë-CÖ„`²², iΑäÉÛNv‹3‚•‘åi¤ˆ×Sq.Ë ‡ñ“ÔŒÿµÍ¯ Së§Zåóþì>ø`Às0†bm'Ɇ;Kà MÀŸ{îi¼$´-¢á§A¸ìHvÅÂhk²7 GŽÖĥЧP”ÆÏ˜@ÃÛ ˜ïÚ(ˆTŽÍˆ¢=T‹ö€"T… DϨ€gåŒàêÆkÂÈêÂ=iféåÀ‹kA-f¤ Ñs<ãgYå>Ëc|4‚xÄÂÈoý‰+ ¿ßP&wªü9ŽÒu”fù Ü*SeDË—ðX¾ñf%2Î…3ù”žáé:CïZkF…k[®Eõi¿!¨îñß>nýÖ­=ŸÃrÃÂF'à×òpøŸÿùŸÓ>•ÆŸþ´Ü¦X$CÞêJqcêêt´@JB æíuµkɘl›3g΂µk×r‡/µÀo‰ Õ´h(ÓÛí“ïþ×wã´ Ô4ކê†p}óŸo_ŸRçRë)@½cšq—Ñ#ø²NnÝôdÀäË`È ÈøA2þ˜4fÉi$¨LBòðhœÖfüÛ´P7">]‰iÿÌÏÎ-ç•/Ø­ ËÏ T& £ Þ‰‰ÄƒDÉ,C5éD¢lÀY—-¿ÇŒ”——ø’éNe%~+)+Ãzïô¶aê±û¯Ç×98}ë[ßÂ×®ø­˜•ƒŸúC#ž·rÕêyüä÷r²Ù5’Û¢F#ý¨.ý1iZ0w(ÄI'ÔÁúª1ÕYzÞyç¿ë¤:ª‚ËZ…jŠdiˆ·ín ÍÍÍ¡µµâ¥âUÓöÞž9ð4}’`µj½DPPh ‰Ly‚áNàÙÚ§²¯ˆ H –h¹ £þ”ŸT‹g%^?¶!4ŸÝ¶Ü·-LûàäpÊËJÏöçùÈÏ@C‚(ØñäêœT3xqF2)r$®ÜI/çuq1”îºTà!©ìºÐ‹áUͯ/m~™é›k~Nÿù¥Ÿ… ú€…6¹.H𥣔÷á›øUḀ·«¡øêðÚæ×‡&¼6|èKR>²Î_»úå/i£=1_Ô²W;fâðã8ŽØOÎÅånB„£¿Öþýi;²#¶2·/D=«È/¥a\‚Rpy.6Dk›æ|ík_û¾t²Í2päg_À‰Š£Õ[™¼¸ªpààðÃþ0{:Jo¨jþat’¬Y¾¾9¥zÕrU˸BĽ#‹X)n ±åàI_ü±¼¬$0Sñˆù=Ý3%9ILåT…z<1ÈÎë´†À³#àÄÌ‘få8§RTäÎËZ&IS(¦#nŽ‚é¦“È˜óexYûTf/AÖõO­ õX†²š60õÿîwo·ß¼ÔóýV½õgï×ifk[UÀ—³v|ó›·Ü>Mý9êkä§ÍÈ~hK4þ¬Åˆy í¨Ã±.òŠÕr(£%!ë´ãÀþk¦M›z Š«$4§´1ˆÿ‘F5ïÚµ;44Ô‡qã&˜&LñÈÚRÝÆ×Ž í}(—§ÏpÊæÝ V) 0šp þ_F*2çe‰4Ì,0V •RËX“ÜÙAÖ,^ÆÏ´hlf±E†,¯xs˜uCãD\%(§±›1g”,Ÿ§{¡–7ñÆŠrÞ /Ó}díMðÎSÞÎh<ׂ³PÌð•߇q·ê‰'æÉCÑus:º;:ü/~ñ‹/c?ŒOýí‘Oýé4 Ð  èXx¦ÄŽ"È@"k»,F‚Q` OoÆCËótsçÎ}lûöËØf2µ¨M6dD }fÂ=÷üŠ¿’j¢’™‹J½¸ñ’pVýÙ(¥i‚¢H†£'ÄÑÌL ç+ò¦¸q²•¤@ÀŠù/éF •f2vZŠÚ¡Ksd2HîTW^hÆgdĽµˆå2Ä:RÖ,_*?%ö.rNùáÒæ˜AcÐÇŒ´&lÁÏÝÿ÷ÿ·éÞFyÎ`mä'‰zÆ”ÙgøÔ÷¶m+±ñ·Òj½¯‘Ÿ3eþ²¡JÆß# í©€®¿ …Óµ,BÍ þú×sç^vÙe¯ÀJ+‰Úâ|ÇN(&*Ó0èÆŽ‡¥ÃÙgjàyͳÒàï/ ·=¶ÚÎúû<¸¹ B!êñ¨€²ä²H…¼àæGTb e‘bÆ\éÔ£WQ)WEš’·Ÿ,±ðdÄYe•ÊŽ4ã²<ð'ÞRm…ê"Ÿ?ùؘp xxT,cÃ×¾þõÀŸù¶vCtöI¦sbË!ÌZO±ñ÷™›oþPŽüý¹ ÐR€…É t6ýÛ±…žšè²æ¸<˜Ö2šØ élðn¼úø3t3võiÿœÈ)¸ç„bAؾm;6[~áö ½0U „¯ ¿gø{M#=Û»¦]qsfeEJC$²³ãð?ÆãÁ4†bÜhžGy/žÌ‘‡0?Êx1ÉTvÈôvG¡ÌöåS.d´6E`!¯£@³r@K›•…tEs]P_Ç#¼oÔûÍ蹑OãoÔîºë®° ¬3T±»£­Üçó€^l¾wdßÄ{1?ES~½¦ý´=m&7~Úƒ—A#í©§fª_W†°ˆçq:Æ «ñöÓêsÎ9÷ÜúúºS¨(SÔoL©ˆ›~™ϺvÝÚÐP?(Œ;Î ¢×e‘M5ÍáŒú3Ã}{î%ḆvUTeqpçñ?a;: ñh—æ3'”r(§ÑŧFby‘ЉӲáB‰ãyA¢‚î%ÄròüҶ áÛ¸mS°&,]¶4Ì:ufh‚/¯"Ó.–ÚZÛŠ‡3Â{çƒùø†vN€â°åyHñØÅÑ’™¼e‘,s]¹*À2¾,DKr–ê.Ë’Œ4§ªžXHgÝ2¦¥©¼L–<Žƒ¥$_ÆßGè[†ÿAxaËåv·„³L~Ýwýºõá¶[o³ÛÒº8Ö¶Øm‰@¹-é1ÕÁ=ÿݸçÿɽ{÷nE’¦þ4~θöϧþœhÔ©gCO;J§R„JS #HÎo jllÜ1nÜØ‹èèhûº"èþ ìƒ> »dË›Üä1öGÒ~óðÃßÂÏ|= ‰ùÀúÑ èhôÔDÑø ÚÇ1„¾rT‘Ôäs¡ò~aÎ`éÒ¥ë§NÚÚÜÔ2™mWËÙb?iÞz:ˆˆò—….ºèBs®w^”ªpΠs“{…ç>ÇÌÇ=°ð¯]ÈI9n¼$°=vN”TF¢¶GºË×>§S\Ý¥VäqÉ‘”Œ>¢RyMºª¨¯Î¥è±Ô3Þ5úoì ?©pêqú«_ýjغm3® ©ö|¢_U¤§?k*b€ì‹«V­žsçOïü2TšúËøµWÆ®/ ­öXûXPo8–«+M˜ žÓ‰ë0À86sÞ9×ÖÖµØv ÜÝ!iV"é,pãÆ86…3Î8ÃÓ0Mã‹¿¼éŠ0oϼ°õÐÄûGpÙ ²H39¹Œ#(Kf¾v„¼°^³«Ëêó£dø…º³<–RˆWÔO¡ˆÞŽN«Ÿnÿq{ñÌ”‚±Ó¿ýíoáqßøpmüä7ZlÓ|”䆬Ÿ2þðüÊúÿüÏÿø"¹Ì–?æÐ[€‚©;¡Òr:q&ÏÆç6.ž}úìßåÓÀ °s ˜õ#Ð8$Î0ÖoÀ‡aÆ©§Zšh5T…Kš. ?Ýþ¸TéÓŠ:®'v ý• b2—Q<’èÖ2ÐZ@Ë©å¯Ö)»b-ÖÇxÉà•¹+ä·”½¢>b} ø=ÿ'ÞŒ/PÅ'ý0 ÔâW«îúù]øR¿ZÍ—¨âž0Žò íÙ^wUá‡wÜñémÛ·oQÓ~9ÝûÏ×þ4~jFÚ)B$õ\èK@©óžÕ–hJ«Þºuk[}}ýFÛàHo*àˆ„àvÔÓy;i1~j|ذaaÂøñICôÎ Uõ᪖—…v=ÚsÉÕ¿‚:¾wœL6i(#%´,Mª+A+QQ˜¯NZÄ#z7Óy—³]J©\‹C!ÍKS™æê³„SjN Ÿ›ø…0´Aåúö™;wNøþ÷jêü Jú6;À¾€;¨óŽß³ø^içn´¦þ„ÚõçèÏ£8õ×ôI½zÓPrj„A½ÅcíéJOo ®Ÿ2uÚ˜¦¦¦It†ì7îÉÓlÂä{Õøþ:6Y©˜ ‚ hWÅ/bcUcxQó‹Ã϶ßÙ¯fRˆ ;]ÅÐÙx;LKêdW«꼪‡i1ÅI¶ÉJnPËÖïäê$Œ»Œ³æuvQ^Y½áÈÿùI_ Ãë‡ÇŸö¯Z¹*|ýëßH¿W­ÝtÆf¹ñ²= _·vÝCxNà‡ Òè9úv¶é§©?Ø,´&rÏÁÞv””ZɯtŽ+]P¼¶ç‚Ï)?>{öì3ëëF¸ÙûhŸöb±6àï ÌÃ0Θ1# >Ü/Je‡T=(¼¼å•a~ÛoÖƒýgO€ÏƒŒƒ°bè€|d¼Rµ ëJíN—쀧ËvªêËèô†éᦉŸ C뇙ñÓ€¹´\¶lYøÜç>®Û¤aC(ÓTÜgr£i ¼MÈ€ßÆ\òíoû @iøùÔ_€Óÿâ=jNP hSÉÇûÊPJ׌ï”ÆtsØá_xú鳯„†ëL5Ò©'ÎHÓòž(.ÖoñE–óÏ;/ ŒObƒÆ Ä£¼¬åwö…aÃ.Ëúw`—ã_§¡‹ävy»ÃQè„¿[m8¢Êz–ù¬Æ³ÃÇ'~2 ªÄY;‚ÿšÏf<ãÓg>c¥áí?» 2tr±O%¯xsµí?øÁøÈçf$kÊÏiÿ‘'EI=úÂPTu9ÂŽ&ñ3^µ{÷îý+W®üͬÓf]Ž Þˆ³DœL8K3Äìß~=÷ט LÃ8 _¼x„W4_vâ9§÷>…”þdD‚]Jœk³Kæ#dèè Æb$£à–Þ§ì¯öêðžqï³ÑÞúú?ì[ÒáÆo ûðŽ¿«2¶v#Ο¢’à€ jûþ÷¿^ñǯaµ[÷Óä»þœòkÚÏ!,ÆëØV,±'N}å(+ÕÄ@hvk±’úb4©“qÎÂÎ;÷ÜpÅ„‰^€¨—Ç0bGTäR€!º¼tq0<üðÃá´Óð­»áøq 2`Z§ëuÞàóñC# áq, N´ ã"<ªÐU6)µ›…³<ݬ§'Ùþhäõá-­Gr´ÌߥÄþSøÌM7á³^ü] ÔˆÓˆ»Úø9#{_2&뎇ñÍË/aÙð4’9ò磿vüé8õÏŸÚ–Æ‹I½Ž—è¬E¦òŒÁt¾nÝ:N§ÖŒ7îÓG ý6ê7ëGŠáÌí4xäðÐC…–––0eÚT»UhÓ¼xQOkœÎÆðî¿Ìª<±ÐÜøŠxOµ¤Xn1ÞSõôU9Ÿø‰pŰ+̰¹n§ ãÙ“pÿý÷‡/ùÿ`û´>‰" Þp7|[0¹ùEVn 2Ì: YAã²óÜZ7*\Ùò’0ÏãaÛA³Ë’N‚SÑP6~¨Âš0/ô|zòMajã43~›öc<'üÁw„oßò-LkÒ^€÷få.;M¤ØÜÔûŸ\¶lùÏ|ð8ŠhäÏ_£¾é'£—`%ì ‚ëÅs_;6E:%žÓ‹8ã)ॡ§FŽY?lèÐYÔß ¾ø*מöøÍø]‚°/q\|ÉÅñ±aTϽ€¦š–ðŠ¡¯ Oµ-ëösù6N6 œßtAøÄ”CKm®;G}?5 _úâý÷Þ‹ŸòÆïQâk|öNã"ÌQÄ8; __vé]øºÏ÷Á¦ÿî?;«wXW´º­ S{ù|<›Uv:Kc¦*òS&LhÅ3““ݘ#°'°Àh$îÐAð¢ÂK,ÃúnñâÅáÜsÏ ƒ¢¯åÀ'»ªÃ-W†‰õÃÜør¹•À*‰¬®ØÿnÜ߇?}=z.i4ö=»÷„O|âááyóÜøiøÐ`uD~ˆÖƒå¶bü_·~ýœŸßu¾gQqÚ¯‘ŸOúiÚÏÉQß;f즈3ô©ñ³Âãí¤YÊ’ã•âTNœx…*ñüñã'Œlj<ÙÖÿÈMHãçlÀhŒ9Í ln»ü„Ó¹çœZì!ÐÍã»&ãõφû·ßöæµ'ªZ0³»yêçÃYÍgÛ5öõ>>Œw—ÂûÞû^ü*ÏFÿº”µ™vêÖ/@'ô>BM‡ñ¯]7ç§?ýé€PiÍ_4þ|ãÏÆ&äësƒ§ôy8^€2¸µåÒ8N¥Ó**ê©§?1~Üø‘6€±›Ñ³ â2ˆ)unËì ð—[‘7œyæ™þ5×è†Ö ¯Â-¢‡„'Û1ã@8Á4ðúoÿ8é#aDýˆø€Ž1pçw†›oþl8ˆÁŒ›mÃu÷ŽW:;ætä6î#¬ÇȼíÈøu¿¿øŒ?gì…ùèÏš*öqOê½óñtlU®_ÅIË•‘ãä) xô÷‰qtƒOöŸhí­I,ÆÓÈgΧy>Ö¯]oN ®ox©6xÚªÚp~Óùavãéaáž…ø¾ý@èïS7&|`âÃ+†_jñâŽ86ñðBßåÿÒ¾¾÷ýïÙ½¶…Ÿ'ðŒ—œ‚è$òønX?øÊø+þÚí/Ný½–÷oŠ ^G¼OÃñvl,ÕÏ@˜+¢7¦Ѱ/øÄˆ#ê†2‹³+$Nù¥qít–@€ÕUø²Ðò¥a΃sðäàùa¾.Dºu“¬*Œ­^>ìøqMxb7ßëýUon}Kø»‰ïMÂ5ôïDÖÔÖÜJüÀ‚… qË/©fÍÎëM^ ví¹7£Y¹bå]wßýK®ù¹Ñ—?G}ÝîËG~ŽúZ÷³â Ö3hľ>õÀ6G5'' ¸ô!euç“[OÀÝ!--3Í à‚™!'cGVàæP:ý+á+¼è{vï˜Ò|•(œ6khÌŽÁÝbðq$9kð9ø$ô%a;5aýþõ „þ¢s›Î ÿ0éø·e¨‹£>÷îì^Ø÷ùñ~n¸á›ð Ѩí ó:3Ž.â£?ãä Q<–þs|P»ýùí>>õåȯÑ_k~<v¾~eüÇÚEx¼ƒ©: !œWI]6zؾ·£ûÛ{;¡éì³Ï¾ø¬³Îz;pÜ!ôQÞßôo ò™sö<ñ!ÎO<ñGI¦Lš>ðÁØï‚W.î0‚@þßîz,üóª†=‡xíÂñÒ@cucø0Öù絜‡ÎÌ®cìâÀpwîØ>òц¥K–š3ð™»;wàæäæ/óDòó±`^ðÇçÏÿÊÂ… ç¡à|Ô§ÑëÿÜøµã¯Ý~v: t 9.ZŸÂþ2`£eøÂ;RNNNh86g68°ikëè p1kqñ,‰F츃O9K|| ä'wþ$ŒÀãÃS§M‡`èÎF@n©^}ʵaHͰ`÷Ü×áµ}¥þ$ÜŸŒy[xÿ„„Iƒ1Ý· „k„Ñ׉óüâÿ>ô¡…M›6‚Ʊ$v²x-ý‡FÝðÝ18‡áV"Ÿí?Øöèc} wCjÑøói¿FþÆø“>ˆô“PtK³ÂâL@³Î4àL`0öÆ]uÕU7ÔTW7úÏs¤§ñsV€‘Þ>7D®™³ úI2ÆÉG8aâÄpã§>íFX(’_ÁÊB„#ݸúÓáÞí÷â®g~¡·4Àwö9ÍÿÛ ïKÖ {·`mxÇ;Þa·ù8Ýwãv&ôä£ÑûúÞÓrÜG}Î :ÔvÏÝwß°eË–5¨@÷æÆÏ5¾á×ïG~×XT¡"ýÊ R¬¢ #¨‹À xÈ pYÐ4tذÑ/zá ߎ[}§šQƒÈ%ؾèJ#ÇáN¡2ᏄºúúpÍ5W‡ë¯ÿðÃYÀyÐøÙœBX»wmøé–;ÃíoµøÀ©g5ð¦ÖëÂ+G\Æ5Œ·ž«;4§ê4æ¯|åÿb½GØ»o¿müŠÞÃt@ð8ôQäèÏq~Ço×¼Øóåíþ)/­÷ó)?ïóó Çצ ¿’ñƒì3Ô ’vÜšÝ/ƒä¤•œ€ö4 #h7xéK_úçÃO~¿X2v®÷ÁM‡@`ŽÀG~ã£yÛÄ€f~ÈÞ,ä“cŸüÔ'ÃÔ)Óð¸rXyÈ!Å=ö:>µúß¼Û§É™>ŽNMÕMáÂ!ÙÎ~mM¼MKkÍÿÙ¥K»Þù.ÃíY~®ë‘æø"›Å9 àBŽ<Ä"¤£àÌ¿õ÷ þ:ªÐh/¨Q?ÈG#ÿ güTaI“Œõ¯ Ù)]îˆkc³Ü ÐpfØ|ÑÌ™3ÿœSû3ƒÏãš ä´hä¶<8tÐvrfâÇHÞýîw‡SñÕ!þ&<<ψò‰Ä­¶…{¶þ2|mÝÿ {s‰8º«[ã}[¸jøKíû|ÌGãU ÚÐ0(<ýÔÓáSŸúTxrñ“–N‡àFN^à˜òÛ,QÛЃ‘Û, 3|s1ž+ù6ûDf½6ù5êk½ÏÑ_k~nòå¢Ô1Eï°¤Ñ~!N;!$Ÿ :rš èfrM£Gžtá…¾«™€¶;Ãa½6ͧ3€#À?ŒÛïÈqp9±»Ëoºîºð†×¿Þ>BºK÷ävËø6ÝîÜü“°´m‰Ø` L4=\3âUáÚÖטö2›§9ƒvØ–d+ðïm·ÞnùÖ-aH˸ÃÏeMžÆCA{,9w ÀáÈ×?2oÞ—ð‰ù•H¦±ÓpÄ—Ш/ã—áæ†O\AF/(z¿¹aõ¡ ‚ä2 § N¨Á|_@N€0_ØÞÀå—_~ýðáÃ^Dä1ÓRiô¾©ç†/cwGáir–Nßhhh—]zYøÇú°½CNó)˜+À3XH„=w‡Ï¯þ\¸3ƒPÒGú·O|gàm=>pÅ`Æë˜}ûë¨ÁÓ|þð‡Â<ö¶íÅÓ}0è8…×úÞ º;ë,™á³È|sw}æ`½ Èù¨/ç Ñëà¨ÏƒS~¿Öü¼à2þÒÅ·Þ…”~dPýT¼$V.§pÍÈÄÑŸ½&w\ð(nÒ!4yæ/œmZØšî4p9€' ˜ÁûÆÎƒ;ý[î ÷m»7<¶“w˜žáÜæóÂïW{Ih®k1ÍèâJ4Ôºººðì³Ï†ÿøÇ6↓åÆlÝÁ¬Û6ò×°Ÿê“GŽÁqt[ÀrÚ·Co_ü䓜òÓÐ+úùµËŸ¿’Ó €¸_`bå¸SúÙ¹¨ï~&^™8¹¬Â͹ƒKÎ@N€!ßàL@K:âMƒ›šN¹ôÒKßÓØÐ0•W­|6@{ÇÈÏ?î x‰y7ÁRc²'GqÙ¥—†¿yÏ{ÂÈÖ‘öˆi©G8Æ3w°Kô~¼ñGáö ·á)ÃÕ,ê¤ ã±ƒÿ†Qo ¯n½6¶‘—Ò½¦Ñ–pO~î¹ x]÷“6âkÔ†å›QËÈÀý!>þkæ_y À¥ÚÚö,Ç7#ÿ}Ïž=›P“ŒŸÆNœGqʯõ> _£>G|^Ænä‡ÌdHŠ÷w˜Ë+œPŽ€€8aîè hôZäÎ`0ž¼ Ÿ{#:Ø ºCNýq…iì@Ì`9 0£÷4Û@JÉI`^x`ŸåÃ~CxÕ«_®¾úLk«qk ³H"É™QžH"¾yÿæðìžgÂü‡‡qá©Ý‹Éq†YƒgÙ.>_Ç1xF8¥_xGðör.…8…£=GûýèGüa s¾öì> Ÿy¡Ksùt:ÒÝ1ð–¢î PŸ;\»¶Õ«Wÿ`Ñ¢E÷" =ŸêËpºOœF¯[|ùtŸ€‚ËõKIi'D ÖO´Ë,œû‡- Ø£ˆÓø¹$Ðl€N€‡6 ¯¸òÊh¨¯Ÿ ãgq ãÏf¹ƒðÍB\m¤ûlÀÎÖaÙ˜ïàÁöªñ»Þõ.l¾Á~\’·­V’ºJB7Ü·åWá‘óì¶â¾CûÂþ~öÀЩ¯nMÕƒÃC./ÂÔþ’¡—xpfkØB§M@±'ñ·òöîÛn¿í6üòîMö²õ¤`Ú qà‰sÝoûûv¥8#ð½:Θê~²ÂÏr¯ÄÞÁ§€çFOœÆ®ƒÆO£'¤Ñçë}¼6GM¤D#ÞïƒéµßKYYÀ\vë`#d— Ôr Ÿ ÐÈ ÈhI@8xòäÉgMŸ>ýÏðöج`ŒéqÇŸŒÞ,Š#?S8C0GÁ<¥}”i{ÍÍÍaÊ”)áÕ×^ÞôÆ7ZÇççË‘ ³tH^¾<ˆtò coÝ¿5l;°Õàü]óàÂâÝO†-¶xE}t^;<Ìj:-œÑ|F8«éì0¼n¸Ý®Z3ÌÚ 1x!¼QÙN h‹ œ»ùwüð‡aÙÒ¥aç®]¶æw¦ò³ìnГÁ£<ÝÏg×6€€Þñì’%_Ã/ü,@‰4t9âM÷‹Sþâ¨Ï†ÄÆ+Çp"œüúœ’V–Qò ’‹xÑ 0ÎY:trù² ñ‚ .x3~^ìi¹astç¨N‡@#÷8ø¨„|€Ltñ@¬]Ø‘ŽÏ¼îu¯³Ÿ7 xtŸ¤FgµòYˆ÷(6†Å³O“Â7=…ñR`Žå»—ÙmÆåmËÂʶ•aݾuaß¡½¡ !¿n´ÿÐ~Ì@æÊ8˜QYx]¯MÔñÀ¯ÞÖã·9šÂA8¯COÂ뵓'‡iø˜æ”Æ©–/?¹|¤@> cò: È¡9EçWšùlþ*ÜÆû¯ï}/Ü~ûíaРA·fóâ:ÄK;þñYvö¡à,W¨mû¶ís{ì±ÛQ Öö‚ùˆ¯Q_›}ùyPAZó³UŒ*ý„ñ20òV4oƒpB9Bš Ð p& Ù€fZ$ˆ]‘§Ÿqúïã“âóöž}†ÜFù’kF@ƒ7]$ÑÊú j,˃ç7ø£¦/ùËÃk^óš0>þ¸)× Žÿ¥œòÖå˜Ê¦rÍÎ8E<: úAÌHøå›ƒèÓ„V/b„Ìnf'׿ x»p| :äeÕ¶vÂúYw¥@:ŸÊcX³vM¸ãw„ŸÝõ³°uËÖ€GlÍ!Øh])s4ü6åG¬ÃG|=.3ëÄsó°»ëî={ø9yò2zÁÜð9âkÔÏ×û2zAJfÚ'‚ãN9Î\º¨.jÞŽ—ñ“ÆžXÉh6@HãÏg6+€AΞ0~ÂkñõX|p„‚à2G7aH´%7ü’s×…}˜ð˜2eJ¸ä’KÃ¥—^&âe$Îè$hòþ¬ë¡ bÿu±© D‰gtÃ6s6£b=ðx¦14ÙhÅBrãµ ¸ÈÀ‡¤V­Z—q–ÛÏi?8g¾œ»ÌFùîŽôź—°ºaý>³`*Z€9ÿÞ¶¶§Q÷ññE ÒÀ‹ÆO€Fû|“/7ü|ÊOãW€bRÈñD<‘^ý“)¨=‚lžqE'À¸fšhY G 8£ôØÙ§Ï~~+~˜öõÍT°þçÈÊv!Ê>æ@ÃâZO1†+.¿"¼øÅ/ç_p~*—£¸ä¤°r4Ó%ò³ÕN%C)ÂâÓèéhð:èhèù¨Ï‹¨ )R¢?რä„oH¡y»„ æN€4?‚`># ÁÓæNÀœÃ°aÃ'Θ>í/ñ±É1˜ŠWùíBôZLöÖ§Ã7)Û׎§Njˇ±cÇÚ bäÈö‰øe%üjr“m¾á˜€`ÅÏYoxï=àYxÁ×ÂÐiàK—.嫱fԜиu ‘}pÅÐÖÃX­_²dÉW0⯂åiØÂeü¢i´'ÌG|:^°ÜøMð¤5~6RFAüd jŸ ÛHœ‡¡f¹ žÏhôrÚ+0G0â”S¦¶Žu6 /—!‚÷¸9É$HÁˆ+hÄ–aç0_Û‹ÿxBü˜Ü¿uëÖeCÆ.#רÏx~Ðèiè¹ñkÄ'¤2KŠ©ìÀzò„Ü0NžV•·$ocŽËÈäŽ@399BÍ ä m†pê©3^ÛÔÔ|) g ‰e„cÐüØ!ì+îxïÜ¢4² Êøeð¤ÓÐ'”ñÓäS}|nôÄ*9¥T07ˆ“ªa“·U¸ŒŸPxî4# äAc׸Œ_¸A¬ƒ[°k?qÔ¨Q/Å׈.ß@8 àgáÞ°aÃÝ0þÕØûØ"hÜ2tÁœ&ƒgZ¾¾'N—(¾ ^¬i @ü¤ 2„“¶…†åí-âÍ8БÏ„Ëø5;4:fج» ˃‹àÆ"ΙÂ@¨ Œô{aèë`ðó°¹x/âÜÉ—¡jtTšFy9ö4x4úŽ IeŸ;¦´!7‚“¶‘–·;Ç+9¦ËäP39Æsg@<Ѱ„]š±Cžú{%ðñäz^’ð˜ôl>þkûùp»1íÏwíiä4ì /¾Œ=‡4æJÆ/]çÆžãJ?©aÞùOê†Vh\±íŠæ‚ƒã4|Æ s'@œÆ/(G!ç`ôÁƒãWÎGž%Âéx8f fþš2žìF¾ <­ÀѦM›æã.ŸÖÓhÎÑ[¸FtÆE'8éäQÈqÑž—v TÖ@®›"Îx~Ðà'nFã2bÂÜ9ÈÈ sžŽâ*Ó ÞÀÄ ¾ˇÁX64">ŽáІ!> ñ8‰&ÒáqÔÇÃ䮂ðJƒô>À}Ø}oñÇ.|¨c;6é¶áØCß‚øNá߃QéÜ‘Ï S+£í Êèŧ²ägº 4á„Å$ ¤+ä¸hÏ{˜wìç½2*( ×OŽ“•ñü M ÇE3£CBnĹ3Èér9M¸òËUœr)NY$'iÝ 20 㢠¤a §Ñ'­Ò!£ÎÓ*åQ*WŦºˆwdøJ#dP;<6pN`ÇÝÓ@®«gnXæÆÈ4q’F¾ÒÈ€;¢ÓU—ê,¤30žãF,œd(4¸".c£ñ—Q2.\P†Îxž^‰.gÀ2U¶ ó«.Õ_„`Ii 9.Ú,h€b t_E}UŠ“¦ƒ%Ë0ESœ°³#7|æU¼³<äËËW —?é•‚Œ‹i4>ÑŠÆÈxg‡Œ˜|‚åa]LWŠƒTF#=]ÅsÞ<Ó@G!c@;Ð@Qw•â¤é`1Âi¤ 2fÒsÃ-Ò•¦Ñ]é‚,KiÅ:$oW!7@WÑ0iÐ Eƒ&¿Œ_i¤ W99$Î šê$ÌcŠ'ñˆVŒ‹>;Ñ;Å@86 TÒa‘&ãT¹3M†Ît¥åôîââS=Œç‡è•`np¹QåÆIzï ÏÓ󼬟i<:ªŸé¹<9¿ðx„`§=§¢>‹qÖ”bž^‰.‡ >9–Cœ¡#¨<*—¼¢ï,ÈÐdŒäMÆÚTšòã¢KÅE$=Åxž6€ºÛ!Ž ÈÖ¨Jº-Ò'Ô!*^äazžVLÏãâ­I«d\A¦åéŠi,¿˜V‰¦|Lc(Æ;¢óÀéè5 Îrô% äìJ•t\‰ÆrD'žÓ…+-çËiâË!ñbPžJ'^¥uó4áÌ_ÄT‚•è•hâ€Ç¨u€c,f ûh ’Î+ÑXdN/ây<ç]0O#~$!7>áE¨òHWiáâ/òˆžçmö’òNÒKU Û:Ó}wÓŠ|Å8«®Dë@¤ŠäJY¤åñ/x´iÅrâ=¤cí=$Æ@1Q]ÎÒ*)°3þŽÒzÒ@{²¬Jí õ€:ê=Pô@Ǩ®®ÒsCí«î2{¥:sZ¥ºJ¯”g€ÖËè«ÓËÍxެ׭£üÇj¤Çšÿyw!wƒ;êÇ[®ú{F=u} »g®Ç@)ÐÀ€40  h hàÿUäp+m2áÎIEND®B`‚ic13¹\‰PNG  IHDR\r¨f pHYs%%IR$ð@IDATxì½ ˜]Gu.Z=iènI-ɲ&[²%Y°-Ûxc3™ÁŽÁ f „!Ü ¼÷€{¹N $“ä~77<&“„›Ç„|Æ vlƒ‡`06¶°­ÁÖ<ÏRïÿת¿víÝçt·¤îÖ‘ÝÕ½÷ªZµjÕZ«j­ª½Ï>û„0‘&,0a LX`ÂxúY éé§òÓRã#ç§¥µžFJéÄx™¨!UmÔq› 9]ê Õ¨©¾ÄO¯šáƇõU§®ÍÑZ°VU\µáê«ôåq²ÀXO–qRã)ÕÍPc2T]ÕÃÑVë‡sÒáêóþ‡¢ª.ç1‘ T'Á8t9ÑEfzö¯‡gÓjÝá–³îG”­:ìá–óNªmUW¯ú 8F¨Nž1êf‚mfz6¯…Ïqµò#űûœ6§n6wÊZù‘âØAN«káêѪÍe î¤åîŸìêÙ¸^¸zc]^¯|­:á4ÉQs¨Ñ¬bª=«e’ËIÅBeAâéØÕ²œ}8ÈvLy{ἦþY˜;¨prú¡ êÔ¾Z^¨,(¼ ñyª–óº‰üaX`¤â0X>íHkÙ°Šc9?d$:±™õÌ«m^×ñ9ÍPyÕ¡™%–uÁ|­$Ç’#ªLZ:qŽÏËÃåYσ‰Ä3ŸÄ —j9¡œ–u¹Óö¡œã˜¯…SñÉ!óL©â”'ÌÓpåœv"_Çœiä¨Ú«V¹êlr^áU–ÃÖƒtlÖÉéU®GO<û¨BÉH<“Ê‚ŽuGc^Ž%§”Ê9sÈ|½CA€4‚õè‰g_‚y¿@—d$ži¸²SMœY : L ’r[åy°\ë¨:cî̤gYŽÃVàÙ–G=|µ^}‘>—…x¦Ç|­$§$MÕ!åØräZŽÝ‹v ’¾žõ<ØgªoAÉ•C4K‰x¥ýâËŸsùsgΜ¹€‰Ôd+9“SV˱¼Ø–öBT_éÙÌÝ ªÏJbÝÏ­TSÙ‘ƒyËÆ%ÛJ$†ÀkÇAúÃv\EÜvûm·¿÷½ïý·'Ÿ|r(éü ‚ * *8(ä¢(hw@Aò€À2“‰íÙR>¢ž~ÀÇïé§w®qnƒjžåzŽO¼œ°–ÃËÉåø >ç9Ï™óŽw¼ã™H+,\°´«kæú5®ÈÝuн¹ËJI8}c •G ­N"+zŽ. c–äD"$G!“7²šÒ)R9+«Ø”í¬.ʘsñxæ4¸¯°å‰'žXuï½÷þòoÿöo~×]wmA ½Þ¡0T@ÐLšzIc9Ò§IUw• 󃎞¯øtv–éܹã˹‰Ÿë qSp³®óýïÿ¯yÍk®ÃM»Eîq³ŽiëN„)¡ŽHJ“CÕÇæÄ‘ÆƒåsófbÄR–·,NZɳ*uG¶Lòfw*ÄâðèLpß  Tƒ'uÛ²yË_ýêWÿõÓŸþôâf㾃³n Ì3 ¯ÀÀó Õ{έL¨Äú§]*Æêé¥z®wž—£WËñó?wxåéðrzƒÓ§OŸú7ó7/{ö³Ÿ})îÌŸÄwnjÌ7ÿ/,Ï)/€R Eµç|ž–C$±æ‰<+Y ¶Z“„—î vNFƈԤÄĪ’ *ɪx©‚¿&‘*êM»¬’ц7·lÙ²þλî¼ëï|ç÷öìÙs5 9T^;„üÒ@y*ªó*#k‰e¥û¹ï|ï{ß{býúõ¼‰È]@Á€;í òA­ Àî”êåUÿ”„Åø?%Õ+)•몑Ú¨S’+©©uƒ“n`ŠVtÞ8[öYpQ¼´p~ý{öîÝõ³ŸÝý/¿öÚÄs º<T`YA€8êåÁP ZÙ$ÍóIô§Z¦°üSM³BŸ\Ç?´+T ¬µ# Ä ¦ ËL‚^z ÝÖO!…¢*¹^Ê ÊñéèÄÉéµâç«}rzÐOþó?ÿóg_wÝ+¯Y¼hÑYœšçæùTAž[v x°«ªÇ i¶ÑùýÎ:D È—FT3œ»¥µ-ða¿xàÚµk>O7_·n]ذaCØ´‰Ž¾9lݺ5à1^жx@ÀN€ýVÔû¯þˆpèëëxœ8à±c sçÎ ø4#,\¸bAÀýŽ€§7&xJ0ô÷ ÄÔüìו¡= #P\ß-`#]ı¶S,Sv'Ò”V¯^õð׿öõï~øÿð§(æA@ù|WÀrfž€ÉzË`Žcþ)‘2k?%ô¡¹NÊòÈŸù|«Ï k|­ö¼coÎgšzÕU/^øå/}ñ;:;go‰nÊIî96˜¤©‚µîÐÌÉ©™Oó MüR(Ì0+àP½aVðÛo¿-ÜqÇÝïÜ¡Õ8žò€A9V¬X.»ì²pùå—‡¹sO x2iˆ³B–ŠUDšËÞíht–ÎQ1 öîÙ³ãõoxßþð‡?\‡àÅï'ðP`>ß 0¯ËB:¿. ØCu7@œRže›·j$Á¥ +äôÄå«~¾âËùífè´â[Àg÷+Þõ®w_¿hÑÉg“¡­ ˜‰É¿1%|VÄ {B%q<$L9 x¥ÂWÃÖ¶6lÉÛž£ ?»ûîpÏ=÷Ø*Ï~ÇŽæP\ÕéX–¸cÀWìËEx’ÑvÜ\tÑEáâ‹/Æ¥ÄâÐÝÝp/Ú$·Œ;»Ç½h-³!ã ¬§|ÖUù½ZzÕêÇýÙ¿üì7nºé¦a#~ŒÈ – èüùŽ€ev ÝóÚ ku9dþ¸N…uk5’Q‹\'zŠŸù|Õç6_«=aZñ‘ŸŠ/Ýœ€'ÕÞ~Î9ç\FGåêÃÁ³ÁxâºïÛx«Ò 1€¢¨ps+;“”Ž¿×ë;wí ?øþÂ-ß¾%¬ÁöŸ(|smï„ošÓó2ášk® /|á ÂŒ]aÚ´in»¡«DG÷Á£U`3˜c2K‚†xa-—ˆHK€]Ò]o{ÛÛ¾øÐCmEBíjíj41aÛäÖ=nÅ7Ás”'”ã+T?w~®øtþtüèG?zûùçŸÿ<~ÖWzy-FݲE™óÀQ8'§/fGuåÇ×räI“ÿü˷¿þë¿®ð¼AÇÔ¶ÍÙ¤6 æ$íúrËÈZž‡6¾r–ó©rÐÜ%püðò—_‹ãáv½8,™îÆ]ß.ªyÓJÏ à[…BâbIרq¸ûî»o¿êª«þÕ Õ#ß èþ@5È*‚ì‰*åy᎘ìuÜH\Tò ²–y9?^+?—SµnîÉñ§Þpà çáx{GÇ ç8¼œÀdìþàT×Zß5-¬Iq2²6¬æ{÷ï k±½ÿö·o ßüço®Ž¼ ŸosÑŧ¦yP~>˜ ‡àÁ;m{›&j- Â*üÔ€‰7Ó½ñ8ýè¶6B»í ÙÆ1°ÇFkvT'Ú“ŸBp—ƒOSÂÕW_á2¡7{q3Ñl)ÇŽæf‡”X7½ Œn  4‹9§Ý¿oß®ýÉŸ|îÓŸúÔý¨Ðn@Á€A@€0ß p¸uI 5â w\¥hªãJf ›ËÎ¼Žªókå§ hÕϯñéüSyà…ÿ-ÁÇhøHÚ<¸8{du®@1‰Î#¾¨E$Ї/뇿üËφo}ë[6é¹Ò±Ó·CÑiè¼޾Ç©8aÒOÏÍ!éÆìF@X‹cŽÇ ñ^X‚Ïêí?ü>iOî ¦L™®ÅŽà=ïzWèëï³QôøäÔämãåAÖW nœ£90ð1f?^b²ú´ÓNûc (%/ xÔ ùn@ 8ÌùPçyT)šéø6J™Ë¬^7ù´â¶ãYý—¼úÕ¯~s[Û$¹4Ç,Í©³áu',;ãr`ת€š l7sý}÷ݾsëwÂw¿÷]”°íg÷‡ŸšÏ‚“Ÿ×ZNG¯”a¬‰+{¥؆nwê{AáþþÐÿmÄR2(öööX0xÑ‹^^ú’‡óÎ?ßv*ü8’ =ihcPaÛbշ΀$•OÒK65==¾ò_ùê{ÞýžC‘€Á@A€0ß p?D­èüÌsÈu°)(TÂ1ßð©±fÑðæÊåUž0_õ™¯®úZñ ¹ÚÛªúòÓgßúÝ[?Ç §í0¬¢š; ¦|ó¿ïßñ‘áô£"q@©sþD£=Ù7æ½’SO=5¼èE/ ¯|å«`ïÃÑ›m"Xc0(í€ôƒªsz;|íxï¾ðw_¼á†ß»­ù’îòAþI_€JP1)ãJ”çÓ€g·D V)—Sy9=IµÝ§óç×ú r|®üí¸ËÞþãÿøÝç_pÁóm3Éa‘¯ïÀ˜sûêÇ‘=k"“†‹Êíáœoü¶®xö~¤ŸÑCš– @>žð;ª<ýRß#Øü| ôý'Œ‹u—O;Ò¦ öiÜpØ À®´=Ÿ6„ÆK.¹Äî”ÇÃí™cÞ/!l¸†ÕÜ9Ü{÷=·½ðE/ü[Ü&Ø R]êÒ€@A@— Ü01—A ZÊ”hÔS.£òôæk­ú Õ-¿9ÿË^ö²S>ûÙÏþþìÙ³ºÓ*ûDr/÷ÅÊÇ3.\$÷„;朔ø¶_ÀK,Â7¾þ <¬ó®û[ñŒ¼æ€ˆ+Ò"$µ½¦%´\üôtúŠER±ïì ¾ÛöpãŸ=ºÌ€¿fz+ò ý¸Àï)¼üÚß ¯|Õ«ì±ç@§I’õ|ú´þo´¼ÂÛ¶uÛz¼ƒà“?øÁÖ€œ»€üF¡vµ. ´àÑHÓ¸òHøÄ'?ð¢K|Œ7É>Fãsþ 9/òSj¹[üK1µO’ª™€¹pŸ`à§°ÑtXpxø=ò„–±Ì/<ñÉÂ%K–¼w!,[º 4‘S̤ÀfôrÕ##Zäž8|Zó¹~ðƒº$¨î¸àê¯ƒŽŸ³!¥<ŸI&ia*2H6AVçŽÏ¼œ?ÿxO[~^ë[ÀóóïÅÓ|WÈ99dÊ->qœ„6JÅÉê…äÄäðû¿÷{vßÝÍ ;<p±ö䑵Øæ_ØZ¯ÅŠ6)WÚNœ†°À]ìVŒÌýpü¾# Ó3бñÃ9¼ß²xÑ)á#ý(>ii³Žl„ãŠÏK=ޱ%ó|¶å@9ÎêRuS¸ÿþûï¸âŠ+>‚|'P½7 K‚¡‚»´‘Aâ&EµF "¹‰ÏŸ×ú<¸êkå·»û(›ãã:Þ—¿ô¥ž´ð  Wp25wCãÁƒ€Ý@=«¹êtã•[¾ýíð¾ðÃqâ/çÄ@¾ü`Û›ñÌþ™˜Zí¹ Ævât8à¼Aàk­¡Ånò)À¶¼ß‚ñ±ñoxÃífáä)“äâ¨dÀP¿È”v±.Q þ‰'ŸXù†×¿þÓ<ð«MhÆ„ù%Aþq¡vÕ›ƒÜ(5lH6‘¤ s™”¯åüùÍ>­úÚò·_zé¥'ýË·þåm“'Mµçô©˜VyË&ïw§¶¢ã<8 kLŒ'žXþû>€oäá‹.tn®öøãªÏM_¿­úÈöÏÂ×`Ÿ/õ¼ˆâN¤Ñ¶@óqïä¾Öм#~ÓŽ‹Ãob¸ìre„/e­Gk‚ü!·ƒùÍA:¿v'³¥¼!Žå‰«h#¥l˜ÒUŸ«~uË/ÇǦ;t~à¸ð3ŸùÌÇðà žã÷Y«3íÏÑ`P0çå€3œÑ²[ÍÜmþü_ßþú¯þÚè€övlÏÉcåb\›_‡-Ék±5=mÂùi«±H§`œ.ÆB‹@Ûòp«90ÇŠŽ¬`øñ~¶ïØpé‡€àŸ P&sz?Aâ|O¨ñ%‘üx·íµ¯}íø¸p-¾S€¢ÓœDvÄɦ[FMî “)ä†Q¾–ókÛ¯Ïöy­ÏÜÿ#ùÈeÿã†>ÔÒÚÒf‹ :,›ƒßEa8Ž‚œÞóv«_>ø¡ß¬|ÄÚðrцM Ò°ŒÙ€ |O°ù¿âRa1&!ïVM¤±µfÄÀüþÐ{YwhêCÀÝŒÐÏŸ` °à`$l,ðÛù†3Ï8#LŸ¯v€ˆãÍ—˹[G|¬7ò-x Ë•Ï{Þeà»÷’ðƒS“RuÀ‰×åg›.¢Ú.«ßl£’Ë¡üpί§úRÀ³öoÆÍ›WÒ;9À6È4½•iXfˆðzŽŸ&1\M¾øÅ/…ïàa£­ˆ ò£>î&ú—àÞÏ[áÅÇD:vÀ}ùöì ­k'Ù½Û ðF!nñ¹&â^€¯!_ÿš×bì°ƒ`´° Îå™ã܈˜Hã4øÇG„·¼öú×þˆx9P½$¨õ >"lØËFÙÄQÐhÔ¼á—oûsç·•ÿ›ßüæ›®¼òŠWѿݩeáàÄÓ­‘JÏî7‡-Û¶…?úÃ? ¿¸ÿ~›0”D´œ–r`^yœÿ*`¸™HÇÖƒžø:ñÉøÁfÜ(܇ûœ>ñÓÞ dXµjUøÙÏ~Vœž½‘ó"MºèìUE¼Þ©—.Y²üüóΟôOÿôO+A«æjæSÄKÌç圦ÚNuÇ6‚0’A+?ó NÌW¯ù«Îßñï?üáÿ}Þyç=_§£»—ÃqÉ ñ×^§ ¼WEJò--°er¸í6êxÃË=/ú[úBï彡ÿüÔg"5ª¦ÞÞ¦þ¬#4ã²À&’>)ànãˆG¿¾øp“8ôáÆ.C;„% éãC b3Ì’ôž{ï½í%/yÉçQ䓃ùn€ŸYä;Þä„ျN<æ™â$Lбã|>Ö;™WPÎOHÙèü<ªó¥k~¼îÿ‚ó¿€ötÇG24KG¤v6àˆ „|~ÿ+_ùrøò—¿ŒåÍ$4²džA¸¾SûBÏ[…ghìD7ͽ‹{¡³†–­­¡u—Ã. èÀýó»ï½7

jeüZø¨Ùë¾®Þ°é¾Ð¾¦#´ôðS ³Þvá§ÓN;m¹=TDAí;œ\œ±œ^Hé¦ ¯b:b2­»ó®»ø°Pœˆƒ ©U[Š«õ@\ÁTcÇ3H±Òñsç×gýºæ×M¿vܬ¹ø~È „) 3ÊÁ-çéŽwú?sÓMáÁ´vÐÜ@víYx(ì|Ûö0Ðɱ9¾Óh9ûH¬p<„þöþ°gÅ®0õÉöжÏ/ ô5c^àUaáÑG ç­8Ï ÚÓfNîï̸u|? @á£Å‹ñâÇqs¿EÀŠü`£¼¬<§#óLšš‚ŽÃó± Õ•_[ÿüºŸ+Þ·èŸøÄÇðÆ6:5“ݬól2«®ýÍœ0!¿Å÷×7ßžÀm¤¨JY›™}í {®ÝU˜^<˜iR_ZN¥Ñ<ê÷d5ÇEP€=öž¹'4õ6‡öMS17°ˆ÷ø“fü¡–U«W…³Î: ¿ÇˆïäF> œ£i\$¸™xÖ¥Ï~æí·ß~÷† øe!µÌïú'|lIplÒx€h%YÏV}­ü”!wþÒƒ>øŒ>n°|Ú¿ØC#aÚ›ùÜŽ…Óû—{hZ:Æ¡ƒ‡Â'ñÒ¼ šcm,X[¿Á³çÊ]aÿ¥¼q{|¥ºŽO+WÑVí0ø7z08pòþÐ7©/´¯ï€Ùâ׊1+öà”y_àüóγ'EÍŒqËo3Q¶à„C²,`+"Æ«^óêç~ÿûß¿kË–-ü@ÎO¨¼±@™©š+A§£óx)"¨m?¡îøç×ýùM¿Ž[o½õººfÌ7§ÇÉÌ-Hë)oDàÃV|“ïïþîïÂ&ü,¶9n<2€$Û^µ%<“n5v:bg—µ GûÈMVwNóÍ=Ì;ºéfÂó”“A ‹ÈZ|üì³ÏF™X›}VÏE&¦zö¤)!ŽÖ––¶ç=ÿùËþæóŸ¿+¢ˆÎ&gX@áÄZЈÇâ4^@Š*°oú¼_×ý ¶õÇ—/ÞW>]H3ÑÜÇÝvÌ{Ùmè7óð¡þÍŸÿ¼½§Ï¼_=“ù¾É}aËk6…ž…|>£±Ó ç§.:ꉞ×3?I}Öê£N]#Þé=aï¢=aúê®ÐÜÇ/!ÐáqY°w7.ð=~­ØætõÇÊãŸ~6,q&š5f̘>û…/|Ñ\<-È_#ÒÊ/HRå#'kÎÓX^ꀙ±RBÓP+¿œ?¿ãŸVÿ?û³?{!^ýZ ©m~= X`€ÙìÛü€|5Ô·þùŸÃ#‡ÌZbg£(<“ V~BöWoëßÁëþ«^tÕëéåœH„ …„Dص<¬jufl„‚þ^¼­÷ÿÄ`À¡Šõ-æÖ_ó$emŒ¥saòa–x²Z^Îp¦»Úex‘ÌëëÑ”Œ 0ž¢‰ìX”¼tžbJĬáˆGª´uTN`TãvêÅ=}'î ³VÏN«?þŠñã¸)xÞyøÑRWʔԼ3‰£Òº"ãôåw:§un¹í'·é~€VùR?›ò™¢ä&CfÕG—«€¤¢À:ù´úOY¶lÙì×¼æú·ÓA|¥÷jÁÀjÌùr@çÿû¿ÿÿðÝn88záxèÃvºõٛÅübÖ±OÔ‹–d‰¥2!Si¥-Q—¯´NHÚGªÌ2Ïâ^äIxò©UŽøŒ•ekõ›äg›²¼¦yâŸ1¡²²l–ì&šq‚{çí ë.x"™˜Œ‡ _ÿú×솠/V\¨ !ç)“—ß.Åw–€ÓßôÆÅ‹ÏBY;€ê.€¾ÁCV$ æG=v9d>w~=ô“€ö¯þã?~ïrïì§“ËúŠ.›œŸN†¸.û÷ÿQغe+ÞúŠaáÓ\èÉžèBÝös¶†çìuƒ C›j(ËÔ-kƸži*ˆ^|ª¼TG¨zƒO'!²Š«UÎèÄÇ s©y®UqìÁœ òTmÇr¤-glÏØàó‹ò@.¾e˜o¾ãŽ;1[Üé5?lþrÊ2@€;ØÌO™<¥?'÷ßÁE¸3è2@— ô™ªU€rÔYX‰fÒ°JöA§¯»õÇ{W_xá3¯‚,àte@ð¹J#’-Öxxú¯ø•}¹§ßïW;6&Åþ¹ûÂúç­cñ˜¦AŽŸ[‡ùT†Ž±lº«NõÔ"Ç•ðl „­®¤Ãfy‘Ÿåq¢ùHªDžÄ?£%RÁꛤ'ej¬2!’ñN§2 ²ƒ bâ*<žiÏüÝaú¦éaÊA,ØÚo6‡õë×ámÃÓà ø*1'!Ÿ¢Ù|Ô˜Yˆ„ÄÓ§OŸ9÷Ĺ{ðÅ´5Àäïà„W™ê±…´´<ñ£šFs@!™rHþ:éxpûÃcê5W_ýfÍÓòöÙŸÖŒw»ví ?Â{ß§LEsÌfÝücÏÝÝaÍËVí±Mƒœ_âÈ:©Ì1.F:Y/Õ#SmÃ:s>å<ÜùéärtËGR§ôBž§ªžP¼(‹ãu¶Zof29>çcòWuˆò¥Ö@²V5èU[²­c ~þCáPû! ZºöŸÚljù¬ÜÔ*b«m¸Sód »éá:P_Ó9ê`–¢cÛÅôÖ.¢¬òöìi•H«ÛÅÙ"ìØBô¹ò¢‡ÃþN~“”—ž>^œ‹xQ­_–ÅyË·ÅÌÜRO«ð2ŽuííÓ¿ô¥/¿H]ëúŠüF~D(K![ʳ|D‰L&åÃA>|u#ç·m?ðSÏ<óÌ9øŠåóÌÙ5>ãœ⬒Cë×U·ßv{8Ô›0Œ+ˆ¸YQâS[+_ük7”`¥ãŸ¥Ü•¼;¨r|UN›ä¤ä&’;§ÜË–­¶m rS[¦dÔâ™(û¸Å2+2rKå¼®^°F~Jc‘áÆ$ ¹¸üç˜{ôl}}¸ÉîKÝ}ÏÝеz/ÀË~Ð¥Äí@›ëçŸÞex#¤ À`@_Ƀ@~/ · ÈJcù°ÒÑ€¼3 F~<˜§Ð<ò•ß¶ÿÿ‹¿x;oüqgƒ¢9<·I…ãsÛd®vìØVþf%˜‚-¸“9#.'Ó†s×ÙW|×TšlˆÉó¬­ø˜´FWÂWéXŽnntÈ#§1¾bKšN¶C‘’ÓÊ^0½R9jIýe’¥zÏk7à»³Ê çN›Åz!ÆöNê «O_m÷Ÿ(ŒÍMœüñ°s§_¢ÙèûbÏ™n’Ž“žs ÝäÉ“¦|üã*yídþ˜" ?¢UrÿBñèm¢`L‚ä)¡µú§kÿß}Ç;V,?ãŒËü Ú¦áóÍñ™ÇÑÛÝð£ŸäïNÙ`ðþ=sw‡Mgn´ºñ<¹”±Gi-˜ b¨ŸçIg“>N Ô™ Ôž´µèµ„Š®Á _¢¸Nƒå/ë”_˜²y ^¹þÑ>¦nŸp_«›Ó“ËÖ†í'ìð €¾õ|À¿ýÛ÷m7 O¶l>ÃÙí™íx-0p޾zì’7½éMgBJ­þ ôíäOòWY@ðˆÃ#aÀÎ%!yñ`žëFÑl2¾é7õM¿ýæëyÑïÎî[#–i }äg7ùÀé!Ü\¡gèmÆxþjÏê G7ã› ¡wH-y0 z ePg<á”!=r><¢Ëùmlc,bÿ–o¼“]P~Ó!êiåLîToCk´¼Ö7ŠÌ6¦]FKžn+òÍtÏóÆ)#‹ìÊs½˜‹v? rðž¿­Ê‹2Vû¼Žóû-n¶ÛÅâgAóþ­¿ó;¯€ðcAùüˆ>EMs?£JÄñ8¢t¤ Ú¡Êä§hE¨(F8?ݽpáÂùgûàÐP~Præ­dÞø»¿ãFõ|‹«òØpúúp¨“ߢ¿ärWú“æDÇÉ[rhÃgm4Ñ2:µ'TÞ"^>ÉÍ(N¯ 3–”mÂcÙL&1²ÔÑí†ÌPÎ6Åæ&Ò§Á'·/íãv1 4)AjŽW$-p°ý@xbÉšÄÎIGé<©ÚË弿­‚9¿ýàÜ'ÞƒÃI'Ÿ|Æ%—\2˜ü2@—ϹOÉg¥1šXª–…ŠÙDÃT²cäE(a)¼0¥ðfß?fij‡|´ 0ÃÐn7˜o—¾÷Ýïù[Y83ø×Þ°îO€ýø¥Òd¢–L‚–wŠºÎOÚHOuJt>…'€§hE#È>1a½¢ÌrfŠkù,ðÕ æÖ€96ŒG5r²_U§=öNÇ[†œ!7/n»í'6¯¹«µ­Ýór‡ç<·¹nÐ"D¸é¦›n€jÚ(ÈäW´ }Mþ†ì‘§£ „IÃD^RP[Säƒüà³ÛÛÛñM(::îtGÐïQˆÆÐV ßäÛ¶Õ~¤Ÿ±rñ^«ÒšsÖ(;.Ð];v%1.¶¯™8¬M„äc-² a“™Hæ½½Hl« ö õ1¥ +äMrSªüÔ·†«­ÙA¶®äS²e Éö -Ÿ2üÍé@•âþƒÁîÝ{ÂŽíø½ Ê ù®| ò†7§°³ë½ÿÏ{/B•œ_;hÜ·ä·²F‚|dIŒFFíTêŒ%æóƒüèøŠZTĶÿ/~ñU×XÄã͸íq£ÀéùÇHÔÚÚfOVÑ(œ5Ö!!Ž]sv†]'îDÅø¤Ò䑿‚“//šT9Â&§M·”ÄDcÌ,˜![0Ù$Ïiݰg» Î™Sš.ÔIzQŸ\§œ>«#½§2¿rÛH‘õ—êS{ÚSÏÑ…ÛgoÛgmƒ^èÔþÙy¸ÿ¿þ…5î|!ïwq÷›í€mîÏñ5¿uÍ‹ÐP»úù!}ŒÌ™?ât$€åJòÒ6EÀœïø[<þ‚³LJs~ a["@s|’ÔC=v퀓ƒ³ÅÔèüý­}ááKøàÔ1HÒX"`Ò™ÌÄ /ÈšlR¦É\¢%ëB>¢÷¤õÑR«Ñ‡??ï¾Ð×Ú‹.!É„Oªðû®|CcSÜf½Ïy®p1päÿI'´ü¹Ï}îÉ®Þ.@>ÆtPë‘™ÃI‡ªäQH‚iëoÎa&}ìÆ?@'·»ý6 qշ݆Ţ¡GÆ^üD3?óoÅ{þ|÷wÐoã)B_¿19>)Mi-Èîó¼ÄÉq\ ˜Š ê(á½ÄY2‘ƼUX¥Ó°Bt©®Ñ2}sD*!¹+B¢Šz°VGR:×=æ“ýÄF4‚Ä+?L‹±€}X ž\°Ö”Óý*ÎãU«W‡üDwú“>y‚É" ÞŒõnภH>„¼üŠ~FmóÅ”d‰„*s¸ ç%ȃB RPî(ü¤W¾ò•˦͘¾ÐWy¸t‡ÇàÓ ’a¨Òºuë¾=ûâxº.vgÑ`õé«@=>ióçÝb’ÙŸ,À:Õ©l‚¸äƒêãä<ÌJôÀÚ]â–KÓPùâF˜ëŸ"–t1ÛD»@' jÀ'í")fzƒ>¤,ËL‚È*PØãÃV™ÕGº¬QŒ*\¹÷$ u:°?~¤¿PÅžˆ°@@iâÖ?÷ƒ®®®yxùè© Ô%4}‰>EßÒB+Ÿ#[c x؉ 7©³2Oäüéàú믿 7Gšïø 1ü»c Ò†CCŽy_¸÷ž{ì+4Š¥n´GÎ}(ô·ÒLcŸÒ$‘†ì2ÏWEÈëb^“ÑHUo:1QùŒ­hX•€H§#š-g]=+´Ÿ‰‹szà»7u[`õ ï£ÿ`èÙŠï“XÑqä“‚êGðL;Gn¨T£é rа¿Ö™­îˆ(»ˆ1c‰Œ¨_41I˰ŽÈ‚F}š4œD2^ÌºŽ‘­áp* ‚7!ÇWûJñ-ÈçS~yúýἕXvûKã<æÏǧ|ÎàŸ@>ÝA«oˆ´Kìšð+ØW~ç;ßy rÉùåSú²¿($Ó™Ub¹b‘¡5;œ ÎÈ‘yZùó`[üPÇäsÏ=÷*:¿E=“æ·í È[Dp—{ôÑߨÇ~Ô€Mì¹L„SAço$vÌ“K‚nªÚÆžýË)™%:“¼v[sj'æ¼T³Ò5däÁ.¬™ÂúÎs:ì0 b}Æ ÙÄ9 )ŽªQçU<ˤá$Z–# æbËëÌ931€6~IV"pXÈРԙآl?Ë­6™ÁœyF×a[ÕKñçïJ¡Ìë8ÎcÖÏ]–¯=#têŒ;^ºa‹K¼2@gøEÁwa¡‹å\'d©Û…^xEkkë—ñ, ¿uÄ€^žï¸"Òÿ¨yÔ ¹ÃHl|$É%.oý( E­›o¾oøu…¨”ÝýÎo·B8šH@‘(¬]‹0Â8¼öçACá?¬_´Î¾ôCÚ±LÉùóN(‡'T©¬ @MVÔ»VÄÅz«c!kÏ¢êI¦öDRiü›YXÅ|,[äåYÏ”ÿÞù£ “xx)"Ø>!˜Ã‘dŒ(ÇG$F¤%ЧˆdÛ˜ ç4Y=±F›J½hœ.ò}„CŒ¨:>bøèÉ+a_LrLg.j”qÿþ}ö[JŸvŶ ¢_°Yg~Ây+Î{6Þ78 ¹à%€‚}-: rž*Ú =4$“‘$1'­kãíåøŒLÒvϽüòES§Lé2Å€´øFŨ(ƃÆ÷:*gþ|ÈÞ¹R3ÆãÁs jüRUSö œO¤L ³°ÙÄ3šHo”‰WÜæZ›ÈK¬Ô>Öm«`lë—äLûc6Û6£hòe퉲œ.YlEŽeËG0`ÅBÈE9¼ó,¤¯Idõ¤ñîÒ™e᨟x‘ ¶1ZÓSL¤)ñ$½I†ŠÄÓóFm¸ØÎúªÐ¡Xp­UWªÁè¥ÿ\~˜ù<æN  o~ì±ÇÌ8é9ÿmUÄ9ùD D´w´OÖ³žÅéør~ù˜vôAY¨d-æë¦‘2sAuÌÛ¼îu¯»øfsr*kŠAᤠG=–·nݺuÓDîüì “¼?éµmöV”Æ>¥I¦®¤%')§Ê¬Ïó‘>Ÿ°†202àÁb¢!6áYO„OTÒ°È–tºRŠt^‹–#½è¬ ÛYÛØ¿\Àð±åÙ^傟µÞ³£(3jËlÖ£š“xYs9t„'oÚvýoœá°Cµ72«âT7ZP}”á@Ï@èºrF8å#'c|£;I7MßÛ@IDATˆD©8¼I<Î ÒgdF‡0 ZMRÕlaŒr>žOu(JÆ Ø*D8ºˆ¾–¾ð؉†Ó¶ÂááÜ|¨þ°fíê°hÑ"³…Ô¡l¼oÈEÐw °þ.¸à‚‹ð*ñïvwãçŠ=ÈùõLµ“†#Rd¤€L™Ô¡¶ Éùk›3çÄήé]'ÑÃmÅ’ Ú=@º9/ ˜zq=´aý»ùg¯3öøR¬°–TŽW’vêÏÊ.£P&Z* ®Ú†uæ´ÐÐ Ë©A¬ËÊ1ËÉn=%Zdb{â“sU=Ûz¡'ùKÒ™zvô&},ÈCLéçjÑvTÚ°¦«éiÞ+b@ÚÃ*Z¶„ÆÊ<"ÉïpBØ#¤àÊÙß:ötò§ÔÇ6Ñ襔´5* ’˜ åÔ2¯Cµ¶ý%¾V !lÏrBóŒÈѽÄHÕÓ–+H¥ÃÁSLó$êH 5õƒ>ühÑP,[ ëP’8Ë;Êi€4i‰Q[‡^'«.N‘—Ú%ÚÈ#2.è™C›Aó¡LqÄ¥]í¸é‡Å²áã óþÀþæé}ªúó2ôü^Ìä×½öµç ¹œŸ>'ç'kù¤uƒòˆ —ÈIPÑùµúS øyïëô?•‘rf^SŽƒ‰ÛÿM7!/ÆŒÞÅË~aø±<¥Á–V#é,›ˆF®¶„±ŽYj˜¬Å’ê†A¦Xgy c…ƒ5c&/lf7úÆ<¥·›ð”‘«us»PE…ýÍZ Ætõzd‰b1˪¥l/š„‚M³ön÷H%|dŸäŠåÑ÷-ºz³3Ìraû¶íɶP¢–¦à¿Ÿbùꫯy)0ù%€|N~H®ògi%Hn5“Ôªª±‚a çŸþœ®™]‹hDýA#Ï"ÂY y:û~AÖðHh˜¦Ð‹;§ëæ>YKžÑÇå*oNŠ®R9v[Å—¤áp¡þ-§¶¤±|¬GQÎmœ×X8¢y&®†àÄáãâœÒ[%¤ôBÙ2”îI'`ìL<“Õ¹%ä財U¹ÅA¹Ñp)W*{Eä–¨Æ#³föêÐÝŠK7ŒŸ‚æ°u;>↹_Ð?´hr <.âå¡|}¸V€zÎ_ÕºZN*Ît"m~¨sE¢¶ë®»î™¦ =?pubÛŽw9¨ã·£ÒLÞŒ±nÎØ;?\J2&‘M•KD^H-E¡x²X›Æ±NŽ¼Ú“m6yš¸tOåvŽi¨sz#1½‰y3]"ÊN?ê5! Üm$[AÊg‹3H·GudQÀ2/á5–ÃÑÉ=ѵ]¸|à•ªlÃ30–ð‘ ž•ñÝzD?âÒùòk¯=´º ßÉå›ì@š:ï!Îl4’$†ê„0­üÈ›0øÁ4išJ™y-C<”â­AÀMø™¯ÖV|,‚m-o‚x¤Ç3'¬‰<ãC#­ã¤K“-á 1|5Šh£Ò¢Šíͪ3ºH :Ãq F¼ e0ë OÂLÙƒPW_Ñ¥£i›.1Ñ.…nEN6eYg§ßrÝa•ò«áȉ×ÌDÀD±û]׿–ûÍ ®ò¶Òà ô 3€ßôËh.œø1g)Ǭùôˆ4ñpZä̘·©¨ `QiΜ–zô€0|Eg×6FoKíÁûþéÆ'^ÞòàÖ®-À]JSÏ:F?‚ÈÚ4R¹ ˤQ@¶ÇH+è«”q‹tÞØÈ,˜Ä”'µ#µœƒtU3ÙF:bEÏãšóK„/ÓÓ».¶õP$Ó%Y8a¸SpÍ6¬·3‘·hŠ1T{ÇöÞŽ6Œuâ•`Ö†ÄÂ{ÃQ=oš/yщÀžÞžÐ‡—…Ø<°Õž®£­?làÛ«Ÿ;Þb¤!ƒ}Pþ˜K¯¼ ȧᦓêPgŠ<çÎÛ'çÐÉéüîììÐ Ì»ÿ@B!²¯ü¦Ãs’ûgžMa>.9f DÊ–e ü¨Á¿i"¢Ê„JN,|lc¼b›b5§ÈO;ÑyêÄé’<4×p#–ˆMF—ì]wÿM;Q;db^õF‹“ëÎsÔ½Ãä¶%“ñu„*{eQŸÊYÆúËÊy–9iåìG s\ø°êõc1ìƒ/øÂ =è?èÛþè/,G8­sÚl|9¨rüÜå—´„ŽaU¨7dN23BÒ 2O(LÛ 7Üðb—UÛÄ8½¯þÜÏ0OÝ›ð¦Ô({bÙ ø›0Îx$jÀ$ˆ¬™]eA#ÒpÄA¥¾\Œ%MÖX,VÁ¢=Ug¿FR¢'u±žy_Qi@–71˜3B9s_è%ÝMŸ¤'ë©#§zLÒ7ÓQm©·¡U'>äCh+©h¼­Œ“ÚdõêÚ`ŸIV";šÂC'÷÷ìÙ9ÜgÒ–?®úæ7X?mqÍ…Ï|æ³!^¾ý—Ê/©MU£j9i8TP£*TGŒBvà‘Fìþ;¸°ˆWqåçêÏ?üsðàAû•_Üýó ÀI€cWÇØ¿ê›F®•,P©¢ªm¯r„$/¸2GŒ;@ ˆÇa>:0LêN9Êè"&:Ÿ/Ó[±ñN¸ èv޶ ü&:!qž<À©Ö‘ñdÂH2sÞFõ‘(‚dk£M%Y{â2žFbí 9*ÍÆ¤¸cÊŽÒGßü† ¿ ËÄÅÀýVäÇæüÆ9óÓg̘‹W‹ñtüäƒÈË/©Q´J‚@ÕNCµÈªvμEŸK/½t1Wq X$äQæö…Ãç“"„½{öÂñ[L2“\ø)ÀÖNÜü“Ø“QË©ÿÔ‡ú‚ÁMj•³kâm8Ò—'%?kéœDŸø¢m}³ž¼Œ ò5rÃîÂŽdÄR_ãŸáÀÄ5Ù¥e&‡Ù'Òm ¢-€c+oi^Šöqæ´YAUb£&H» %ú2ôQ$Nó¦L|ä%ðÝÜÁ×ÛùêOÈGƒùâPû íÿ]7 jæSÐü Š\|ñÅ Á€Îoþ(_$”š„Ê#k©Z®9¡)qd®”§­K–,á[ñ¥&Ò®û¹ à0Úö…Ð#Û¾ý{C ¶±v폒ùÆãóÎ?t5ÂäÂÏå&š%gSV”ƒê Qp£ÎZÕ½ 0D*™£°§Ø&:€=iG'Ã#·¥#Ÿ6*`D8Î)ý0H¥__¡©`¡g l Ç`oüÌt7›Ø©ÂQŦ̕K‘Æx!_‡W¹øŽ\×±ÎnæÚ܇Lü(üà!¼ò¾bq× ŸñÝ4ð ð3øÛ|H§ÕŸ£,¤†yžJ˜ÖÌÔJd2TRcBåÕ;¶cÙÒ¥ËýA”£`Ñ üÜßËÄõ‡îî›ødƲíßÞ_U¯$M¢S%ÍÞD.¤IxŸ^ôåb¢,ÕûöWE­êÎ(¶ˆ•¶ò³¡Êà¨vî¬+·!ŸÙ¯À[Ïm×Õ„ËÒ>~ GBY;È‚oçõïõm¤ËP>Sž-ø"™:.W%‚,ö“ú#%pø¶bûSL&»iIoË“ÈQ^Œõƈ„‘1rVŠú°Ê ·a©^öúŒD},Á€‰ ÊíªüÅNéË”18êÓÖ©[ÀnDL <kŸÐÐ@âð›¾1 š &´É²ü´Ó–@ˆä1/¿$¡Žae.™1æÑ†²Ü:wÞÜ¥6}mûâó@ÉÑ Ž®Æë6„ªP9Ô­›¾¥qHÒ$ë ÖHù*”W“­KfŒ¢-s^Gjª\$2G6ÊT.Ú¥ Û:癨ЮcE;¼¢á½…M\”Õž2 ʳޘtÈTëÙ&vLÙÌI*¶·dƒJUïgч÷å-yF[3!ê@¨~g¯ã9bØÚ¨ÞÉ—ˆ¯ÍÙk¼ÝØŸŸèXXl€A Ÿ?ÿ1½pb_‹C†>‚’á\öù ,†„ù ê—Æ4‚u¢/•Ä ‡Ì—¢ÏÌ™³Ná ‹‰œ»~$÷¼»ÎÏoÿQa:¿]à2€H6L²ëɤNš³äzPVNdKi†û„æ@Y•ð zkÖù0‹x"KÎþ¹¸—9x¢Î(ÁÇ[8ŽôÅ%DÑ·çXç“Æùyq°ÉùxÿÎ×òÄã0]H{öq³žÑ(B釲lâ»2&Î%ò: dmØýûɪLãL2ë!6‰"Ķä)TÏ"e¶$¹ŒèÔÎû*ø²>âœtLÏ«¦=îÂB` ð•¾>~­öb Åá7é=Èg‹èìÙ'ð= %?D™ÚåŠe ‰ÈS½ 3Š–eÒ*Ú˜—?çòù”—°lÌø'@"íß·ÏïüÛ=0³Ñ—‚¦mp‚1:»dƒ™SÊ”jiœ*‹Œ9FQ,rh/%¾ ˆÖpZ›ÀdNÀ o›1a+1¤Ÿ G,qäES²d“Šx&æ­Îåp$&û·xkÖÂ9‘ñ¶ˆ7ŽÖ›g­ƒØêYc2ªc@Iá,œÓ²äDÏ"“Ê e–é‹Tç2Þ)ãİàà¹zø ÝÉ*­†-®oº% aŒð#-ø„Œ;óp¡oy@p¨ñ¸è¢‹æ¢:ôO–)±d‡Nõ[IuAÒ*(ßüü<ÿ ¥¨Eè p'À- #7üöZä&¬ødíÏÿã×Z…žþØÉ'i‘`œ**—ºÊÁú¢·"JK%LÁ\Õ£ »›»ñíÀƒÐ vïñâX¾ä¿¥é;ÃAzó/\*\yå•g‚X‹±ü‘R+Oy¥…`ŽcÞˆ-O9!Q,ëPY¶,]ºôLÞ©T¤ráQ­é‚ã&h¨ 1ƒÂ6 i„¾¦±{ñgœv”½’ªªVª‡(–&J²1 –Er± Mdsªœ¯DˆNá[z¶sW*xÐÆëØ ›²?KV`wÞš“¯¬sÙXgr ¯¶€&g¬÷Ý™$p>âKfFk\Ù¨ž­¼ò3¹8Ñ#¿“=P¶2I©H e2,Ó§¾¢þƒèˈ¡K’±BUUGXìmî Z¢ýéÅ÷¸í§rx:–ùæüq]~ÚòeèJ;€Ü'eEBu¥bÑ$2"­ :l9ñÄ—›I@]ûû5 µ¡à½¼û?î¨4wœd|¿¤2®ÉV¦6L‚^*M^¯·éYà#7¤V6fóÕFŒ öEŽuΕ̘ÒÄš4½œ)ä&|ä œyçä¼=à²/&òó6ÎY”ŽK²q "Ê×kœÑ/×$KIõëõâBYÌÉ)¶Š0â"q$1:?Ñ"n–#2Qvƒ^‘ꌪÐH|²Ö†*l$=œ²è$–Çðµw[ K÷úŸ°Oâ. n  Ç熚þæ-˜¿âÕ òUš¦0O9oÚñ:¾šª r&bl`ÆŒ“ñûeSl“bÛ|gÅ)í0×FTã·žšqãÁÀ'…×onÇ+’%EÍ âÔF2RQŸ¨Þ.oz*MÜÈRØŠtœÈ…Í‘"ˆÖ1 „½ÿ¹/xÔ·ŽÎ›ö} hR#§6.-â]ð4yŒ_”-æù\Aódð±æ`Ù4MüYὑ'‚øÌ?d$‡½yFK˜zêd&a\èë& ¤¬±lµ’] ™ÓQ]ðFÁ> »D–2 Ey\ðØï úˆY‘C¡V¢&*ÙU‰W­G†Û0y}8©û$ëÀ|õÁšø :†—Ñ_¸Xš=i3Ps|ø»›Ó¦M›„LjÍÎ!ÊÕ¬«A­ •Å@0¯ëŽ–… Nƒlø9X4A £“Ÿ\pÃC2äW€íc¿¸~Ø ƒcC€(+Ä-Ìg‚Æ-*k!' õô3‰¼DLQ"%´‰9w£}„lD<ÉI ؽ©'¬ùO„ƒá¡‘±J±ßÄž]#øwØÖ2MŽw™ùÜÁô+¦…%7-6ÇuáÝMk府¢¾Ñ"ìžwˆÌNÄ©26vºÜÒ$–FCR½²¶Ä3Y¢õÞÆÛ;Uq¦4.W;šÜ¦)øzðôÅð€ûÀ‰x@§äÂ@·²Ë'føfa—‚¿w lV>)¬*"Փص@ªÌ2b$ÈšgÏžÝc·æ×&lã×,>T†½ò#ÀhB»þƒB|(bÌ“¤¤Œ#î”8 õÛWƺçȼRß@V3…‚—S©™voì[çg'I8< ¤Ì5*ö‹¢€Ýü†.§)Q¿òK?3Úd¶ÈF…Áúû»ñQ ¼›ƒ„hc¨ÈÇ‹†¶½¹e«'43>„äaA”-#ÏÔGµ¡•­uÍšÑDnšÌÁîŠ2Ñø³´ PØ/ã–ÓLÀ07Þ–™]3ñ;ñC®ü¨:Ñ‘‡J&ÍñQ68£««[Ã99ŵ``âC`s~n_ð'#­ÎÃ5¶<¯ƒÆ*¹;Å+'…§AU@°–x£Ò,7˜ÆÊ ÈÂ8?—Z¼ÐÞȧ­Ù Í›±oôÔ€ãÌÓ ‰Ë’´²ÔD ] -ÝJ¢'4LB{iÌ„P|„Îùx¾~Í`ÚÑÇhÙµ!u0÷Ÿ~8»•éüLô'À–ææ–ÓgT@u'@‡Tr¸`ýF&bÆ6–Ÿ1}:hÂ-<Œ#Gá˜üÁ æ¹oA¹×6PÒnþ¡9Þ‰oE³T5 ÊÑÌ5E*ÕÉÑ©©«¼€"½Ð6ÑSŸ´‰_Â;½]“¯2!Pð:\LQlÄÔ¿_·cð’ÒSòK+“–q«ˆDe*ì%~ÖË,²÷¢ÉàÙ¢]Ñ›ÕTŠâ3Vp{Þ lÏŸOÈáý#tØ’Ãß‹°l¡÷(u~PÜ\“<_R¥VqÎùäø1ß2oÞ<{é‚B`Ù?¿d?Ì Ðé)'5Ž“ŽM("R KÇ:ƒ¥v6%SÂljžØfí™;8spvžÚ¢ólÇöD¨<²¼Q×ȉÀ$ÄÉa¡ / mç*k_´!Ö¥©EKÒˆWA6Å´­­úã·ÊœÞåóJ~† oaUf1)³ ‡²WQË‘ÙÎì—µ!E³ 4óÀ]4/S°T`Š\l]•"uMŠÑCîÀÀ?ç rÝoå/þ4`îK1Ïé¸ãŽ÷ƒuAš¥zÔ°làHÁk¥œ¸ÊœmLÒéÓ§Ïdc»¡gûÐ,nuÖŸD3ØÈK¥w¶ýK@(V9ÕŸ©&iO ŽT§D)D„ŽW-›)ïE‰ÓVsŒö³[uGCÃ’‡Ý4#ìš³·î:ÏÆ:÷ïÓ%åŠÎY±‚]ï²–Ê1qR€-|ŽÏJ¤¸ÓFNI¤JÌ×Obáô‘®@Ѱ«œ(ŽTŽ:ªüŽ6.‚îdDÛøwhÐ?þ-Œ>ÿ§Tv™ ãÁÿö]û’¶ÐNA ä‚Ñ NåNå;? #n-N ›L«‹¾Êô*UéËxÔŠ  E8†pwË.xPÎ.‘©dô'[D±¡rçG»lÃAaÞèèèà›$}UÚzx£*¨!¡6R¾yò”)Ó(mYPaAi±xeøÒûƒ‚ÌóóÌÞ&þžáØ$ŸBƒy—& 5É’&Xrž‹ÕеàW%«+¤\Ñ:qŒÎ…“{â)3ÀhLäÑÜËè£e¿½—zǐ˵‰êûÍMÈ«`›ôŠ"ÚÓ§—ÎÔOö«¥+{ªÔ«ù™, ,q1XS³¶D:V…^<o¶,~ŒH‘¸{¦íz–Ç0uêT¾!˜©ÀÁ CÈ$襬ÚW~HÂ:&^•X€ù)@.<ó:F$êp@ÌsȼE<<•ÎÎí¥³@”Ò¶ÿŒT&<&qvóÏvàÒ7†;€zÚÓ€ƒ±>IKæ$!ä¤ÂE;–ŠT”œ‚åWÐ)WÔ‹£»;ëSàÕF5ÇÃM@“JrÊzpD!@Ù¨Ð4®ÿ™}É¡¶sÚ‚Ca%æd½ EÑÉ"1€lÃxjL<©ÒXÄ&Dª.ÊDbµTô»Àå0_ƒ;ãæü²´Ý4! t&-´X€ùkÁ”N~̼Ž 7ÒG«ÌÌ$øØbRܧ$¡(hz(kŒv#ÁÆ|ÓѱØT)Ê&UV„±‘LQ@/ÜÓ*çD(1¦}ræ¢[29?Nf9ºãyNü"J}³hVƒÍýc@ÊÊñ÷ï}P#Ù@Zñp‡¶‰ma1OÍ-á´h€ŽOv±*ÕËmܶ¤W*¸å2xmQ'êVjÕ<'9Ê<~æð“šðÁ_š —«fÔ7uÈG秦üdÈüÏI6gªBÇÖ9+rÔ©NèZL±«ÇéÓ#’C_ü1TO%ìæ•)®Y!¼ÿ\$e|zhW¹ö˜;VF)lî­…÷)©RNœm/Á'~É»hÃ<1&Wˆãa`óYîZhVèåuësò»mý̶EŽ%Y¦ bNاõózâyxR®ŠÞ{ãYæÜM̆­ã øýŸ¨tɆ¹mܺ²°k캲Fxqn,Ó- k¸r®E=|¹õÑ”èü†_ ‚œiaå vFs1 VÉgirÿÊuEÌÔ%ª[.)”Ÿô€ÛÆh_—ÿ¸UP8*28Õ¯qÚ¢•çxf›rrŒð¾¦%Òjê»NA|Ñœ1Z)æ³²ASÜÑFéÊN/‘©kh/·œ×x>ǨEY+ë©U¹E¹ä}í•«‡W}µ—?z9].ºã£ÇŠ€\»ÏnÝ¢Ì €|1gêÊS6S ²#½`¬ü ?NVüYx‚Pqà}¹°M-œ¹…·.):þY<‰†«—×9¦ÖDa ñ^'eŠ!Q½÷å| W8ZÍÏ…—²h3ê9:=˜Êùí¼à2V˜8õãÒ .ºü;Út”` ¯¹yÀ^¢€QŒÒ·ÆÒgœ|»‰½p¼w²ç`ÃÕ¯©E+Wõºò@°äÜÊ8¹¹·Ð—(ËÈåaííAƒBmEnª8d:ÒKcŒ3°¯ù[ã`ÞÖùèü°Õç³ÿ¾‡¥ó{°>†o¼*)µi‘uHyk%â6µâm[KUŠHŸÐyÞ¤¥8uvNŒãá€7¶]g­Ì…æEÎC‚´^]x‡ª•m4ÞsŒsêê¹ÌËKe[ ÆTùŒN™>À`cK¿@ÖvÑÑ—L–Ì·$/iúùk"E:"‘GÄ\ÐäêíëÅAá/Þ˜Ð÷,PF0£ôèÆ'€µÍá„¶àÝg”4é\æ‘IV« µæánO>tŸÚ,1ùíPáßx¤ "§‰x'÷3 xì\r×Yâ»=Ê:¹EŠÕÞhIè1zû’•3›‘XœÕ+[oÏΡÂuƒ1¥f^̮ё¡Z°?¶Ý0%±E’«?:Œ‡n¨šoѧto ùÞž^ÿIáZ Pœ‘€*;3 Þ`Ê_2p“ѽŒ³)AÁíIA³ttêŠÉŒPå{ŒÊÎlÊ8ø±6“¯ìÐ^áÄtîÁôf‘ÌJYŸh[ÏûYœÌÀØæH&ECf=ºÙ¼°pfö £Kg…FÚDzº&y¹°!i {§X™)OZ§ÊYÖ{ÉúʲY%%(©Ô}±‹ ˜—Ð[÷º…„Eø)s,{Æ@(k˜ªÐ±uÎù=6JUë:ë ¿»»ûÀ”©ø1ÈXcÃiBº 6˜ñŽ%W~„:ùÖ~Ä>Ü>ïä’U{%V“²ZWXÔk¢nø~ÃÎÛw†}ãµNm¸¨A,k适|k€•¹b»4wb°Qo÷=À¦i b?ßÂë举‡‹LÍb2Ú5w°±óèÛÛzwä»>T5`bpgòs´´´÷ÞqÛØ5¥=*¼5iª)Æ—Ù±…l©Jqñ6(]g=ˆJ­¢ì¢­Â‚ì¨s­Øpà6cÀ>þP(ÿÐ/»æ%so &×Ùá§„üŠ[÷H®ÙáÓpHfL9dÞ.ó<¸ßI¶j‹QÑùmço{C^ªX%MÍ×ÜDÕ‡ëÄ£œØ¿”)Xׯª^ÓÃÛ9-/Û¦,šV}lmèÙŒ/5í?­V€ )­ØYp •ÙDÌ AÈH@ÖEgb5?þéÝÝøà¡+W"(šÄ8E ¤—ªPfl…y§ð¤ym6ñ[»°5æ[‡[Q?Falí@ w@PmíäÜÁ ŠP+½V¥ñ„ô¾„óG/?-‹r[8”í¸¤˜œWÝ݇öAVRò0ŸŒyqH¸Aép|v} ;ƒYäjI6tm­£…k˜d—Âth€¾üR¯õøgꀀþ¥ "Šûöíç{™¥,Eb^ËÃ&±«E(ÆbX-÷ïÝ»w7…£»'çG@ YÙ`z#0·9¦$zeäkÃßø§l°“FÄ1ÕO>ªel;yþäpö?œ&Íç÷2&Òx[`ÖKf†³áü;¿80ƒnÔÝ4þQêR¹ý(×FOÀv‰ºo4ãW´»Ý‹¹€LîK޾å‹+×{üØîî(R­Õß84“©|Æïk=Δ@» Dàà#ƒv@÷ ìÜþ¹ƒMD15jp¥¸L rØkµ°é¬lmìä´lî^Ó¹¢3,¿yYh=ápî¯\'rC[à„WÌ Kÿü#â5sžüf41Žç¹<>ezzM€È©RŒØ1óûçÇ1¤Æ}<\¡y§?÷#®ú®K¼€u ¿ ÈŸÕ2_ŒÒóÈ}5×(ÏƒÌ®Ä wãsÜ8€-KŸ{=;|›âžq§€bÕ÷;PàÄ1b8½FPï$¢¼Ö6M’w¸cF¶±Np ²Ño/¢À”Jë xÖóŒl¾ú³K¸¦}ì‚þ…Õ>£,M3hY•%¥Ê€>Í D×å3ÂÒÿ}ÊÄAÙêá ¯š–á+ç™[Ù]~°[—;0*œtMl©]†WëR½cO8Ùý¼Í°0âéÚä?vóœžU÷+Vã0€-[·òA 9ßRÕbƒ"kälx×®]fC´Ú’œA£€F_ºdé3ˆä#¿l’L²UÏB Œ—…¢(2¨Ñ UáÁ¹sæúèZ{Hu‘hUf»àlH@wâ% Œþ<Øp\ÁøkÀ_¯µ&«¨Ô2¢u SyNîÜŠi—æœ<;71^hÆF¼zD˜ú/p @íè·´†Iœ€!Ñ»¦ë2òe^Ònû2Ò?^³"‡—YùJyã%lG>V ªAüp@í¬® ¸«F‡¢iK%ç œöۀʊ1Â.X°`=°â €’m’SÌiÄ1¯ò äPÁ„ª”ž‡øÁ-[·.§€&rôZl€¼–¡88°Ÿ»—i#Ó¾%8ãÐ Ó{Æ×ýàMOÈ£Q•lS µ2­S’XªÅ³ø™ü ˜$tß7aÀ ¸jÒyÔ›G†Éÿ€‘·úJ³©˜,"jWñJWµ<˜ŠNC刮x’¢oSÃLŸÝ°=°Ú†Ý@Oá(oýŒ6E”6…†™Áø7mÞ¼ ÙÌö2HÃ'-Ò´óБP.ÄCâe•o|î¹%2xœRs9@Á#$„v7€ÆBOgUáŒCg[%½~R+²ŠÚu¢Ø1Èšœ@âw#gžò|YLu¨ƒeœyçó‡£Â¸¿[,(Õô|CFÿQk˜ü!ìö£——>RÔ” ½JÅ–jè?¿n)[ùu)Ë—x"‚ÄŒ»˜Úãñ³«Î5à €¦@ãOö‚šÀ`Î?³#:à6¬_`à²G&ç¢I]¤· ]9fPFBVD‰t0~`åÊ•OAb ´{@>p?¼1Þ Þ ÀíÀ¸þ§ýónÀiøëÓ࢖ªlwCOd!E˜Jˆ ìD4tBÄ8@êdÔǸ¿&ÿÓ$r>¯Ãè?nÅŒˆÏƒÄ[ÄQ±KyG" F.ý ZŽ$ýéŒ[^žåœSþüz1£{>»šD{,œQu†¾Ù<¾îKTÏA“”‡}~Ж0=l¼/E @îd“„ÌÉ è± ç®€ ®Šäu¬¡ÍÛ÷ÌÁfÌ$³ÁCa'6:ì (l9Ÿ}BãyŒ?<¡‚x½LbGPg`Uja¬¶ãËSÀo˜•QÊLŒGYþÄ[êˆLõ&,þ¾Ÿ> õЛ€Æ?éïã´ŸA—¦IÓ4d&”i“„ÈãiiÆfyJt–A3¨R*ÀêHÕ”¨Fw—“Ÿ°dýŸ¥c\S¶aý¯Y4å´ÙdÖT‚ ¬Zµj¤HöqÙ%›¢£Ka+9€Jº -¯@¸Íüñz Ð÷˜Ç²Â÷Ç‚Cؾm»;4]3:€™‡gv)ì±0´»ÅVã±2ïZÅšqÀ_êTH—#‰å”:£J$o,/–1æF…Ñ×긫?I⣯wã÷ƒ†ïŠ)iŠqÆpd:+k>/DR4!)ÕË-•®¯¡@oŸ]ìÇ gUÆÞo·þÐõͶnÅ«ý¨Ó·Òp­Œ¢P7´1Î.\¸¤| {$kŽSVÒ: •@‘Ùëwª §á7/´~ýúø:“;âði<€2¾wßÞ€o úÈO5ÄÀÔÃÓ»ÕË8æ³„É ò JÏ ÚºF.‡Rè6VˆÒØpÏË3"±LEfþH ä¿=1†?Ncÿ|t˜ô~Îúp?ˆ}€º r¤ ƒN –\SQ;º>¢*OL–®ËË*–-fÂX@±œœ¥‡ðéÕ3Jƒöø| o‘›­ ~ý)Q´í§!mÇöí6mÚ´¢§ÿ²Iµ€P¸$/Æ;¼ À b.B9y dñâÅ÷3dµœæ±ˆ".‡@È€¤e/=g㪰!ÖË!f»z K…ô(v‰j ³ÿÒf¡:¥73ñ—:®'0®2­cÇ|ãÿz\˜Gp²‡±1:Œ×8W^l;!µSR]Ikt F·è¥HRU‰›¤Îb)‹#ª0ÊQHí•è„jÜ¢Œè÷¼ÿ¿~Ý:s‚¬Ìúd2[!”† -š ’Œ?·AÙ%³(› Ji»3P&¬ŠlôG"á[n¹åçnôÉôc#­Â‘ÀæÍV&§?¶ €2†T #ÂHÕÕ7Pê´ ’ÅM€Ø)جK)Ýât¥'z‰Q†^‘JùRó[]‡Ã˜ëG‡1oÅrà$ ãþzL˜ã/­}ä—~“vL…ˆ•?•"Ýz)í–[ba"BIÏ^z*|F‘ñ+_„^îÑœùaèë2~>³~ÃsþýÚMŒr»ÍøòBáÿÖ[o½IûqÈ Èe—”>¶˜‡b\ôgyHÈJ‰›ñîÇçÁðn®çÌÐÙ„¨u.ôÕâN~Tļ•ìË€êpyÕ(ªBÞºT]EbJµV3&¶L,ÑÙZïBL+ïhˆ%Z© ÝþšøÞña>|q².qÆ¿}¬dl¿Fþ¤6Ú” ü3¨ßX¤`¬L‹l–œ-cÅüeŒÎÞ×ç—Ô^…qä÷š÷ãö±[chôT ëÀiXfoÂ78ø Ú]îrã—[[Œ›]Í”‰P‡*“çÜ¿y󿥯E°F —5ÙÍ ²ÆÏ—0 ø Ú ¸,¼À„êÍ“›dl +b« ˉhYG4§°b)ÝÊò†+ì)=UêTòð`¨²+^…wß'®“O–@ãçèφF·ñ\/h­)š‚“N-҉׊éT—éÕòÙÉUÑÎf–ϹKgåË®V)ñرÖ^îwÿ±þoÃúÿÀ¾ø ¬Ÿi:l”ýY_Æ=ümܸiRiø-f sÿ?MøÛqaÌŸrù¢6£eѸ#É‘µÕA©mž”3”Òˆ¹f‘^`qºŸSŽOGôÔ/CÏ ¿Ss1Þþkòë'@c_µz¥ €>@RÀh/ “ЇÃÜ9s2\SÚœìŒ40/ÈqUâ væÄ¤Ì‚?Ú÷Ýw?‚~ì3alNð“njpCÐ>€œ†o{æªÃëkÞ˜gë{<t_Í0èÓÓ$TJc‡££‹ÆXê‚Yy¢‘Á —+’Xe”\7è÷Lpïç`çö­/À6ìÎä°gÿãȯóº½÷Ýw§ÿEЙ ü]¶ª+@\GñîÇ—Jwíܹc­ùÁmÞ -$´É?[Ë+¸wV­\jÊ6¾ÐÆ…ñ`êݼ=[Å Ô:™ 'Ÿ³@g4ë¤LfSS°ÎÈŽG K/¥[gVgé¦"c2V;U×TÛ7ñ‡\ÖR"ö3lâûLJÑ€|A;Ø®ÔÈY¦3k'Nlwl{y:Œ)¥«ë8™i¥¼VuaYXkb1íÒD¬©?dY{åƒ? xÿŸ»þðŠ¡¶¶6,[ºÌMþ´,ìÃpJg¢XV¯Ã-@ÞþKvqÚgd—M÷ì@º ]9åW*œK€$½Õ³Ï.™cŒ| -´ÌlŒYéˆÓ>õ~J׋£…PwG_Pûp‡@Á,t0Ò+9qIyHdçó®dÝ0O3* s•ò:/ÏÈÀ<8ŒÆÒb„Y_=5ð3Ùý-Lxï¸0ö1í7ÃwéMfœ¬é83xç‹SãÄ£Öœƒ—òq( óZ`…Q{J+B±*-§õ þº‡ºêZkÛðÄ‚'¬ÿûÒ"€†A¢ðäí†62ÏÍ´Õ @Ë€ÜɬƒÙÁrºpƒÈ©NÆLeiˆ0®t¥¥N;¯è‚VNì¸âU!ŽØ'´žF¾¶[‘²ulÚÿ'ºÏ•΋:.›åXC’æ½]Ìbmdû‰¸.äs Ò#‹ñ‰žôí\J,£*„™4bïXêÃKüá½û¿rå ìdà\§CjîÈsûí·ß ¿@nsš°@»ŽÆ°dŽü:äè•ìØÛÖ¶gÅÊ•Rxûã D3鬃Jˆ£ W¯7âÊú][7â2ó.Áµµÿ‹uõ}  YPÌHÞ ¼Æc‘ÝÍGzÒÊÊ00GÌO’:¨ófÇòb7*ü(&¿4<äÒ㿘ú/“Ø·_Ní¡Ôl‹¢%s3UP©¹‰ DEÈ1e±¦7åONÀÓŒ'ç±³‹¢„^†¯ô:«\ƒûùòåË!Ï0ÝF ä7ÜDäÌÙãX*<Œ…Gÿâ @¶ÈÖHÝjYw@±@Æu¨â¢؇M‹Ÿbzãûà–#°†1h\Ÿ·'õ„A(è¢Úß øëíº#[U 2f¥ ’/âì¤e0K#G?²–:3¸-¯€»ÔMâa½Vw,Œ¬‘òU×â5ê/N-5“Ô÷½g¾âÓg"”‰mHr '¤†ØNàÒ•ñ2ÉÒK<Ò‡•G2ƒt!h4/+•S*Ȳ؉õ±ü<ŸRc½éú‹ÞC}÷wê/‰Üüð§?ù‡·g­ï»•³2ˆ€qóζqè¾_Ýw7RðØlý+¿å¶‚pÊBlaF) ÝqÊ¢Âsã—àÔĦ)‹-Z¹wOÛ6o¤È¢ÌØÙ®W}Á|¬‰°ögçÐ^?š8»ætfêõ:Z'Èš«Óš yš$MÐòYk¬ËÌ:cÊSJ÷:"ËÀa±ÔqcÁ1MEð›ùt§¼¼ïâ÷ûÆü¾oøÉ½8›Üjã’ØÛÆužôî­ML,Y¬+‹„X¹²òœÊ:bz–– RH+Õ/†žƒgÔ†Ö ñþ ÑøáGy&Á¶Ð&¢-0Žé,ñšðާŸyŠïþkêOHÓ€v';š´¢"Hë4t×亜^+Ï_ÞiîìX¶|ùÞDÊà ö†#–”€‹Œ5ÿÚõkñµ í˜!•–5Àÿ¸á­6 'SgPk+U¢4u:våDË2hŒ’¯2¯¥Zfveªˆ0­Ÿ³ºboa­¡fpM˜òÑÉø’!–·/Nü¾Q×µšŒ¬ÏtGñu¨/2Ž`¦ Üú: l‹ñFÃK|Æ/>2¦¶3/bË•Ó<ÕÒ½ÞTš§Ä|éz‹¿‡áÛšÿ }ýËYÎj·oßV¯=£¿[“ìTr² n_òì³áKÚ;  _Æ/“HÚˆÍF:mUw€ Q¡yeÄsG oµ÷Ë_þò­¼âÞ0¿Eãtl8îIDAT^¾ÿOvÆK§.–/_a·J¨4 PT-¾Ÿý»µ¯ðøñ8§³»ø…KbdiÞ:\ÛDK\Hbô¼)ɉ7¦ ÄØÙIM<@=γ2uþÚ–Ú0íãSÂàӃػaê¿bÏ#$©)N²ìX7Ú`ŽQk3xL~&'cfFPSœijgÌGB!E¨lÏOJ!¤rR­Vv«×¢¯tu¨«ª3[÷ý­j´Ë@·vÙÙ/¡]G$›Aüú7¾qäôŸ _N@#?¨ \PôNá‘:ÆJxÈûP |&`N¿_¸÷Ù%K~ÉLöuS69¬­8òdÀŸzj±­“hÿún ½ç ë_jñ×! ”MA8`çN€Ø¡cFAF gg,¥[L<„e]|ËŒˆ|‰—¥ 3ôX7´.ÌúÊ©¡å‚ÞÛ˜Œßék½w$¯ÉOhô%Ã7E3DéŠ0ÁÛf)ˆ²Æ›·-áà²Ä 2£h9šIÔIÞt™¯‡ûê僮´ÑŸw´ìÀ3 .Äe¢Ànà”=m’9ÕÚÃAòégž~åk¿F~9Ú zí8ÀJTa% OeÂã†_b/-ÆFX4v½C‰dz†ƒ€s=¿„磩8\F*¯µzT¸°ÏQ÷QH#o±êË 2£;\Yz^qÅM^@i4czÉ(¼Ì, ¬?äuœy<_ݰš0ãfl þNÏߘþ)<Žü&¬ùóP&CLP;c[©—/,ݨeº$Ùõj˜µ5–˜ð’®b›­l–9K%Gz–¹z\ÜpI];Ú¦ýìÇ50þû~u?žð;hbÚ,2Ñøùú·Ù… 8`.‡ç<8ç~) ¨é¿\Ù[¨VQێĨ`U”CâšPHû™7ïÙ;wá¡4,ÊhöŽV"‡ $´ßŒ}5^ܵs—áT÷¸~º¾ñ­x. ^2ô=4aU­w±Ø'*]ÝÐ;3’Mù ã-B¤1™í/ãKyˆÄÎN#.V+?òy ˨ uÃjí‰ÁÁ³{îÊÌ/Ì#^C6gC™dl¨—! E*£ybl‡§$~E]_l B^HÄ“>‹é–ÁóÄÜåù•arðzODyßÿm-\û³ïB?è»üE¬+–#s‹ºd ®´dHÇ#òëçÏŸ¿ òä†/›Ò À3ºÐÒVvÙ¤£q*TÐ i:’;NYxì½í¶Ûn¶©—¦@¸Âe3‚˜Æ/-zrQ¨­«3ç"©I*ò5 }÷\@ê$R)[-ÜZÞqGµÑŽü Ò’ Ñ73£ìØ,ÍâäeÈ -=‰ß™È—@YNÃÈú03Æiƒ;†€ÉØô›†ã޵)YY/Ä)Õ«1ùž8Áœ´Œ‡©ò´”)*›LYÞ¤ßBº—ÇzbÉey¬&è&Õê„>ÿï¦×EãgMpƵõá‰ùóí—ÒH‹áèï¶qÕ£…ðï|ç+‹Æ/`KjÄeüù @¹ª%Gêr•²B¿P’‡?!>c¼|ó¦MOZfk-¨ÌiT6œ¸>{¼ àcP¤0Ä~HÊ|qÃåxKà;´WÚ­sê,Q.Ë”á©+e4µA0ñ¨ÆJ¼L#=¦Èù,%e¥Ùe|Tsø†À‘{ðì£ßœö±)aä5¥¬F«»LJ莫LnDŒ7“3æ%¿u3Ö˜I†k‰CbQÒeNK×3Ëד(ïïŠÆ«löj3ÜÅÚ¼icxì±ÇP gn4z j.`É xÚÆMŸyúé§ùÓ_zøGN@vE;S)‚ ¥KOñN‘#u*,¯„8Í™‡œ€¦+r{ûíoF±Íc@ |ËÉ=bTh® ÃxY¢¯?ÎAúÁ’7Å  ±¦1üUÓÛ%CŸÀÔiŠ-fíèŒ6*ž‰#^vVòð4¹YŽÈã¹Éï|Œ[ÇgÒ,ÍËtBNŸÕàÉào'ðÙéað¬#_Ìú÷¡¿c˜:­Õ¯š]Vµ+ºl¡ÚDƒøHÉgÖ(ñ‹Yíe\e¨íF#ˆ×O¬—¤,M8¯Ao‡w}w\݈>ë·²¹õõ«ûï õ nè&W©Ÿ» þ0q`xNàÑ{ §F¿ìIöUtlZÞên7õh@^ñü }ÓSQ`ÍÌ üìg?›‹ßØÂÞÄ?t‡G4ѽøx:?–`oP×<*àéõ³Ãiµ³‘¡ïBê<.šWœáÖ©IÍh•pë‚ò”:¨•>±–R½z7²b ¸HÂáŽÉ%oSŸÛžêÇveú'§â¹‚¡V\Yݱü$\Ç*5CNGLGYŽ9 I² ÜY¬¢4j7l EžŒ×y@ \ ‚ž®Ÿ1ôÎéôú3 gÚR•‹ývó–ÍaÃú Öf¶Ûú=û9e4˜ãò÷´m½çž»ç!U£?m‡GnS6†‚&˜·ä# GãTƒ*¶æ€Hè™åµ’7çÃoðƳýœ€™'*ƒWÎQ+‚äŸüä'føvKÐ6UèY«Ã›¯O߆ԉ W ­ãGÑÈT†Ó8Ý”¬s«òˆÏpç¡É[ç¶²ÇòåüìiŠ‹/ÆËë¤(‡µÊ)ÊǘÉèÐJgÜB,[Qñ1žxˆ[®Í3¥ëæÑ^;ÿ^Ë›}‚(ܯâ€õ½ï}—@B»±Û o}Þ€ÉM®o~ó›Ÿñ ÊŽrÛ¢1H Eè©Ý8‹Pñ¬œ‡<’¦)<-žyæ™Õ›7oZd“¹¡^:_ ðNŠá¿LˆðSâøÂ9]¹Q±“ê&‡kûnCР êül­BNü•ÑÄ¡uVvlÄ‹|Š›q(Ÿîx²Pg2²öåOåg8›°pÚOít&0óÆéXóë-CÖ„`²², iΑäÉÛNv‹3‚•‘åi¤ˆ×Sq.Ë ‡ñ“ÔŒÿµÍ¯ Së§Zåóþì>ø`Às0†bm'Ɇ;Kà MÀŸ{îi¼$´-¢á§A¸ìHvÅÂhk²7 GŽÖĥЧP”ÆÏ˜@ÃÛ ˜ïÚ(ˆTŽÍˆ¢=T‹ö€"T… DϨ€gåŒàêÆkÂÈêÂ=iféåÀ‹kA-f¤ Ñs<ãgYå>Ëc|4‚xÄÂÈoý‰+ ¿ßP&wªü9ŽÒu”fù Ü*SeDË—ðX¾ñf%2Î…3ù”žáé:CïZkF…k[®Eõi¿!¨îñß>nýÖ­=ŸÃrÃÂF'à×òpøŸÿùŸÓ>•ÆŸþ´Ü¦X$CÞêJqcêêt´@JB æíuµkɘl›3g΂µk×r‡/µÀo‰ Õ´h(ÓÛí“ïþ×wã´ Ô4ކê†p}óŸo_ŸRçRë)@½cšq—Ñ#ø²NnÝôdÀäË`È ÈøA2þ˜4fÉi$¨LBòðhœÖfüÛ´P7">]‰iÿÌÏÎ-ç•/Ø­ ËÏ T& £ Þ‰‰ÄƒDÉ,C5éD¢lÀY—-¿ÇŒ”——ø’éNe%~+)+Ãzïô¶aê±û¯Ç×98}ë[ßÂ×®ø­˜•ƒŸúC#ž·rÕêyüä÷r²Ù5’Û¢F#ý¨.ý1iZ0w(ÄI'ÔÁúª1ÕYzÞyç¿ë¤:ª‚ËZ…jŠdiˆ·ín ÍÍÍ¡µµâ¥âUÓöÞž9ð4}’`µj½DPPh ‰Ly‚áNàÙÚ§²¯ˆ H –h¹ £þ”ŸT‹g%^?¶!4ŸÝ¶Ü·-LûàäpÊËJÏöçùÈÏ@C‚(ØñäêœT3xqF2)r$®ÜI/çuq1”îºTà!©ìºÐ‹áUͯ/m~™é›k~Nÿù¥Ÿ… ú€…6¹.H𥣔÷á›øUḀ·«¡øêðÚæ×‡&¼6|èKR>²Î_»úå/i£=1_Ô²W;fâðã8ŽØOÎÅånB„£¿Öþýi;²#¶2·/D=«È/¥a\‚Rpy.6Dk›æ|ík_û¾t²Í2päg_À‰Š£Õ[™¼¸ªpààðÃþ0{:Jo¨jþat’¬Y¾¾9¥zÕrU˸BĽ#‹X)n ±åàI_ü±¼¬$0Sñˆù=Ý3%9ILåT…z<1ÈÎë´†À³#àÄÌ‘få8§RTäÎËZ&IS(¦#nŽ‚é¦“È˜óexYûTf/AÖõO­ õX†²š60õÿîwo·ß¼ÔóýV½õgï×ifk[UÀ—³v|ó›·Ü>Mý9êkä§ÍÈ~hK4þ¬Åˆy í¨Ã±.òŠÕr(£%!ë´ãÀþk¦M›z Š«$4§´1ˆÿ‘F5ïÚµ;44Ô‡qã&˜&LñÈÚRÝÆ×Ž í}(—§ÏpÊæÝ V) 0šp þ_F*2çe‰4Ì,0V •RËX“ÜÙAÖ,^ÆÏ´hlf±E†,¯xs˜uCãD\%(§±›1g”,Ÿ§{¡–7ñÆŠrÞ /Ó}díMðÎSÞÎh<ׂ³PÌð•߇q·ê‰'æÉCÑus:º;:ü/~ñ‹/c?ŒOýí‘Oýé4 Ð  èXx¦ÄŽ"È@"k»,F‚Q` OoÆCËótsçÎ}lûöËØf2µ¨M6dD }fÂ=÷üŠ¿’j¢’™‹J½¸ñ’pVýÙ(¥i‚¢H†£'ÄÑÌL ç+ò¦¸q²•¤@ÀŠù/éF •f2vZŠÚ¡Ksd2HîTW^hÆgdĽµˆå2Ä:RÖ,_*?%ö.rNùáÒæ˜AcÐÇŒ´&lÁÏÝÿ÷ÿ·éÞFyÎ`mä'‰zÆ”ÙgøÔ÷¶m+±ñ·Òj½¯‘Ÿ3eþ²¡JÆß# í©€®¿ …Óµ,BÍ þú×sç^vÙe¯ÀJ+‰Úâ|ÇN(&*Ó0èÆŽ‡¥ÃÙgjàyͳÒàï/ ·=¶ÚÎúû<¸¹ B!êñ¨€²ä²H…¼àæGTb e‘bÆ\éÔ£WQ)WEš’·Ÿ,±ðdÄYe•ÊŽ4ã²<ð'ÞRm…ê"Ÿ?ùؘp xxT,cÃ×¾þõÀŸù¶vCtöI¦sbË!ÌZO±ñ÷™›oþPŽüý¹ ÐR€…É t6ýÛ±…žšè²æ¸<˜Ö2šØ élðn¼úø3t3võiÿœÈ)¸ç„bAؾm;6[~áö ½0U „¯ ¿gø{M#=Û»¦]qsfeEJC$²³ãð?ÆãÁ4†bÜhžGy/žÌ‘‡0?Êx1ÉTvÈôvG¡ÌöåS.d´6E`!¯£@³r@K›•…tEs]P_Ç#¼oÔûÍ蹑OãoÔîºë®° ¬3T±»£­Üçó€^l¾wdßÄ{1?ES~½¦ý´=m&7~Úƒ—A#í©§fª_W†°ˆçq:Æ «ñöÓêsÎ9÷ÜúúºS¨(SÔoL©ˆ›~™ϺvÝÚÐP?(Œ;Î ¢×e‘M5ÍáŒú3Ã}{î%ḆvUTeqpçñ?a;: ñh—æ3'”r(§ÑŧFby‘ЉӲáB‰ãyA¢‚î%ÄròüҶ áÛ¸mS°&,]¶4Ì:ufh‚/¯"Ó.–ÚZÛŠ‡3Â{çƒùø†vN€â°åyHñØÅÑ’™¼e‘,s]¹*À2¾,DKr–ê.Ë’Œ4§ªžXHgÝ2¦¥©¼L–<Žƒ¥$_ÆßGè[†ÿAxaËåv·„³L~Ýwýºõá¶[o³ÛÒº8Ö¶Øm‰@¹-é1ÕÁ=ÿݸçÿɽ{÷nE’¦þ4~θöϧþœhÔ©gCO;J§R„JS #HÎo jllÜ1nÜØ‹èèhûº"èþ ìƒ> »dË›Üä1öGÒ~óðÃßÂÏ|= ‰ùÀúÑ èhôÔDÑø ÚÇ1„¾rT‘Ôäs¡ò~aÎ`éÒ¥ë§NÚÚÜÔ2™mWËÙb?iÞz:ˆˆò—….ºèBs®w^”ªpΠs“{…ç>ÇÌÇ=°ð¯]ÈI9n¼$°=vN”TF¢¶GºË×>§S\Ý¥VäqÉ‘”Œ>¢RyMºª¨¯Î¥è±Ô3Þ5úoì ?©pêqú«_ýjغm3® ©ö|¢_U¤§?k*b€ì‹«V­žsçOïü2TšúËøµWÆ®/ ­öXûXPo8–«+M˜ žÓ‰ë0À86sÞ9×ÖÖµØv ÜÝ!iV"é,pãÆ86…3Î8ÃÓ0Mã‹¿¼éŠ0oϼ°õÐÄûGpÙ ²H39¹Œ#(Kf¾v„¼°^³«Ëêó£dø…º³<–RˆWÔO¡ˆÞŽN«Ÿnÿq{ñÌ”‚±Ó¿ýíoáqßøpmüä7ZlÓ|”䆬Ÿ2þðüÊúÿüÏÿø"¹Ì–?æÐ[€‚©;¡Òr:q&ÏÆç6.ž}úìßåÓÀ °s ˜õ#Ð8$Î0ÖoÀ‡aÆ©§Zšh5T…Kš. ?Ýþ¸TéÓŠ:®'v ý• b2—Q<’èÖ2ÐZ@Ë©å¯Ö)»b-ÖÇxÉà•¹+ä·”½¢>b} ø=ÿ'ÞŒ/PÅ'ý0 ÔâW«îúù]øR¿ZÍ—¨âž0Žò íÙ^wUá‡wÜñémÛ·oQÓ~9ÝûÏ×þ4~jFÚ)B$õ\èK@©óžÕ–hJ«Þºuk[}}ýFÛàHo*àˆ„àvÔÓy;i1~j|ذaaÂøñICôÎ Uõ᪖—…v=ÚsÉÕ¿‚:¾wœL6i(#%´,Mª+A+QQ˜¯NZÄ#z7Óy—³]J©\‹C!ÍKS™æê³„SjN Ÿ›ø…0´Aåúö™;wNøþ÷jêü Jú6;À¾€;¨óŽß³ø^içn´¦þ„ÚõçèÏ£8õ×ôI½zÓPrj„A½ÅcíéJOo ®Ÿ2uÚ˜¦¦¦It†ì7îÉÓlÂä{Õøþ:6Y©˜ ‚ hWÅ/bcUcxQó‹Ã϶ßÙ¯fRˆ ;]ÅÐÙx;LKêdW«꼪‡i1ÅI¶ÉJnPËÖïäê$Œ»Œ³æuvQ^Y½áÈÿùI_ Ãë‡ÇŸö¯Z¹*|ýëßH¿W­ÝtÆf¹ñ²= _·vÝCxNà‡ Òè9úv¶é§©?Ø,´&rÏÁÞv””ZɯtŽ+]P¼¶ç‚Ï)?>{öì3ëëF¸ÙûhŸöb±6àï ÌÃ0Θ1# >Ü/Je‡T=(¼¼å•a~ÛoÖƒýgO€ÏƒŒƒ°bè€|d¼Rµ ëJíN—쀧ËvªêËèô†éᦉŸ C뇙ñÓ€¹´\¶lYøÜç>®Û¤aC(ÓTÜgr£i ¼MÈ€ßÆ\òíoû @iøùÔ_€Óÿâ=jNP hSÉÇûÊPJ׌ï”ÆtsØá_xú鳯„†ëL5Ò©'ÎHÓòž(.ÖoñE–óÏ;/ ŒObƒÆ Ä£¼¬åwö…aÃ.Ëúw`—ã_§¡‹ävy»ÃQè„¿[m8¢Êz–ù¬Æ³ÃÇ'~2 ªÄY;‚ÿšÏf<ãÓg>c¥áí?» 2tr±O%¯xsµí?øÁøÈçf$kÊÏiÿ‘'EI=úÂPTu9ÂŽ&ñ3^µ{÷îý+W®üͬÓf]Ž Þˆ³DœL8K3Äìß~=÷ט LÃ8 _¼x„W4_vâ9§÷>…”þdD‚]Jœk³Kæ#dèè Æb$£à–Þ§ì¯öêðžqï³ÑÞúú?ì[ÒáÆo ûðŽ¿«2¶v#Ο¢’à€ jûþ÷¿^ñǯaµ[÷Óä»þœòkÚÏ!,ÆëØV,±'N}å(+ÕÄ@hvk±’úb4©“qÎÂÎ;÷ÜpÅ„‰^€¨—Ç0bGTäR€!º¼tq0<üðÃá´Óð­»áøq 2`Z§ëuÞàóñC# áq, N´ ã"<ªÐU6)µ›…³<ݬ§'Ùþhäõá-­Gr´ÌߥÄþSøÌM7á³^ü] ÔˆÓˆ»Úø9#{_2&뎇ñÍË/aÙð4’9ò磿vüé8õÏŸÚ–Æ‹I½Ž—è¬E¦òŒÁt¾nÝ:N§ÖŒ7îÓG ý6ê7ëGŠáÌí4xäðÐC…–––0eÚT»UhÓ¼xQOkœÎÆðî¿Ìª<±ÐÜøŠxOµ¤Xn1ÞSõôU9Ÿø‰pŰ+̰¹n§ ãÙ“pÿý÷‡/ùÿ`û´>‰" Þp7|[0¹ùEVn 2Ì: YAã²óÜZ7*\Ùò’0ÏãaÛA³Ë’N‚SÑP6~¨Âš0/ô|zòMajã43~›öc<'üÁw„oßò-LkÒ^€÷få.;M¤ØÜÔûŸ\¶lùÏ|ð8ŠhäÏ_£¾é'£—`%ì ‚ëÅs_;6E:%žÓ‹8ã)ॡ§FŽY?lèÐYÔß ¾ø*מöøÍø]‚°/q\|ÉÅñ±aTϽ€¦š–ðŠ¡¯ Oµ-ëösù6N6 œßtAøÄ”CKm®;G}?5 _úâý÷Þ‹ŸòÆïQâk|öNã"ÌQÄ8; __vé]øºÏ÷Á¦ÿî?;«wXW´º­ S{ù|<›Uv:Kc¦*òS&LhÅ3““ݘ#°'°Àh$îÐAð¢ÂK,ÃúnñâÅáÜsÏ ƒ¢¯åÀ'»ªÃ-W†‰õÃÜør¹•À*‰¬®ØÿnÜ߇?}=z.i4ö=»÷„O|âááyóÜøiøÐ`uD~ˆÖƒå¶bü_·~ýœŸßu¾gQqÚ¯‘ŸOúiÚÏÉQß;f즈3ô©ñ³Âãí¤YÊ’ã•âTNœx…*ñüñã'Œlj<ÙÖÿÈMHãçlÀhŒ9Í ln»ü„Ó¹çœZì!ÐÍã»&ãõφû·ßöæµ'ªZ0³»yêçÃYÍgÛ5öõ>>Œw—ÂûÞû^ü*ÏFÿº”µ™vêÖ/@'ô>BM‡ñ¯]7ç§?ýé€PiÍ_4þ|ãÏÆ&äësƒ§ôy8^€2¸µåÒ8N¥Ó**ê©§?1~Üø‘6€±›Ñ³ â2ˆ)unËì ð—[‘7œyæ™þ5×è†Ö ¯Â-¢‡„'Û1ã@8Á4ðúoÿ8é#aDýˆø€Ž1pçw†›oþl8ˆÁŒ›mÃu÷ŽW:;ætä6î#¬ÇȼíÈøu¿¿øŒ?gì…ùèÏš*öqOê½óñtlU®_ÅIË•‘ãä) xô÷‰qtƒOöŸhí­I,ÆÓÈgΧy>Ö¯]oN ®ox©6xÚªÚp~Óùavãéaáž…ø¾ý@èïS7&|`âÃ+†_jñâŽ86ñðBßåÿÒ¾¾÷ýïÙ½¶…Ÿ'ðŒ—œ‚è$òønX?øÊø+þÚí/Ný½–÷oŠ ^G¼OÃñvl,ÕÏ@˜+¢7¦Ѱ/øÄˆ#ê†2‹³+$Nù¥qít–@€ÕUø²Ðò¥a΃sðäàùa¾.Dºu“¬*Œ­^>ìøqMxb7ßëýUon}Kø»‰ïMÂ5ôïDÖÔÖÜJüÀ‚… qË/©fÍÎëM^ ví¹7£Y¹bå]wßýK®ù¹Ñ—?G}ÝîËG~ŽúZ÷³â Ö3hľ>õÀ6G5'' ¸ô!euç“[OÀÝ!--3Í à‚™!'cGVàæP:ý+á+¼è{vï˜Ò|•(œ6khÌŽÁÝbðq$9kð9ø$ô%a;5aýþõ „þ¢s›Î ÿ0éø·e¨‹£>÷îì^Ø÷ùñ~n¸á›ð Ѩí ó:3Ž.â£?ãä Q<–þs|P»ýùí>>õåȯÑ_k~<v¾~eüÇÚEx¼ƒ©: !œWI]6zؾ·£ûÛ{;¡éì³Ï¾ø¬³Îz;pÜ!ôQÞßôo ò™sö<ñ!ÎO<ñGI¦Lš>ðÁØï‚W.î0‚@þßîz,üóª†=‡xíÂñÒ@cucø0Öù絜‡ÎÌ®cìâÀpwîØ>òц¥K–š3ð™»;wàæäæ/óDòó±`^ðÇçÏÿÊÂ… ç¡à|Ô§ÑëÿÜøµã¯Ý~v: t 9.ZŸÂþ2`£eøÂ;RNNNh86g68°ikëè p1kqñ,‰F츃O9K|| ä'wþ$ŒÀãÃS§M‡`èÎF@n©^}ʵaHͰ`÷Ü×áµ}¥þ$ÜŸŒy[xÿ„„Iƒ1Ý· „k„Ñ׉óüâÿ>ô¡…M›6‚Ʊ$v²x-ý‡FÝðÝ18‡áV"Ÿí?Øöèc} wCjÑøói¿FþÆø“>ˆô“PtK³ÂâL@³Î4àL`0öÆ]uÕU7ÔTW7úÏs¤§ñsV€‘Þ>7D®™³ úI2ÆÉG8aâÄpã§>íFX(’_ÁÊB„#ݸúÓáÞí÷â®g~¡·4Àwö9ÍÿÛ ïKÖ {·`mxÇ;Þa·ù8Ýwãv&ôä£ÑûúÞÓrÜG}Î :ÔvÏÝwß°eË–5¨@÷æÆÏ5¾á×ïG~×XT¡"ýÊ R¬¢ #¨‹À xÈ pYÐ4tذÑ/zá ߎ[}§šQƒÈ%ؾèJ#ÇáN¡2ᏄºúúpÍ5W‡ë¯ÿðÃYÀyÐøÙœBX»wmøé–;ÃíoµøÀ©g5ð¦ÖëÂ+G\Æ5Œ·ž«;4§ê4æ¯|åÿb½GØ»o¿müŠÞÃt@ð8ôQäèÏq~Ço×¼Øóåíþ)/­÷ó)?ïóó Çצ ¿’ñƒì3Ô ’vÜšÝ/ƒä¤•œ€ö4 #h7xéK_úçÃO~¿X2v®÷ÁM‡@`ŽÀG~ã£yÛÄ€f~ÈÞ,ä“cŸüÔ'ÃÔ)Óð¸rXyÈ!Å=ö:>µúß¼Û§É™>ŽNMÕMáÂ!ÙÎ~mM¼MKkÍÿÙ¥K»Þù.ÃíY~®ë‘æø"›Å9 àBŽ<Ä"¤£àÌ¿õ÷ þ:ªÐh/¨Q?ÈG#ÿ güTaI“Œõ¯ Ù)]îˆkc³Ü ÐpfØ|ÑÌ™3ÿœSû3ƒÏãš ä´hä¶<8tÐvrfâÇHÞýîw‡SñÕ!þ&<<ψò‰Ä­¶…{¶þ2|mÝÿ {s‰8º«[ã}[¸jøKíû|ÌGãU ÚÐ0(<ýÔÓáSŸúTxrñ“–N‡àFN^à˜òÛ,QÛЃ‘Û, 3|s1ž+ù6ûDf½6ù5êk½ÏÑ_k~nòå¢Ô1Eï°¤Ñ~!N;!$Ÿ :rš èfrM£Gžtá…¾«™€¶;Ãa½6ͧ3€#À?ŒÛïÈqp9±»Ëoºîºð†×¿Þ>BºK÷ävËø6ÝîÜü“°´m‰Ø` L4=\3âUáÚÖטö2›§9ƒvØ–d+ðïm·ÞnùÖ-aH˸ÃÏeMžÆCA{,9w ÀáÈ×?2oÞ—ð‰ù•H¦±ÓpÄ—Ш/ã—áæ†O\AF/(z¿¹aõ¡ ‚ä2 § N¨Á|_@N€0_ØÞÀå—_~ýðáÃ^Dä1ÓRiô¾©ç†/cwGáir–Nßhhh—]zYøÇú°½CNó)˜+À3XH„=w‡Ï¯þ\¸3ƒPÒGú·O|gàm=>pÅ`Æë˜}ûë¨ÁÓ|þð‡Â<ö¶íÅÓ}0è8…×úÞ º;ë,™á³È|sw}æ`½ Èù¨/ç Ñëà¨ÏƒS~¿Öü¼à2þÒÅ·Þ…”~dPýT¼$V.§pÍÈÄÑŸ½&w\ð(nÒ!4yæ/œmZØšî4p9€' ˜ÁûÆÎƒ;ý[î ÷m»7<¶“w˜žáÜæóÂïW{Ih®k1ÍèâJ4Ôºººðì³Ï†ÿøÇ6↓åÆlÝÁ¬Û6ò×°Ÿê“GŽÁqt[ÀrÚ·Co_ü䓜òÓÐ+úùµËŸ¿’Ó €¸_`bå¸SúÙ¹¨ï~&^™8¹¬Â͹ƒKÎ@N€!ßàL@K:âMƒ›šN¹ôÒKßÓØÐ0•W­|6@{ÇÈÏ?î x‰y7ÁRc²'GqÙ¥—†¿yÏ{ÂÈÖ‘öˆi©G8Æ3w°Kô~¼ñGáö ·á)ÃÕ,ê¤ ã±ƒÿ†Qo ¯n½6¶‘—Ò½¦Ñ–pO~î¹ x]÷“6âkÔ†å›QËÈÀý!>þkæ_y À¥ÚÚö,Ç7#ÿ}Ïž=›P“ŒŸÆNœGqʯõ> _£>G|^Ænä‡ÌdHŠ÷w˜Ë+œPŽ€€8aîè hôZäÎ`0ž¼ Ÿ{#:Ø ºCNýq…iì@Ì`9 0£÷4Û@JÉI`^x`ŸåÃ~CxÕ«_®¾úLk«qk ³H"É™QžH"¾yÿæðìžgÂü‡‡qá©Ý‹Éq†YƒgÙ.>_Ç1xF8¥_xGðör.…8…£=GûýèGüa s¾öì> Ÿy¡Ksùt:ÒÝ1ð–¢î PŸ;\»¶Õ«Wÿ`Ñ¢E÷" =ŸêËpºOœF¯[|ùtŸ€‚ËõKIi'D ÖO´Ë,œû‡- Ø£ˆÓø¹$Ðl€N€‡6 ¯¸òÊh¨¯Ÿ ãgq ãÏf¹ƒðÍB\m¤ûlÀÎÖaÙ˜ïàÁöªñ»Þõ.l¾Á~\’·­V’ºJB7Ü·åWá‘óì¶â¾CûÂþ~öÀЩ¯nMÕƒÃC./ÂÔþ’¡—xpfkØB§M@±'ñ·òöîÛn¿í6üòîMö²õ¤`Ú qà‰sÝoûûv¥8#ð½:Θê~²ÂÏr¯ÄÞÁ§€çFOœÆ®ƒÆO£'¤Ñçë}¼6GM¤D#ÞïƒéµßKYYÀ\vë`#d— Ôr Ÿ ÐÈ ÈhI@8xòäÉgMŸ>ýÏðöج`ŒéqÇŸŒÞ,Š#?S8C0GÁ<¥}”i{ÍÍÍaÊ”)áÕ×^ÞôÆ7ZÇççË‘ ³tH^¾<ˆtò coÝ¿5l;°Õàü]óàÂâÝO†-¶xE}t^;<Ìj:-œÑ|F8«éì0¼n¸Ý®Z3ÌÚ 1x!¼QÙN h‹ œ»ùwüð‡aÙÒ¥aç®]¶æw¦ò³ìnГÁ£<ÝÏg×6€€Þñì’%_Ã/ü,@‰4t9âM÷‹Sþâ¨Ï†ÄÆ+Çp"œüúœ’V–Qò ’‹xÑ 0ÎY:trù² ñ‚ .x3~^ìi¹astç¨N‡@#÷8ø¨„|€Ltñ@¬]Ø‘ŽÏ¼îu¯³Ÿ7 xtŸ¤FgµòYˆ÷(6†Å³O“Â7=…ñR`Žå»—ÙmÆåmËÂʶ•aݾuaß¡½¡ !¿n´ÿÐ~Ì@æÊ8˜QYx]¯MÔñÀ¯ÞÖã·9šÂA8¯COÂ뵓'‡iø˜æ”Æ©–/?¹|¤@> cò: È¡9EçWšùlþ*ÜÆû¯ï}/Ü~ûíaРA·fóâ:ÄK;þñYvö¡à,W¨mû¶ís{ì±ÛQ Öö‚ùˆ¯Q_›}ùyPAZó³UŒ*ý„ñ20òV4oƒpB9Bš Ð p& Ù€fZ$ˆ]‘§Ÿqúïã“âóöž}†ÜFù’kF@ƒ7]$ÑÊú j,˃ç7ø£¦/ùËÃk^óš0>þ¸)× Žÿ¥œòÖå˜Ê¦rÍÎ8E<: úAÌHøå›ƒèÓ„V/b„Ìnf'׿ x»p| :äeÕ¶vÂúYw¥@:ŸÊcX³vM¸ãw„ŸÝõ³°uËÖ€GlÍ!Øh])s4ü6åG¬ÃG|=.3ëÄsó°»ëî={ø9yò2zÁÜð9âkÔÏ×û2zAJfÚ'‚ãN9Î\º¨.jÞŽ—ñ“ÆžXÉh6@HãÏg6+€AΞ0~ÂkñõX|p„‚à2G7aH´%7ü’s×…}˜ð˜2eJ¸ä’KÃ¥—^&âe$Îè$hòþ¬ë¡ bÿu±© D‰gtÃ6s6£b=ðx¦14ÙhÅBrãµ ¸ÈÀ‡¤V­Z—q–ÛÏi?8g¾œ»ÌFùîŽôź—°ºaý>³`*Z€9ÿÞ¶¶§Q÷ññE ÒÀ‹ÆO€Fû|“/7ü|ÊOãW€bRÈñD<‘^ý“)¨=‚lžqE'À¸fšhY G 8£ôØÙ§Ï~~+~˜öõÍT°þçÈÊv!Ê>æ@ÃâZO1†+.¿"¼øÅ/ç_p~*—£¸ä¤°r4Ó%ò³ÕN%C)ÂâÓèéhð:èhèù¨Ï‹¨ )R¢?რä„oH¡y»„ æN€4?‚`># ÁÓæNÀœÃ°aÃ'Θ>í/ñ±É1˜ŠWùíBôZLöÖ§Ã7)Û׎§Njˇ±cÇÚ bäÈö‰øe%üjr“m¾á˜€`ÅÏYoxï=àYxÁ×ÂÐiàK—.嫱fԜиu ‘}pÅÐÖÃX­_²dÉW0⯂åiØÂeü¢i´'ÌG|:^°ÜøMð¤5~6RFAüd jŸ ÛHœ‡¡f¹ žÏhôrÚ+0G0â”S¦¶Žu6 /—!‚÷¸9É$HÁˆ+hÄ–aç0_Û‹ÿxBü˜Ü¿uëÖeCÆ.#רÏx~Ðèiè¹ñkÄ'¤2KŠ©ìÀzò„Ü0NžV•·$ocŽËÈäŽ@399BÍ ä m†pê©3^ÛÔÔ|) g ‰e„cÐüØ!ì+îxïÜ¢4² Êøeð¤ÓÐ'”ñÓäS}|nôÄ*9¥T07ˆ“ªa“·U¸ŒŸPxî4# äAc׸Œ_¸A¬ƒ[°k?qÔ¨Q/Å׈.ß@8 àgáÞ°aÃÝ0þÕØûØ"hÜ2tÁœ&ƒgZ¾¾'N—(¾ ^¬i @ü¤ 2„“¶…†åí-âÍ8БÏ„Ëø5;4:fج» ˃‹àÆ"ΙÂ@¨ Œô{aèë`ðó°¹x/âÜÉ—¡jtTšFy9ö4x4úŽ IeŸ;¦´!7‚“¶‘–·;Ç+9¦ËäP39Æsg@<Ѱ„]š±Cžú{%ðñäz^’ð˜ôl>þkûùp»1íÏwíiä4ì /¾Œ=‡4æJÆ/]çÆžãJ?©aÞùOê†Vh\±íŠæ‚ƒã4|Æ s'@œÆ/(G!ç`ôÁƒãWÎGž%Âéx8f fþš2žìF¾ <­ÀѦM›æã.ŸÖÓhÎÑ[¸FtÆE'8éäQÈqÑž—v TÖ@®›"Îx~Ðà'nFã2bÂÜ9ÈÈ sžŽâ*Ó ÞÀÄ ¾ˇÁX64">ŽáІ!> ñ8‰&ÒáqÔÇÃ䮂ðJƒô>À}Ø}oñÇ.|¨c;6é¶áØCß‚øNá߃QéÜ‘Ï S+£í Êèŧ²ägº 4á„Å$ ¤+ä¸hÏ{˜wìç½2*( ×OŽ“•ñü M ÇE3£CBnĹ3Èér9M¸òËUœr)NY$'iÝ 20 㢠¤a §Ñ'­Ò!£ÎÓ*åQ*WŦºˆwdøJ#dP;<6pN`ÇÝÓ@®«gnXæÆÈ4q’F¾ÒÈ€;¢ÓU—ê,¤30žãF,œd(4¸".c£ñ—Q2.\P†Îxž^‰.gÀ2U¶ ó«.Õ_„`Ii 9.Ú,h€b t_E}UŠ“¦ƒ%Ë0ESœ°³#7|æU¼³<äËËW —?é•‚Œ‹i4>ÑŠÆÈxg‡Œ˜|‚åa]LWŠƒTF#=]ÅsÞ<Ó@G!c@;Ð@Qw•â¤é`1Âi¤ 2fÒsÃ-Ò•¦Ñ]é‚,KiÅ:$oW!7@WÑ0iÐ Eƒ&¿Œ_i¤ W99$Î šê$ÌcŠ'ñˆVŒ‹>;Ñ;Å@86 TÒa‘&ãT¹3M†Ît¥åôîââS=Œç‡è•`np¹QåÆIzï ÏÓ󼬟i<:ªŸé¹<9¿ðx„`§=§¢>‹qÖ”bž^‰.‡ >9–Cœ¡#¨<*—¼¢ï,ÈÐdŒäMÆÚTšòã¢KÅE$=Åxž6€ºÛ!Ž ÈÖ¨Jº-Ò'Ô!*^äazžVLÏãâ­I«d\A¦åéŠi,¿˜V‰¦|Lc(Æ;¢óÀéè5 Îrô% äìJ•t\‰ÆrD'žÓ…+-çËiâË!ñbPžJ'^¥uó4áÌ_ÄT‚•è•hâ€Ç¨u€c,f ûh ’Î+ÑXdN/ây<ç]0O#~$!7>áE¨òHWiáâ/òˆžçmö’òNÒKU Û:Ó}wÓŠ|Å8«®Dë@¤ŠäJY¤åñ/x´iÅrâ=¤cí=$Æ@1Q]ÎÒ*)°3þŽÒzÒ@{²¬Jí õ€:ê=Pô@Ǩ®®ÒsCí«î2{¥:sZ¥ºJ¯”g€ÖËè«ÓËÍxެ׭£üÇj¤Çšÿyw!wƒ;êÇ[®ú{F=u} »g®Ç@)ÐÀ€40  h hàÿUäp+m2áÎIEND®B`‚ic12‰PNG  IHDR@@ªiqÞ pHYs%%IR$ðÈIDATxÕ[ypœEv3£c4£[²%ù’lK–dI6–ùfÁ » æ€1µ»X‡]HHH*)v!µ•kC…ÅPÙMŠ*;Àˆ1Ky1lƒÁ[ÆØØ²-Kò)ë>F·4Òh®ü~ý}=ž‘fdÙ8[•VõtÝý^¿÷ú½×ïë¯%ò‡O–I¦œ¬o°ïú¿žPã×%)e]çpʃxÐY·ó™I—ÆÓMü 'ìf¡Õ85“6 Ö9.¬Î6=– úò/¬ÎöpÁÜTah0ÇMIV`!N2œˆœ„ìËvÔÙÇÌqLdÒkæQ”î°<‚ºÇìÓÂàù¦¤›%2ά™NF= 93999ûÉ'Ÿ\¾hÑ¢âÂÂÂyYYYÙ)))ÉÈŽÄÄÄŒ zF=Þ¡á!÷àààPOOO÷ùóç/eÐA\ Šï”_ü'2mÚ4¹{ýÝrÏwî‘ììlhð ÷÷÷½õÖ[ocþwàOšá?:AÍ‚Ú0e“˜œ` KZåéàróóóË·lÙò·+W®\AËø–ÞÞ^yãÍ7dÏ®=˜øoƒØb‚"?hÂl¨õd)À8µãõAñï ˆ£Ý)w~ëNÙ¸áAÉÌÌ0̈Ž?~졇úùÀÀ@Ýðð0µŽrÊ&¡·¡Éhb©¡‡OEž¹nݺµÛÞÙö|qñ‚b¿?óŒY>Üõ¡<÷/ÏI¿NlOAMnꌫ “qíÜ€#fâ˜!‡m•U+|ré÷—åÀŽ’€ÍcnÁ\¡‰Í@ºï¾ûn­­­m¼xñbŸÓéô2ajó¤i*g~ÖÆøöË/¿ü³ô´ôLŸ? –Í/½(û¾øTÆþ|T¬·a>ˆj#“’»3$ à VÄS2"çÞ¹ çjëeAѱÛí’’œœrÇw¬Y¸Ž;Ö³ñºÝn½CÄFŽžk €ÌÓæSgnó/¾øâ?$ÙíŸßo©­9+Øž¤%¿Q<›`~ q ñSYiŒ¼¾DJ‚‰Añ¬‘þÖ~©Ù}V¦c÷ȆŸ€?²Ã—C]‡jÉÉɃIhM0€£ü^Kìç67ãöÛo_ûï¿üåÏ’’’~0OÏþÚk¯‰ëþNñ,eð†d˜»Q¿Æ/#€ñ ÊÖPd0¾Ûx6ç›ç‘ÁiƒÒòA«ÄÇÇÉœ9s$...qÙ²e•—.]º\]]Ý9kÖ,|äþ`2p*®éôÙ³gßòë_¿ñ|ÔÞëõYŽ?&ïnWÚ~Ô,þLàŸãŠa=޳rE#2¢ûY*ùP$±“?Ù'½¥Ý2¼ÚSŸ/ öªªª[víÚu¼«««?##cæ Cè Èb €óÒée@µ ·nýÏ¿G ‡ç8yï7ïÉ•G.I€ñ $FRLBZ0^3Ï6Ö;paÀI—xŠ* ÂbŒ«¸K‚¿³âk™ž=MII)«V®,ú¯7ß<4sæLÂkKQ¥Xä5U?ç©§žºéÒʰù`ooeçÎ÷¥éž+âO„Pc‰€ŠY2€Ì:£ÂÃU_¢áÚiÕ±æ ˜5Q!á-(ǧ`â‹_jÖœ’äß;%3#CÅ sç.ܼyóž~úé_”——»Ïœ9葚‘ÈèøÄ6ÆöéEEE¥?üð£¾ðÂ2jÙ³g´ÏowÞpLæ¹êšù ud®²%–¨ÇÏŽgÂ,X0%<ã9$HÃ6" ‚¶  åÊż òÅÁâõŽÑ$ÜvÛm÷VTT”ù”’’ÓEG€* o¡ˆ)Fzy?ùñO6%Ù“¸±ZÒ¶õ¶Ió²¦ðñu2o¨´É80… „ÂÑ1‡z0…¦aóf›ªGá_×P|uℊ±=¦ãÝäÏЕ›ššÊŒ¼EPÀ†ðÄNÚ~zee媕Ußðú¼‚íDNUWKíº3b DÀ‡`CÌS݉•Œce8¥bÄ4Fx–8ààs´Œfjº‚%<Æ^á¡=°Îþ(B mÇV––æäËÜysËׯ_¿üèÑ£Žââb¾}D0®˜ì`¦ªd?þøãßÅWÔ_ªÏT‹+«[FRjOL˜×B 6E°AôŠÓK# h OʾÍ-ö.ƒŒ ÃÃ+À -ƒ8¹ãQ Ýì–fg³Ô«“ª+Åép¦lܸñAì ‡ñÖ ÛîÙ¤LA×®‰/s «±å©Õomj•ºÅ5&ÄX#é!¶ó`þ„›Z¡¢ÚÍñªu2j”„Qï —ÇÀË1Äi΃þ MÕ¥'dxpHºð"†G ¶ñÒââ’B²Ð·‘ÇP¯Tÿ´M›6݉ý4ɇm¯¡±Az3zd,‘‘åÄr æIÌx!)¶±õ£¥_MD¥eym%€N¦p##óÔ¥+ÜØ0,"¡,Á#mŽ6ÉjÏ–´´4D‰öäG}dý³Ï>[±=È¡7FÊŸ `*ÓF2—,Y²ÚçÃë(Ô¿«£K.œaûa jF‰‰Xµ@LæÕ ³™"7W=j‰ník1„GÆ92µDS‹gÕN˜°Äíòl^Œ N•$>..q ì¯gêBcP$hPªF’ÃáÈÎÏŸ³€Ì÷öõ ƒW Ws\R-ŠYt˜Ä±¼J4ÚI°© êÀ”«-sb8C ã„1F ½„ %Îß™Ö!8ŒîžnUâlr¶Ä¹Ä.d$Ÿ‰(ÙèØ°aÃrø¾/ïïï“Ö,¾bÇHj50¹*1F1ÏÒ\%ªªÉ|4ÆÀjÂh¼FyU(ƨD3hLm”²¾r£ñ%>77·Q5€@´NÇŒ¼óèüäC àJÆ!ìU¼§’¡v&  –]$Ê Ì°[4)¦Y²×¾é¢=Ž BôA%d÷Z³8/aôüê٘٘Gýª1áÌJ¥NG"›ò ÁŠÝ-Z8F¹$s¥IÎdg¶‡—Vp4”À¦(IÆ’ÝdD•¬cEL!í†@ŽÐ _;-m\_–h>ôÄÇ:"yuÖ@As2fÖ1†³‡ óƨEáO˜NO¯©\lò­dN4|  Äãí/™ê?†xÚ›@;‘Gµ]šI3MÊÌ:WOi†l »Ö/ /` §PLª6ª:‰ßìCU [i;#Ó˜mL ¦HàêëX€T+‚ZåðØO£¥жФc©+¬]ì6f0j×þÕÂ#SzµWÙlSÏøÁØðit³.GãG@N™aÒqq6½úäu"*4âøÝš@ àé¬?nÂۣ«U‹¢Wè㙪j®†šA­aNÞª§âS†/uø~!ü¾ˆzö8ñy”Ll…r¤ü£R=ü†JÅf8f6çᱡÁŠðý×%s­óÔç3\¾`,À‡ÚFtñ„˜‹ÍŠø 4àJCC­$6<ê– Ë"〒#¢&ƒ(¢w+–V{ÙgÛ¥ìýâ¨P×j$óö¹v©øšDÜô°ŠqBšB`Íl¿H^KÁ®á—þÞ~YÉX + W!´*Š ÏlÅ”4Ï! ß»>!(æçÛ¥üÃ…&~“aàÔ ‡„A_@³ˆ’[K_/´4Áÿ¹#a˜ÖRëJ7»ZZZÎ3|è|ö–"Y€áÖÂájŒŠRÅ«& ‰diÅ!¨³Ø!¥ïNMd>©(IÊwj毮²Á´‚ɸbà Cÿ’Öb[‰øñõØÕåX¼ôwã£ÎeŒ‰©DåÁ'瞺ºº/´hmo•»î¯#—Qµ€+èƒRû2ÚÔŠPìÉ%Â/ˆ‚ ¬ Û^R1˜ßQzuå‰KišÁ°Ò8³M $ ƒª’Öõ‰ëÅÕí‚çwË.a5\iàW~a|O^™#–• úqeÌ`”ñssc³Ç—àóp -0Iµ‘yM¨‰šÏ±F9“+œRòFQ|èÌ;:¤ü7pœ — Oç3…¦i!¡‡Ò“jI•¹Áùrårƒú>0„­ýàwãPd£"¾×kšÁ0‚¡&\U;È#%~hhº"›AdJhã¦Ä£fZ© f»YW€bâµ0µ2E¼Z‰Ì'—;¥lW0(q*8ÒS!æù<.‘Ʀ>!ÝÒÖÖ¦z;;:ë/_¾\[-<Ù 9@v† ÀdCFa]p†Û½cÐxкšZYW&¹Ö<g Ï"¥®„ Ì}¦ÊêS qõŠã\BÒW¥JÑ¿Í3€ÅüÛ%!QÀ±„áê+¼QJC藴͈›)EB9}ò4>›Ç >:rh;¹°òpCó©à&rc\0oF^>/ÏÁ>ê—…Ê.÷¡ Ã+JªL=ªMMG±%¤Ž2in’Ø e¬Ý+oAíÕ{ ƘZ£`CÚ`À¦`úšðÉÍ:Wÿ¥é¿À‚ÕKÍÙ±'Úýøîw¿^PPÐ…/FƒJ±†R4°3È»z­--®ò²²oÁ‹%tv´Kþ¬|ÉK›!§<ÑOvѦŸ`Ý„ª* ĆúmÎB‡äüñ4%ÕB•¦ $#Ï! Píh‹‘6e>&)®TÙ»w¯º:ƒ]¬ûã?þWìluˆ\ˆ#ìŸh¢ @­ÌÀÀ Nˆp˜˜³Œß š›šåÞÅ÷Ê…Àyéñ÷@Þ™H™Q]Šõd0ƪ3…˜Ä3Wšpæê+‡§„a¶±N85*r>>ÙpÂUf/“ ¶†>Œ ~l'î ½ë2].—‹ê±ú,£'àøqݤêSŽï3èqÙ@~Tù¸|:ü‰:oµ=FKÆÀµúS2C¶M†£fI:H"Ç›Y¯>a£¥x|Ëuâ…çùé›~K]ÆäÅ©ööŽ“;vìx>/oF[kk‹ °ÎO㊦º¤ú¡AHò"¾¬ÜŠm$…W]y^ø×KþFöïÞPpÆ«a"JÅp8áaÌ+hF¥ÆÒññO Á\ýpøì<̈¾ímóšünÿ~ùì³ÏÕ§°înWÌàŸp®QÕï€+§÷W³CÕôàò§ß‹ /6âÒÑø;lAùËŠ¿’s¸ØáíÐ0QK“-ÅÉPÏ,C-ÿt‰úµR…s‘lÎ}I·ÈG»>’´ÔTìÆåÉ—›âi'nêÐ7*ºÉ4@ÐÆúúúû¹po94!±¦¦FúðéìO?&9ÉÓåøð1HÓ¦££•dÒH,Ãs´ÑÛôOÎü Øü²cÇo^^Rñ¶ß‹ûȯž9Só®ÏÑîû!ªêkÌS5Á=Õ‹×Év¡++3³2Ÿ\/]º,¸~"늿)Rðˆu‘?×Qã¿i¥Æ9;q¶l]ðºX¯ØdË–­òå—G%-Ìcåëêë_=}úô{¸<ÚSø ‹©€°¸ñð⎠7C[ú.C·àÊj ïÛsÚòXé¥2{©œú Ñnâ/ÚN‹˜híGF\†üã¼–ûâ6ÈgŸ|&¿ú_) „sæµû¦šÚÚ—ÎÕ×——×…N/tè ¯n»ž¥bÔ!¤Â±LGYVUµòï’“ á#hùùò½‡¾/å¥eÒœØ,Û»·ÉWÆ!©VÝX»†&ˆ sµq$£š–¦.“ïMÿ¾Ì%§jNɶ·· ^ÖÔ-r ðÁÆq{§z3Kü7J7_xÈ|tϬ°^ý¹Šããp!Ù9:2’…×ÍÙøW˜`â{a"éh³ð“zyE¹Ü}Ïw¤jùJå•ONÈáÁCrfð´´z&ùÚ ä3gHyJ…¬N]#·Ø–5ìø‰ã²ó·;Œâ¦x¦ rÔtCè{ ò[±íµ`·rñ =PLiå1N¥ëcÅ„vÜ"É‚ä³RRSÊ‹ ‹ž!å8GLÁá£eWTFË­ko•Ê¥•²°d¡¤á?Cðï3â±áš« ˆ×ÊÀ ßã‚ ’L{}Rísçëåð¡#Â3 ˜à–—bç£#£g¯\ÙÒ×ß_{?=#pθÆÇ_sUh§ôs#Ј•Ià J*$Ïÿ!ÈËž–½75L´ÛK¡É0xÒÅ "ˆ%[©deg©KÎiéi*@ê‡ÖtvvIOw455É… T;fÄ ^¬øðÔCÅ·ãØàïÄ¥‡h=ý”U^¯Ë¯#âPÚBì ’ ’üø˜ ÿPˆoñëá –BK¦¡Ï $‰•g ú°…%ß;tÆX^fàÖëÅX7O¨°½Ö¶e=†» t7ÌmÀ36vCÿ&£&5¾®4.jƒ 'beœx“LÄêó£B&ÜEì¶ &SˆÕœæ2 NÂÊâ ±-ƒ¯áÃ`šñF#LáÂîÿ0¦cØxÐ?ŒSÍø”àc¦›%N@\:s{åghæ$dj3ëÌl'óz¦*P²òÌŽ™3ëlׇŒKtFõ륛)M‰Æ. 2ʬ™ÖÏz¬ ¶ÐOAhaè:Ëp†Y¿iIpÓŽC¤ñë’ݬÓd¢¥ñ^\3«Ëh0ÿïÚÆ cüó”¡ÿ•ä&M?¿IEND®B`‚ic07El‰PNG  IHDR€€Ã>aË pHYs  šœ@IDATxí½ œ]U•ï¿k¸5¥†T! ! sH ºÿÊëVÐv@[°Å¡Ûþ‹þ[_Ûv‹Ï'ÝE[lÚ Ä‘d `ÂHÈ}pŸp¹Ë˺ àã6ŒÅÁ\g²3ÆtQ†»ê‹_üâBÁ…é]‚Õ›Ïç;(«x§§ËG\0×YAP²Mø¯¹ÛÀqpfÃ||˜ ³¹ŒÑòëu5àÃÌíÛ·Ãìú|à‡½ç=ï9qÖÌ™‡qäSš¹|o_Aî ÄëŠBE¥TK^mU„ ý\Ñò6D•Z@¨n¾P¡²*TQ¥Š¦”¼øÅÅ‹—¯X¶üæ›~öÈ 7ܰXÛ[[[{¶J^ëêÐ…Pp¹Æ(&”•N ¯©Vp¢ƒÐkáhßqp¦ûh‡é0Ãð1øbz³˜Ž0Œùö·¿}Ö™gþéÜß5rööööC…v¥¸®Rº ykfW$l&SNâ±ACÈ*WQÓ­N^’¡@•œd#,]ºôÅ|pÁÅ_|§ªî’0tIvÖ…@  Ÿ*Ð n3(hMÒÚkâœø¯vãÞ.~VÕ£æ]­Ã|˜Þ(Z7år¹¦ÎÎÎ1ŸûÜçN¼ä’KÞ6mÚ´©…B¾×ƒÄ"³â!W„jXoÁ$Iž)àå,Cùò|sRØ¿'O-P…:y ]4äóšz$n•«×¬^.­pÛ—¾ô¥‡ëëëÛ»»»wöõõ¡× >E |j2Îý{~½Ë¯BSi´é£ÝG<ÌwõÞ¨pSMMM³ˆØ4vìØq×_ý;þüÏÿÇŸi؆^qÝԲȅj·‘‹Ú†9 iF‰?!™‚æí ©ß.e—u]]¡³«;ˆ§b©¦ Xkâ¥â¨<$ B%T")Æ7M,ȪÊ*Ëþ¯ßþ×o.øà7mÙ²e³ÚÜÕÕÕµ]Ûtù4ᆥk0÷KÁWÇÑ¥WËy[øPÍçxñ¨yß,Æ·ˆñͧœrÊ´«¯¾ú'Ÿtòœîž®¾êêuäD|‘*áµ1„0hÄY jæoÞ¼9¬^µ*¬[¿>lÚ¸1,}ùå°|ùаvíš@ž ¹0f̘0~üøpðÁ‡C=4̘1#pÀá I“ÂÔ)S,ÖvìØ)`TUaOX³Ä‘M8j®Etúúò½¹šêê'žxâ‘Ï^qÅï»ÿþe„6 Â6qAp2k#x·Ü7˜ûê'b¼¯ áÒŽ·åK2|ær·š¤ê[ÅÀ–Ã?|òu×}ï½'ŸôÆ9=½=ùªªjÑWÚRd°kði´%Ê=߇ñ–MMÍ¡¯§'<ü‡GÄàåá®»ï wÿîîÐPߤŽCm]m¨©EɆ´…ÕK‡4&©ò8ÚEú f…ŽŽŽÐÞÞÎ:ë¬ðæ7Ÿ¦M;4œrÊ©!—«–@ìˆQ‰\ª’ðcø#Q8Ÿï“àVW=±ð‰G.ùëKnxö™gW+¾]ö v‚Ž‚/!ñqÀ>gе¸~hÃ/W÷>êñ-\±vîÜÙüŸÿùŸï{ë[ßz†–eùªê*i ¹ÌÒÅ쫹?ô‰‰-ÍÍáeè‡~8Üþ«ÛÃ=œ81ÔÖ×J^¤ z£6 zź¦ˆ9c„Š&š 0ˆò`«úBbªv©Õº6R+qª"\LðŠ 6„ÓO?=œsÎ9áOþäTiŒ™aÛöíAê_Ìž’€øG}U–pIúª%á÷Þ{ïo;çœëe,î”±¸Y|jpmàËGºí€ö‰ƒ1ûÊ9l|T>#’›/ŸQß"u?^ê¾õk_ûÚÙ—ìò÷õôõô‰NUtÝæw°”*ÄP-¾CM.´ä?ø÷„›~zSX²dI7aœ™S®þ+çi4Ãìƒä%?IA‰á0Òz¾°Œ¦d1‘öóÏIÖË—Pä‡ÑU1 ±-[¶†YZ„¼ç½ï ^pAØ%mÑ#íÒŠ@Y‚Ò. õI*«r¹ª®¿þºË/¿üWš¶I¨¶ª„‹‚o*ÑuoÔ}%žs&ÄÉáâ3êùé<¯´quuudÙ{iÉKŸ?pâjÁ]Å‚ð‘ùô>Îëc5o¯ ßýîwÃ÷¿ÿý0}Ætáv0¹òøÊPu´˜Ý›.ô%ôJ ¢¸ç_ÃÈUȶö Ÿg% Oi’pà*¥¨ª«ªÃÊU+Ãź(\tñÅaÒA“B[{›¥[!´‚@èWô©Ð·i˦µ3gÌü; ô6M3›TŽiÁíÔ˜3ß}9?±W£©ÃaâÃx.®3¿Ua Ù0áŠ+>;ÿª«®¼H›5yéO[¸Û¯LI€Ô|^sx½»*h'.<úè£aüÄñ¡·KÓ¤ˆ¹wV…ªi*Ã!°[ì–:ú?®=\ úˇžŸ÷…Êv a®2ÔT×톓N:)|öŠÏ†É2";:Úe[hz€¶²€t7/û¦ªò߸æš/|᪻”¸EÚÀ…€‰Émg¾ûÊÚ{çÌÚ{H‚Ã+>ò]å7+ÍGý„ûî»ïcsçÌ9m\£Šp2úµÒá3W\þññÐÐ4&ä{4Ú«¹w‰˜­"g¯*¨•‘ŽpZÞgBã³¢Z¸lþ¿<'EŸŽ•93 g7;ü½„·®¶Æ†1¸Æ‹–QOöž}öÙGO=õÔ¯hE²M+×츈&HôQª²WζW@’Ê ŸQÏ|ÏÈÇЫ æ(¦Ž×òë²Â+sµ5Õttæ3BzûzCSccøÞ÷® ?øÁBCsCèn×&ÚìBÈ]ˆL©¸ ïë‘n ã'‹SÅ«Bõ"‘ ¦2ÔUÖ„vi€÷½ïýáƒü«Ð¦e'¶-sÓ=—ÑÛ£>&L˜ðM ›“)Á—ŒÙÍ#† ÎýáoÔŸ#¬œ©VŽùŒ|F=*‚–XOŸ>}¦î­|K+º SU-ËØ–c¶$ãµ< /¾ðb8~ö áÖÛtÏ&ägõ†Ü5šc/(¢º¿1:dq*¼¯/ôüSW(ÖgÓX½úqç¿ óçÏK—¼´¬• “§K³ý—јÓvN+ƒ›µ >LSƒÖ-F;h-P ,'„Ó\I#wqH¼>5|ù Œ•ÏÈŸ¨ëÀ}èC§Þ|óÏ>+Eɶ]US÷ÊÔš8Ôi½þõÿõõð?úÐØÐúZ{Bþ“½¡â›^35/ô†ï Zíø¾Ð;·'ä^ª ²Øœúƒö)0fO9õÔÐÕÙ•ô+̶`(„ /¼ðhÿaÅ‚ X&úJÀ} ûèwÚǤÆh ˆË—yHmÊü/\õ…·|áï¿p‰¶JM׃=ƞ͡ŠHêÃ{Þýî°yûYö2ªÞÙòçÉöáv G5©ðëÉ9kD‘ž“»Bߨ|¨{I"›6l ?úá„3Ïþñ½fsÁxc¾Ô_mMíÖI@BcKcèÐ:þ¶-&Š(:'¢‚¯[—ô!?¡/tœ¶+Ô,©·C]m}¸ýöÛµÍ<Á¶žÑ‚0žaŽƒVsN8á$­"vÝyç+•ä̧HVP|ÄnoÀ™«|äÃ|›óåtåUW½åã—_þþÈp©~„€?͵"À׿þ¿Â÷?ª´­Úù¦ŽÐy.÷Iäö«aÿûMfîÎÙí¬CýêzM{uaÉKKl7óä“OÝÚÊ6yA¤D®pܱÇΛpÀ„mwßu7B㳂@ɽr#%µ3棬¹u‹ÚÇà›tÁœª9—Øá ­ça¬{e]èQ§·^²)ô.Kß¡ Èh;ÚŽ3+}8†RÖ¸«û w‡ö#v…æE"We>ìjkšïÙgœ´+*:¨  –ÈñfŸp²ÒW<þøã4ˆ|_À§WCÁ`·2 J»¥ï)ÁY•e>{úµÔ;øÐCùÀ÷ÿ#K#¿ ý±zu_?\öÑê Wm™®û”¦8º‚ù8Ê®ÓÁj½l"7£.½B¤ª¦ÿÛ,í$V„šª:Ý|ê WõêÐÝ«­d-+ -ÑúyÛ[ßzÉâ_\Ò¾k×z¥p}–ˆ®l•2D7TrdÁQ‡Ë-~vø`þ1’Öù“Ö¯_ÿ-S÷qgÇT?ËÝ —]öÑPßXzs½aí‡WE§ƒ£ävcºÃIOË‘s4‚±«¾O¿qVÈõTk@›Gíá«_ý*û¶ÅŒ2ˆG&ºe=éÚ,ڨ͢ ª0oú¶1Ø•ÃZÉåÝpÉBy.°ñsz¶Ã§øAº&¯\¹ò›Zât Fj_,4çëvì§>õ7!W[ººÂšˆù :\ T¥Ô¥LÏÂʆ½óª5è¿ý›wÊYUN{”:/@z&¼WÚ!¡ÁŒŸj:t˺"gÌÿÒÿü’MJaJ0- ôµ{Ü}È´iç ´À]Ü?`·#gˆ3Ø)6ˆƒ‘ÃuPÛ àë}3üî¸ãŽéfW¥°ÔI[mpHÀü\MMÐÁüºÐW›Ì§Õ24&y¨vògp¾éNQ†+œ^ä±ÖR¯í Oâö¸å©\Z‡úÀ¡žÕm$>e% ÛKîå¿|)jD3Ù‡×^{mÈUçlÚÌ‹†¶Y¦ÓoºmRyÏ=÷\©*М½x/àIM¡!:* Õœ+«úýãuKsÒÇ/ÿøŸž{Þ¹o•X™®ñ%ìzÝø7†M›7éÀFoxù¢— ØPUÄApsŽ‘$ÌÀ5ÊóÊúÎL ÎútJJ¯ïmД¥ñ“¸l%H#ÍÇn -:8i `7ÁŽ9úh3œ9ÆYÝTª?¾ujssËúÇ{l•ŒhF>†¡… qÜöàez1hIï"¾ŸÖ…ùdÔM“W­Zç}Ô>ëYü(Ú'ŸX´„ :vž»pQd>£g„ΘŸÅ: '{‹P?^ÁÃŒI*œ©ïÅÈI‰2?MÝ-Žì˜¶‡•À‰¥9¸ªÎùé‰vìLûåvðäØcމ¢w˜¸é©©àí²»ÖËîÂ`*ðÓÇ`ã—‚»á²ÁüŽÚ± 1¿õÑG|^Òú¨}¶zµ›§½:%sÛm¿”+„%ç¾*ú„õp[ÌàÞùeŒJÕ3¶‡ç9g\›ºWÕ,u<ì|4ß4ƒÃž.pOÛ …©µH’'ÏÚÊJe†â¿B,ѧx3­nÝ"jÖ}ýÌ?ùô§?ýü«¾»²º²ŠeŸ]Rý:À¾üåÔñ¬\X{Ìš°áèWFÌüÝÆ0Þ|Âä`’ù!<õ¾gB÷FǶ‰É#ýíë̇“ž—‚78>Ê“&­YÂÙtÅG4H¼xr8äùCí˜DwwOøÄ'?!ãPEý¬ 4Ó25ôýò—¿üΗ¿ü囵¾V­oÓŪÀ BtÊ ÎI7X!³>ò‘66{&k´O^¶lÙ¿E¦'Ì×èãüÜo~sGX²tIØÑ¸#¼ðÿ<7üAóvc¾:£l ã•áB!ˆO¼ãéеVKä=’`Ðæc¦š{ãÂÓ0(k†L¿t¤26gè…ÔIùÄ;îžBÓ®FíÔ†C§M gœq¦…&f·`T„™3gž£;©ë¤…9ÉÈÑ2?V¤A)àÌ¥à`µÂå'{šÅü–ë®»î}š¤á£ê× X!ØV®X©íÍeZ„ðÂÙb¾b°Êä¥Ì‡vv)Ê{œ:0]ñ¨â½r´;°¬ÝbÛÞfL'¿xíŽ#h€È0œŠ?}æ"zµ­¦V¬\Ö¬[c!Ó,§¥XeéAßO~ò“ˆù Lödà‘ók îI<ß5@£ ¿–ÓgL>CNXB©‚©þŸüô'¦ú–¸4Žˆ3L×ùÔMm`ŒØ0#2?KüÈ„QÃlsÐâ ƒû1WiýýÊõÇcXB 8h’f?'uÏHáW¿ü•™pz4ØdVÍ›7ï/ôìd„ØØgð*Ë;EË;/T>7¦R‰òŸ&úæ¯^ýÏïÕ D¸øèWg¥m<º Œ?.t6w„M‡n mwו2·ÑY 3º,- †M£iÃ6dRmàíFAˆÂËÑv¬S,ciÃÐìEl˜úJèh”q¯¶[ÇŽ O>ù”üH÷¸½ÞÓÝÝ÷•¯|å2„Üáé*xÏœ¿Rà ¨ìnÎ+QÆ7}ýÍsæÌ™vüñÇÏá¹YÛìA%éDO—Œ•§… f„ðìiÉ’o7°ƒ'”2¿?Q!&LŽŒŽLÃs‡?KÊ4£´¡8'Œ£é¾Õ-܈Kùºâ¨WÀë+yá©O‡Þ­Ü$wâ 'ÐPlO ãüÙG˜èx™Øªˆ¡:V º$.‚É*“çeÊú*·hÆSá'§)»"¬^½:¼á GÚ}»S¨‚âNþ-oyËùºýΣfnÂK¿[Ø4²^êè‡ùÌÿ¾û7fÒAMÕ{u*Ø…bÞáá½#'465†ž\wè¬Ó¤Q¡dù¸‘òI“:æd"–¯P¦\ö<æGM fèñ,ëE p/´ev‚`+lBGša ò£0xq'Íä ÅQ)1Ÿ€\àôWp:j;B·hfÓ0&¬Z½ÒŽÒÇ©Àl½ÊÖq­³Çw}SžšÙ6JÀ à#5&RÿW~þʳÄpÍê +ŒþµkÖ jExvÖ3v'+ |ao‚0Ðü.P¡2å–T`æY)«Ë6lwo‡L»Ö~’Φ9â)>Þh¦–O™„ñ^¿ŸHx½ÝýŠ|exjÚB1‡ù+„¯l´GÑY’knÖ K|úÚÕ_{ûøqãÙ‚‡ð2i)õ x©HAÒ]ýóî½ÚyóæÎÕ¡N @d> íÔ+SÚõ =ÒVO\5tIðd|§h¥Ätâ9º*‹‹ù !ɳ|A‘ïyÍÙæðÊõ,æŽè7j–b[.°iû@‡®~8^Nr‹KxrJ¯:˜ÏÏVŒ_Á,¬z:6Öi¯¹Éëö»m aêÕxG}ä›6oÙì<ć" Š-”’‰\¤»Àš¬Ÿ:uª^Å&9gã  †¶é‰ž\MUx¥uŠÈí>&ïé·Tf‰Ì~„´xRBaÔ±iF~ÅcŒ¢S#±­¬F‘­F3þÄ|NøpBe‡$ݱu-ëŒ hO¾€Vt¥aED,ÆQ¢R ÃñQHT éDÇŽB z:Ä¡%ý¨9g*ðÁÙÛ±>€›œÿ’o±$Ý2ù¡C†gšÒ?@þð×4¯² ˆtèµ4ìÇpÀ–é¶5Üݧ=›ùÚ!ô•”òÜ7ÉÈ6I— @­žR­ÖëÓÑÖ¥† šFÊ$<ç^«)ECg Gцé¬%UöU PŠA-ßr”f)”¢D óõCžIAÌ;~4 Òx(3á ˜ÃUäRGŸ0lÍ6®8QâXÓ#/Ba%PÍ ¤`Rôc½Lf„dݲ6JáDÅ_5Ó‘“íeýÕZ¯&ÚõÚ⺽« WØ©ø¥Wݦ7™1ú¹²†` +?iœ½p°U€&ýbwþâüŸ×kÔôh³ ‘å­ËúUr„þa“jµÈüHÜH+hŒ³’^,x F°Rì8I u’âÀHyƲN=ÍÛò.¶o¯5¸^W°L¨Çš>WlTI»±µØmZZôeb¬kýò<÷)6L÷rËRãÓ±4´4x`ŸÙûQ+ôÞ¥ã–N´j®TÈpæS‰Ê5¦V”ãË?Ô¾V:ò]¶6l#ÄÀíùÇHåNˆkAð¢‹LJ¦äì\Iõ„ †90‰Qª0‹r,ìB(âeÖ²rí([4šŒph73r!¢*?æ’"3“¶­ ð#_ÎâÖ¬Õ 6Iø†Ÿ"±dRÿVÒaÄÈà¿h¥Íõ›í6±MQ Û* áÏÛf7@\²† ­zYˆøÆv]r~èÂÙqß?Þö… ÔfÞÑy€°½Žsˆ#pŒZôV3aK§å”Ô!› Jg£8á` ÚLTµ_B¢S¸öN`Ú±6È? Z:‚eï7鲬˜\æb-‹ZzÒÇ$7-¦xÚwOtAJÊÅÛV³ÕÞM¨U¸iõtð ƒýo>õ7zÅFY `d”ãR4†ù©:íÐ)zwŸ–Øz¯>Ì×ÐèééÒæƒnJ¨àšÆÕú‰£¶QZ­‰P C’1È ¤D¥AsÃ6iÆÈLY¹¢fˆ#ñ$ká,Ý”E7w…´©álÎ7ÄÀ3AÈ0UÅ+D,ŒÁ8/êʦ'ø¦°ÁŸ2«VªYa!»„GȺõì…Þ¡¬Ñ¯\ Î ˜rÈÔ“ý„ .ÙD½Ÿ¯YÒ¤?û°‚:`‡LýÇÞxÕ=û mܱ¦UÞi)E§I ɈJgÈAÅ‹¤ÔIêS‡„7†©«q€7ªÎ´M"¥› x"1ë Gð% §|«—¤ÇN”©Kïù7Ë-áø®r½„RP¡iÙ.mýóŒA}]Gøás9!°‘Nc¥èÃ|*Të9µF_û3—2ǰäà=yÛkyeèÎFLRÜT aëHæ2IºÔE#ûÓ/!Ýà/%zÒÖ^{P‚66óßH6lkÛò(§¸—W4ëb 2UÃÊÅ”¤sI½lÁÃÛsÛM_cÂ(Ó€m kîÎÕäxr~ÆF‹à¬a2Ji.•ÚHhÕ|ÊgU’­F^à(¸-u[Jë9θug!CO„•Ÿ‹b)Byéè'¿ ‘Û–{V×K:ô½÷mÛÕÀ Ô#|2Í^IS”J&@™ÄÅ`’f ²™6E‹=öšåýMµ:…%lÇ#ˆìÑ`ÈbI*Æ44ŒWMýÎï´QOpèdp‘n¾ÞY×2¶¬’dÑÆ+Ó»ªxþ`è.Û©lßé€Løé£ÌèH^–‚I8û)/œ¬`ŒÔm§†Ø´G¯ÚHÖ!ëȳL/@a9Àei;® ª¥Ð¬è€?]•³³Ô‹|’¯A*eP¡'³™hÙyJØ1²D€“È…sߤ¦¦¶¦"›zT A3:ªtðP#qÞŠq\ˆ –1?/‹ ¶¦(/ ”„­§G?íEÖ^ÕFd-[$é:)xÃ6MJXlhé'M¤ËD•7LÜU¼K<€Q#IôLpc(⛃—8oÎýTbvñ× TêõZÖÏf šg]í­ä¥F^´XyÄ!`7h$Ø•i‰¸ÑRýG²‘Xt5ýãÅœ‘…"fÀ¤µb‹‘½1^„œÄ¢Çoª"Œ$¿Xox!(Ì” À (³Ñ4ð)¼ª¬¨â^®lk¥S@,šœˆÔ‹,ÈÈx€sÅ'UduVÉ(×á è±)†ú(VMÆ([W†Ö¤í½JšÞßÛ/ÃRGþYWlÜC´fá,e¦TR‹² žYŒf6m°0´ï«åÏ2Pœä{Øg¦­á•¶ƒ…7AÖb9X @Z &MýѲÔX@¨Å‘2àÙÎûkkC"}ûQ9I‹¨Ã/2LbÖ¨ü:ŽŽ¡cB:ao;æ»Øz)GapŠ•–öZƒùÎ ³´$´•€ô¿MÜ*'i€¥ûžåý‘Á§÷|!‰úçŽ ¯.áõfzìB 8i¼êp}'õœ”†AæÇF‹ZR˜öd†$%• Ž8XA ³MÊÏÑt‘900¶æÌr–Ú #k<"~Q,âo,q%&a+vOÉd– ‚ <ˆF cYSK@ÑkZBƒ”m܈þnn0 p^/"èÕ.` £8‚Ü j ºû¤¿½rX}àA àêFâÒ‚É&]gÆ’' ìå›1¤bûÄE há‚p‹T,ϲõKZ€¾¥NõûÅ)Ì¡;èR«oß0%'D4|ŒW$ÅϬð1\Ik1Ñ€L/à>§Hóºë×U£÷ºC`é—ø(’Š2ïÔUèÀÉðpŽ­êצsCR CX„ Ÿ#³þÙÓ¡çïC¿¯Z¤Ð#<¦èi§2Ο ÕܸêPÙ¨“zõuµ¡ªAa]yK8ü›³D;õ l“: ‡ WW¨7^ úÚó¾ø®F¯ÔN ž×ä.¼Ä9oÝ·-˜Éàr4óíz"¨QR%1—P@ Tè›8 örÐXq¸¿†ý@˜"fÖÒ•Fc'Ü}\XôŽgCçŠN} b¸­ìûò®!ݪpgóˆ¼yOž Qd¯ƒÏt2âYÒyC§‡çÆTèܧˆVÖóZ0GŠÕ@ÜÒƒ¤;è🋆ŒÇ»»$ƒLÈoßÕ¶#žr#Ašdp”Ÿ¤"#pB¡8g&õ5ò² %ÑÇÞvT¨Ÿ“Ðe3ö³0øCµ„Ì0î‚ãÜ|JÆÅÌ)Àì—vÖ;M´P,:Øï¤Â$³þ¹3Ë)-”ˆ‚vv³PÐçj¸]kÚ\¾1]~Ú‚cæÍ{[¤R¯Î›m‹†`T/Ø@>‰2!àå‡ä§sœwÜ;+Jd0ìEI6:ážcCn¢Þ»ÏyÿýÙAQQoîgö"`~Ä8öÑÃièœM¤‰CLÔû9sÚ•Õ;š@È:à›>pÎkc±ì×PqðÈNa¿z;:;vÆÝ%%²¬p$÷ÿ™ñA ®¡º´|¦’ + 1lF§ŒÐÙw*DÐýU$ÌŸóøñ6TêÓr$Ácˆ”xEzY‚ø ß‚N‚¡øTÐe{2šnx@·GïaŒKuöÄ/MÛ2át·(}àn‚ààíã§ÌW¸gg[›>”–OžŠÓ@^Á½6fõ&v0ª1Tgú/)ìaZ÷‹,Ë·9U½šûÈñ¡JÆÖ~' óç=1ÛF}¥F?C FXˆ>ÒŸŒë/êÅŒlz1µH[?áðÂáZîêsyRÿ,ÍyÙ´‰Ó@³›[UÓ§€,Îr.›‰`ô¬Ö ÕmEÕÂM†®î.{*¾O,L´ŽöGoh1ÓAÝM%2Mhå‘×Ãosþp|àû°ç ŠÍ{r¶1Ô׬W´òpÒa§6¾]» HRr@õmƒQ²£]†2ÛÀLºDÃÊõë×-³ç¼¥Ä7Ô²@C âÌ7¸å–[F€R+g¦$”.ÓøÁ…ɶñ@塸TºK Я2ëêrN=Ò9ö6æ<>Û–:¯¹À|¯¹OÃ|-ÿ0ø–b ;£UŨï´HÒ_O+×ýL›=“õǹ ªÛwŒµ/b÷md§Ù]A±òúï_ÿ{eÃˬ ŠµT*Jï'¬kº`|¼ÏµëÙ6=Æm‚9ú¶ËtÒúnèè‡ôä‚`–ǯ«ÏÄ÷%–ZÚóÍ„À™¿Hs¾¯J T!ŸAìOìIJ'ú˜ôÎBOs‡˜Wq’½/@ó£½/€o HQÛC!¨²× 0<,§¬R —k>FÐ¥O­/ÖÜ"¡ŠZ€ù¥SÓ@CP6šv_Öø=üˆH¬Y­E-!“ò”h}°Ÿ4á@P~.j—îÁŒWÓ9óŸ9A­bí+Áð¥?Â-¢ÃàåyVš„ÄY¹¤ç Ç^yfyŸ%t³þ \­QÛ|å<Þ±£_¯,lÚ¼ñIA€‡®«pV<ß G†õnذq¹æ Àej5°]ŸGå2(9$ĽUëdÒÓHêJ §2QuBPK¼X>yI˜÷œ–\"Æ«%¦q¤Pi)µEÓëSdâ,îé™4/›Í÷°×ÄGŽ’–$ú×é]A|ÄÚŽ´'¶:{ëÖí‹u¢Ëy‰ï­¸_Ö|'Ó+u‰á Õk©$oâ¿Ñ4@5¤—©`>[qì°ì€´oÒ™œ¦Ñ,’2Tóoä.x•\ÉxS.–MYÅ©“ÎHŠÓîK˜W©ï pDK?=¼6mÜh+4´²]}½ñ†#à<Ìýš)%““ß4€ü. íYüÂâô„%˜ÙÒ;Ûv½‘J³έ:O¨2tgZ€âFÀ„ I8‹¼XŠd•ˆqµgL‡°TÖ?qÖßZ†ÙÜŒX*í³9_ªöÄçNÐ žxÄÚ,ˆ#¾7hi§8xN‚òSÔ¼l1@:hü®šw›€ÛjLèíÐãáq(.Ó5@*V®ZyOKK‹óß0ÌøÖF©HAÈÇeË@1yÇ7Þx§Ñ]ëO:…¨ÒË xQ;‚M•-úhàXu#횪Á9Z*Ú¿¦b q"“¹6¢b<Ρ”åm.¬çüAš€úåzŒ!:¦Éz˜·Hj_pÙy‹¤ŒíÑ~d¾Š.ŽGQ ²ý Œ_JOÜPiF¹±­¡¹ª)Tkê%¾|ùËzIGΦæh¤Ã¶BÅ·¯ýöÏe£ñ²HxH¢eÈïç"h"5> pOy—ÔÊrÞ†šÁÂê\²dIhnnÖ&H>¼³ú/Õ?ªŽÀab&DJ ãñ„É]in@)“24o7`t_~†!Ý.ÒzXˆÁ|V'=+µoÌ¿"C³áÈØˆÚ\?¼ÓÔ":Žó±‚¶ï®}¯Œ? >Mþ-zyôóÏ¿`ê ¤hbñ&¿sçÎÅ Ï\ýg5@¿ÖÊ háð¬ÈM]xøáÛØ”·ýåbð]›¥K–šDήfM<yvaî"žK… A3%Š)!l¶ ÷c@¦\•‘!h‚ê±¹ ÁÑ2æ‹2óžÒœ_‹h 6ª$m£íMø€éø$aJ+ƒû uÂpê “ù×X‹¸kÐ+â=ý´ä@S‚ÇéMì³›o¾ù>©ÿ6¡3¨úÝÀ»’ €védPûsÏ=÷=¨÷PÅå Ó÷6ÊÍUÔ„“kßHÝa»~BP† ©º²zKG”ô£¥yz•Txus.—4Áœ«ýJ›IÿqÊXe +¶WÀŒ€^0<–Û­OÎpåĽ±ö”PËè×=‡WÖ¯ Rõñœ|àçòå˪ :Ô?+€[LÀ`þ`.iãwÞøÃÞ¤(}Ç$‚¨îEó s>x¨ÇáÒºªø^8ÐõQ‹CTŒpžïyåÒÈSy„€×Æ÷«ÌÄ)_â˜ï9M|âcŒ|µ'BÛx¥Í¤ó©§ö‚T(é¸àã2u†Xr¿—ù¤4lŸÝÑ¿áþ°;²ã~§Vøæþýßÿý:½-ãžÁ;§XÙ6GÊnvb_èÅ›u‡øSLB€Ds7jㆠ!§“B'ÔΕµ:üAJ$PýG}’¡S†@ôÈT`¬ƒO™˜§˜¥#µSêÂq:^VÎ1ò¹Å|âãsLíÛA‹R˜í§Œ÷´lYÚ-)Ã^&ŠU94vKƒ&c+[üº9ýµŠÂúõ¯„mÚŠ·#àð@ZyÃ+~'>lU8k&­:6ý›H(• oäFP)»ôuª]·ÿêö³ ÚQó&J¿ûÝ=v@'|ºñÿµ< Ç•#6@2L`éQHCúFpIsj WwH]8ò†Ãû¡äsþÜ?°½ëí©ž p8»Á7\ĶR\UÏÛ'ÍúׯõÁ#Ð÷3-ŸÝø‘zª“­¥OôFã/a>Ë?®þ¯ßþö;zÐÕ?<ó¡2`#ƒ €WˆOzL`ûÒ——.[½- ûOÀïÕ§L.|BÅ+ÂÁ¹ÉᔚSµo5üÍùR!€€)àjb·4Ÿ”c9«“„É3hùÔ4»1ý“#cü*3;é±d©‡Y”aºÃqXØý4˜j#M§‚ ¹I¹g‹[«CûvR{Z˜R3ÙÞÈ¢Ù^ßeX ·ƒö˜!nÛò¢{_O^7ì6ÜóÌ3‹^îîî.§þÁ ¬Û“8ùðm5 ¿M£½í–Ÿßrƒ,Ð*˜Ï¨C°@±˜p€½AäÃMÊ¢lû&Fâ%ÙF@R"Á!p¼”¢p$~’–=ŽÖþù^–mÔÆ#¾{¸Masˆs¾oïz9Pç-ÞV"ÇvcÛ%ŒNð-â™À½-Ÿí.m½ÌîúagM<`bxðÁV4·Õ¿Cõ­¿¸íý|42küxP exªwÖ|.½.._)Ë¿pÈ!‡Œ×GŒ§‹ï|1Üæ¢j};ˆ}#Þp„–,}aŽÖ­÷uÞ«jÃwÆpªÑjâH³hšF ¦YâãÜO*§°<.•Z{pM˜zédÖOéÞ~¶œ… NÚ˜Á-¶£š%Ó^Q b2xa&iCôþaÂ?jׯ%ýHÄ~ø£Ð©s¦éÔl·wíÚµ?¿÷Þ{-…¼M +S.¥DŒöÿŠ éeêk#ª²ZKÎ8óŒ÷¸4¢ x:u‹Þ!̧ãÆÆVó‚‘¾ðRÏ‹ý[bŒNö# aS©Š˜®ø_ÝKf}Á°hâ#³ùá=7& tâG¦'e)#µiº’´‘2ŸÞ½½ñÜpZÃiZaUÛhI;® <"ÓKgÔë.˜®¼æškþZ¯ìÛ&cµvógÐÑÞ{š(ƒ£KCª®viZoî|ü±?^×Ë{äÅ|»I¤LÔÕí¿úuÐn”zwó{ÂAU:¿®¿‘:ˆbX€‰]Ä•šÌíż$]ÅP߯SåĽ®Â) Ò"옦8å¨#:ÖFj*—P$Âm¦8$ù†³ú4šT=)¼wì{íè=÷YZƶ†Ÿßr‹ÙÚ†5:½óùÞçžþjúèÐè÷¹HÆŸc4 @Y£…|ºíá „ᥗ^\7oîœÓõÚ¸&!%ü>]>¾0œqæ|Wχ³Çœ~Öv³$Î^d Ìa»Óõ f`h¦Hѵ0Ì$gÌSóO;G€!^:… ZN¹|à%*9,m¸ÛwÝäëm¤ó"Îæ¦æðÏ_ýªÚAÇ¿×t°ü7üàÊðãeM£À}ÿrÈ G@. !暨žkþå_þNaÙ‰P©>½¿~ûÎíῸMY²_E±'ýX‹á5r—Lp V6zmÄøÈTÏÛݧÌî—ŽV4Øæ[cEaq­‘-…KpGè Í¹I´Â&‰мùæ›tòj[èÅÈ֟݇‰ð+ÿõ_þõBí΢öá…3?‹ù1ªÈÉ f°6Û¤z¶ ‰­xä×0ú©PUv…Í[+W®²4&œïLú®Ò÷ÞÙ8pŒçaù‘‘´Ó}„Í–0lc}«›ÀŒ+Aµé )”´ëã2I‘÷½)ùŸÓmö>=}µ|ÙòðÐï/4õ'Q!­úP‹žZtUmmíNíúíP ¼€'>ú)5$7Ô)ÀeÅÛÃzY_±X¾bÃarZŠLTaÄWHK’e´<öØcáä“N楅¶S8ÌüpgÛoæˆ}×e‰Ÿ¨ç˜'T÷šúj9íDva‰BDnr•’TñAÛf¯¾9ùÚКj«ëLÝ·µí ×|ãǃ·†¤Z“thµÕ­ jþ­ÄêÏÎý¥Xî ïÿ f 8UxB«Žñ2 ºüÄ'>q³°ÈE{@›:AÌHkkßþåškBgg—¾lÕÚòíá¯W_d6ÁÞN j·ŸcĘ+×»ri^›jžŸ€°¬lØŠx!¯82Ÿ9Ÿ¾_?íaLU£‡~(¾ú×ùHÓ8¥i;˜<ý+(h‹ºë›ßüæ™JÞ¤k»®ì²LK°UÊ n¤=¡À ø8Q‹¦‚ƒ¤’&~ü¿ÙŒA1Ÿ•KCÐêèì ßýÎwÂvÝ7è)èùBÊ÷¬øKÉiiÁâbt]*6Û[È”S&›–!cÔ"dtü\…ú,ülÆ «°ï:hß¿¥9|ð‚ B½n¬ † ÇÚpme_û­kß$Õ¿Y}–~>÷ ÁáØå§ËBe2mÒ.áæ›~zÓ¥Øv1i韇rúÊØ%—|8Ô7ÔëÍ<]\n™~k8 7Ñ4A¹ö&-«¢-l†ôNr|Zp‰®i¾âü¶cäO¬žn=ìWv ¶ÆÔ~!èÐMxÿþÊöùùT¤!>s!ÜvëmçŠùÛÄüì†Ïû#Bs¸6@¹F„^êd«ä :<Ò¥'ˆWLž2e¾SK¶-±x§­ž7 §Ÿ~º=ÑŠP¼­å†Þð|çs) }ØC÷”?x½kÜ_†ÏM¾Ê€=Ö­½ýº·é¥±‡<¢Æ ´ÿ‹=ý©Å‹?©¥7#µÏí^ ô÷KÁá¹ÑoÑ‘0U´~ýúÍ’ÖíúlÉIQ°.Ðê@!§G™¸£uä‘G†ñÆ›š;ºþØpÒ˜“ÃÝ;î²H£m8’¯•ÆÃýïiׄ7µœa›eh@ÅK/- W]u¥ix‰Ê7¾Ûy„ŠðòËËþñ¡‡ºCtäQoŒ>·ú]íg!Í Ù–8 Ä…ËëÜÀj]Ú5~ü¸ytÔ/°“¥0ßxà¾ûîŽö0ÿMlõ„æê–pþøóÃÚžuay×2›v›Ç#ì×Í/êž>Ìo>3üÿ3¾Zµ5^­S=œ7`§ô{ß».ÜpÃõšóã =mŠ9ßN"…°råÊ«ï»ïÞ›Õa >Þ÷óQû®úöJ¾ ÎaàûÊ †ÕuÀßxÊŸ¿áˆÃ>®sdšË¢˜@ÈHd•0vlK¸îºï‡M›7Û!S4ÆêîUáÊUŸ[z7 Äëׯ¾2íŸÃ”ÚClÏÐfuܸÖp¡Œ½­:ØÁS=Lh„ìµbÅJüoQï™óù~£Çïþˆˆ4ZÀ…À‘ÁwõÔ§gVê~ÁŠƒ&4æFM "FGgWwøþ÷¿fzh˜2õ[ö4V6…óÆŸfÖÎ ì¼D{­+}aêÃ'&ÿMhä?Ö¼Œv_°`Aø°Œa ;ìR35•ÁèçšJ…/þ”ÊÝ¡>dGþ¨2ú¨ùQsÀrx® øðt£®±êÜøqãÆÍ8û쳯Óé%ä@¯ÑQfMþXs{{G8æ˜c‚în飔›ôØs^NЭEbu¸oÇ}ákk¾jÈB°ýmjÈâtŔυùcÏÔ~GᆥzÈ2Œmm Ÿüä'ÃÓ:Í˽ýG¾©ýBeuUÅý÷ß®Ž×-‘²s¾30À|°MFúã iýÒz¥B€ 0°OЬ%â©¿±ïz×»nÒÀ4çÌG8ã!·íØ.¾èâð¡ ?d÷¸%ÚÕ'AÐ3Zïø}øÎºo‡M½%Õ2ƒ1„_;W-œzµ‚™; \6é£á´–Óµ¢éãu~Oó8꾩©)\ýõá»ÿöÝÐÒª=3I¿©|=k€ÏCâf·îüåñ‹_œ­ý”m²‰PûYƒÏ;:j̇j£-Y˜ÀF¸üô‚ñ¾Fz¥ncžÃø¬Gœõ#3ˆj½øˆ;a'ÌžÚ´LâYøn4‚6Q¶‹>ßYm¸wÛ=´™®öµf@@1ìü9È?û§áÒƒ/×.X‹6¼zõ€ ïîåEy³êŸzê©ðwŸþ»Ð­ãrLÜÏÏÎóÜî%ÞÞÙqßÝwÝõ9}r‡Ž|ù7x`¼/÷èꨌ|áö…dá;ç¶™šuµ{ì1gOŸ>ã325òaÊ"óÑ8ξOž<9üýÿ^‚p‚âm 1€û š3™KÛ¾ <´ó¡ðèŽGS£&áÑü‘þ”Â'£î¤æ“ÃicO '7¿Q`™ŽâþeÄ3&Àø«®¼2¬^³F€.3Ú5»ëªN|iˆ¼úR¹zõš«Tžùž;~wÏU¾[ûÊ]æp_ @6mÀ Ÿš´ÍÙ*æ=묳þUšaŠ˜©“Æq*ਹ{VÉ$tàáÒË. çž{ž F•Î <¤âË&Ht÷–»ÂÒŽ¥aEçòðØÎGÁ¥Ÿcûh ÿëWP‘›N ‡Öf6Ì 6îͱž¶nûtÊ©JšÜLp%° c­·Þ¾õ­o†µëÖ‡=3‰ƒñ|Î †ºçSá­;¼…¿ûÝï.TßwJpõ®òùnHfTG>qûRb ± ÚáBpÿ€)AhqÆŠ€ÍÇwÜ;&tÐǤ Äû>;rÎjÁmˆ :;»õzš­á²Ë>ÎÿùaÊ”)z?Ž^¦?F;Ï}Ügp wjU²Vu® Ë$Ûz·év[èÈwh:‘†kôDS}e}hªn´½ˆ™õ3ÃÔÚ©ÊRe]Æ` ª¶2‚I‡ÏÖÕÖÖ…µå?üѵ×^«%íX}Ë—ÄÙ IDATo*3Ï »|O PÑ+Á¯Þ´ióÕõ?S˜[ê~K•ó}ÔÃt®}æ`Ê«áhÇÛB¸ ¸6#Ãgœ ŸÆ¹sç~¤¥¹å/zó<}†Fè?-@X7I6mÚŽ8âÛV~ûÛߎ:ê(cv§>£ ó(ƒ£ç9¸JºS4"ähE`·e¼Uk´FGiiíWÐ6ïEÂ=ÿüóáÖÛn é”î /¼&L˜`e,3ù‰#?Îñ0^‡<ªwµµýláÂ…×h—´=y‹gvÔÃø}bìeñʆ½÷Ù´}ö¶ð™ °ˆPµAÌáXY“ŒÃ)bì¥õ §çµ Q]…Å’1ῊG‘q¼¯HBN;í´0}úôð–³ßfŸ0;Ò/ Á“yØ–\& &j‚%­Ãú¼¶–ݺ :~ûÛ߆eË^ÖÑì‡t6¿ÛF;yQ;êïX°Ð*¦º³³óž¥K—~COî¬V›»Ôž3Þ =˜¿ÏU~ ‹£²4}_Æ¿.¦.öCDøfuLkkët=¿H£ît19³­<ƒ™Î4UãBC†äÁ᫟6mZ˜5sV˜zÈ‹»ðp«¶]ƒ˜V¬Xa»®‘j'mÙ²eöÞƒ€ƒá&0!¬S(ôÊàËuwwýníêµßÙ¼eóËÒH<_‰‘ÇÓ;¨zW÷¯šÊ/Eyr–Õ¸·‹Ï0ãr#Ñíƒz1³IÄnÐ;ZgΚõAY×ç…ˆ›ãŠÃ“Äœ >ZÀ/î? ˜*ÐRË郹†Àì´å,^znâ§z>⺞ޞ­êG‡àdG|ÖÈ{ÕG}аΈlÚ«¦}džf5M˜êĸ1ºôÑŠ|Œ¾Óµ£x¾6Kg‚æ’Qí`{õ(˜‘ ö+$P‹õÐæÒ÷I¨ŒéÊögõ¸}›ñ0ç}ˆ±Wù÷5¡ZI÷´‚ ƒ–LMZ21]4̘1ãíÒ o’Õ}ŒâŒXT)pö•@Ào†7l§õ–Î{–,YòsE;$˜’ƒÔ¥¡âa¶/å|´cÕƒƒã¡`¿0ñ×Ì¥”xÍ0(߰ܦ". .0Ý G„AͰ/dÖhNGX,mêÔ©§È€œ¡¼VÍÃ-òÇË'Ü(¦bpæ†dT÷ÈGmïÔµMp6Êß®k«,ø¥Rë¿WyÑ<; <>;R¾xG;C†5&@Aº{G;G|¼Ž?<(?ˆ>,+-c’°À±“c-+)CŠQ&#%&(&%#(QÄÇ €·×ìù÷íØ¶ƒ Ž˜áõÑ¡‚oo‚¡Ðõà™‹dÔì e¼Æ¼e¦ëÓeQˆ€çÆg³ÆÃÄÆ ÄÃÆ´pÉãƒv†}櫎ÆÄ‡ÆÄÆ”­ã‚š„^ݬ©Å€ÆÇÉÊÊËÊÉÇ€ÆÅ­­Ù`â‚ÄÛŀÆÉËËÎÏÏÎÍÊÊÇÆÆÅ¡ÂÄn‚†ÕŒÆÉÌÎЂÏÌÊǀƔЂ Á¯¼ÅÆÆÉÌÏÒÔ ÓÏÎÊÇÆÅ½±Â‚iÌœ€ÆÈËÏÓÕÙÒ€ÔÑÖÔÐÍʀƣÅt ’½²ÄÆÇËÏÒÕÙÜ‚ ÚÖÔÏËÇÆÄ¶¹—§°ÂÅÆÈÌÐÔØÜß58'&*ÝÚÖÑÎÉÆÅòª²¬€ÆÉÍÑÖÚÝâÛÝÞÝÚàÜØÓÎËÇÆÆ±±µ®€ÆÉÎÒ×Ûß〠×ÛØÔÏËÇÆÆ³²®°€ÆÉÎÒÖÛßã‚ ÝÜØÔÏËÇÆÆ¶« ±€ÆÉÍϬ$.‚-/;ÎʀƶŸ ­ÅÅÆÇËÐÒ›‚ CÑÎÉÆÅŰ‹ q¡ÅÅÆÇËÍÑÔ”„CÓÏÌǀơtN‘ÀÆÈÌÏÒÕŽ‚EÔÐÍÊ€ÆljW€¦Ç€ÆÉÌÏÒÕˆ€ GÓÐÍÊÇÆÆÇ«z‚ SƒÉÆ ÉËÏÑÒIÑÏÌÊÇ€ÆÌ}XƒkŒÐÆ ÈÊÍÍΓÎÌÌÉÇ€ÆÐgD‚ 0jÍÆÇÈÉÊÆÊÈÇÆÍ£`7 o„;]šÓ‹ÆÔžVBO„)6NxÒʇÆËÓxG;C†5'@DÓÒÈÆÈÔÖŽB<)?ˆ>,,.c’°ÆÈ±“c.++CŠQ&#%&())(&%#(RÄÇ €·×ìø÷íØ¶‚ Ž˜áóÑ¡ii‚¡Ðóà™‹dÔê¡J57875K¦éÓeQˆ€çÆR78778 7789ZÈã„w†}æªBƒ877ƒ8H­ã‚š„^Ý­@‚876776678D­Ù`â‚ÃÃN87646€56567€8TÂÄn‚†Óv€876654‚4467€8€ÎŽ‚ ¯@887755333356788B±Â‚iË€8764431/./.013456€8ˆÃt ’¾X987653310‚ 11446789\¹—§°>887653210 01245688>²ª²¬€8764310.,+,+,002447788±±´§€8663310.€,02446€8­±­ª€8664210.‚.12436€8°ª ±€8763( ‚ 57788¶Ÿ ®@887552$†46689A±‹ q¡k:877541!„ 35678:m¡tN©€876543!‚3456€8¬‡V €¦K98875543 € 4456789L«z‚ S€´€8 76655444567€8·zXƒkŒƒ9€876565&5€67889ƒgD‚ 0jc987675677€89d£`8p„;]›†8:‡8:8†žVBO„ )6Ow½N8;98 9;8O¾xG;C†5&@A¹yD8Dz»Ž?<)?ˆ>,,-c’°À±“c-+)CŠQ&#%&(&%#(SÄl8mk*00*,|Âæ÷þþ÷æÁ}, , ñÿÿÿÿÿÿÿÿÿÿò , féÿÿÿÿÿÿÿÿÿÿÿÿÿÿêgŠüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûŽŠþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüŽ füÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿùi ,éÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿê) ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿý¨,ñÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿñ*|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿü†ÂÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÇæÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿê*öÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿù*0þÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ00þÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ0*÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿù*æÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿëÁÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÈ}ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿü†,òÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿñ* ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ§,êÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿê) gûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøk Žüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿú”Žùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿø”hêýÿÿÿÿÿÿÿÿÿÿÿÿþêj )¨ñüþÿÿÿÿÿÿÿüñ§) *†ÇêùþþùëȆ**00*ic11 ‰PNG  IHDR szzô pHYs%%IR$ð¼IDATX •W}PT×?ï½]X`W>Wa,bV‘âÛ˜ŠcJ‰FÿÈt:É8vÆZÛþc¬N:ŒFü#ÑŽ´Nk¦:ãÔiÚNœ©4cFÈŒÚbe¢â7"ËBv—…ýz¯¿sß¾Thzàì½{ï¹çüÎÇýX‰þ’¢KŒÖРE;FkŒÏØ>«d:a–c6“À6°f ‚}`/x 3ÿ æÛP ( ”µk×®+W®\—½pöìÙ©V›5^S5òûýáÎÎÎ×®_;÷ñG×šÍæî‘‘OK3à9‹ÃápVïß_Y^^¾)5=mVg{'ݺu‹ººþC½C}Bq†=ƒæÏŸOK—.%ç"'=òÖùåé={öü&>>¾³¯¯o‚/ŒÆtd,HÞºuë;;vì¨vÌu8꾨£SŸÑ`Þ)/Ë$%N]ªù5Š4«”~ÏAï–¾Keo”Ñã¾ÇGŽ©:~üø©M›6yjkkŸ‹‡÷Ybãö}ûöíFÈô÷÷Û>8ü5,»@W'HÊ€a3 Bš € IΔ(°bœšÔ«tåäZ‘»"iý[ë–f·=zôúî_í666ª“ > €µ¦TUUíÞ¾}{ecã%ùнƒ4úÖ’,˜âYŠ×¢Qňã9AñD#ÃôÕõË4Ç3W®¨¨(NMM;pàÀåcÇŽ…Μ9Kdz6oÞüc„ý@SÓ5ùõÈCúXoT7·šÓ`6¨Aƒ&CŸ¤ëd0†l`îÝñÝ¡Œo2å²×_/ôú|=ÕÕÕÍÝnw¤¦¦F` @IOOÏ9tèàŸ¼^¯í=GÉã)꣆5I%ÉCøNlIÓ G„ˆÆ²ÉÝ禟—ò(ƒO=”¸SMÆ™5ë{6ZøûÅK_@O.I¦œß.„ Œâ;o?&¾=󤡾þþ ñëIœ„ìïXGGǹ'#Oèûêj¬Õ-ˆ|FTV$« ¥•¦‘ó0@¼€RßH¡Å¿[(v„PÁ¢5¡u‰TB½}"ÝÝî l;ŠÃõõõµ£Ã£Þpw„ì’]äŸm°aÞb­Dö7Óè%¤c2¥½™J9‡a< !ÇÌòLèAg:©=á°;õÙ©O1ÌïÆX¬5“ÉÔÝÜÜ|ú~Gm“Ž20 R†q(g¥rX&ÇÓiÁÞ,ÖAöòTZtÐ ãø•`£Þ³®_˜I7ïÜÔðn< ©°0Ûw¡P(üðáù9‹ß‰x"I / ŽP;ätŒP÷ N%²å&‘µÀJó¶d˜¾eöœ£5ÎÞ—'V碗Ü]îÁ“'OþdíÚµƒ2%l'‚—l'žLU<™q=“\ñy±T \Pl(„2ÞÊ”RˆC½f£@„ÿâs-Œ  íûºµõo%%%c ±Ð _€'W¯^­5_Ù{Q²å²,%Ëø"eeeQŠ#÷‡LÃýÃäv»Å–«MW#jDþ?ÛÚҺߤ(]%kÖÏŸ?/ŠÎ0l´Ó`ü˜ZZZÌmmm|GÌÃCâ=,Å©æD›Œ¸ó­Æ*¤ªª÷FÚ 8ÔxŸ÷äææ\yùÁÚÓŸ´§ÅŠØÇŒbRœbÙ ^"3þ8õc^<8ÑNks‚¾-Cž[cÑs†1£5Æglÿ dŠÒ·ƒ IEND®B`‚is32«„ˆw¬º´´¹­w„ ‘·H:;;:J¸“Ÿ;;€:9;;ž€h·;;:8€9;;´m‘c<:8755689;965€69;?§¯;;96322357;;±«;;953€ 67;;®A;83‚ 0:A“dz>:83€09>|bA;=:9409=;‘B€ C—;>;:9:>;š@‚ 5w‚C;:Cƒw1X€ q!SyxS X†„„ ˆw®º´³¹®w„ •·‘¿ÆÆ¿”¸–í€ ©ÆÂÆÄÅÂÂÆ¨”€h·ÆÄÉ΀ÊÄÆ´m“¬ÄÈÏÕÙÚÖÑÊÄ®—¦ÃÅÎÕÜ€×ÏÆÃ§ ¯ÆÇÏÙáççãÛÒÉÆ± «ÆÇÐØá€ ÙÒÉÆ® ÅÅÌÉ‚ ±ÅÅ”fÃÆÉÏÈ€³ÊÆÆdAÆÆÈÎÂ²ÊÆÆ‘B€G¡€ÆÇĀƤD‚ 7wËÇÆÅÇÌw4X€ q#SyxS"X† „„ˆw¬º³´¹¬w„ ‘·F7887I·’퀋Ÿ88€7688ž€h·8875€ 688µl‘a9753€2 469c•§<8530€358<§¯8852/--/1578±«8852/€ 1578®>850‚ -6>”dx;750€,6;{bA8:751-6:8‘B€ C—8;8767<8š@‚ 5w€@87@‚w1X€ q!SyxS X†„s8mk4ÀñýþñÀ5’üÿÿÿÿÿÿü”’ÿÿÿÿÿÿÿÿÿþ•4üÿÿÿÿÿÿÿÿÿÿû6ÀÿÿÿÿÿÿÿÿÿÿÿÿÃñÿÿÿÿÿÿÿÿÿÿÿÿóýÿÿÿÿÿÿÿÿÿÿÿÿþþÿÿÿÿÿÿÿÿÿÿÿÿþñÿÿÿÿÿÿÿÿÿÿÿÿóÀÿÿÿÿÿÿÿÿÿÿÿÿÃ5üÿÿÿÿÿÿÿÿÿÿû8”þÿÿÿÿÿÿÿÿý˜•ûÿÿÿÿÿÿû˜6ÃóþþóÃ8ic14W‰PNG  IHDRôxÔú pHYs%%IR$ð@IDATxì½yÇyß=»XÜXqHïK$E‰©+:C‘¶e'¶ÞDŠ«RvòÚN•²c'ñË)ÛJ%ªÊ?©$®8vê-Ù‘eÇR$J¤dQEê xHâ ŠàAÄM‹Ý÷ûyº¿óëßî„(Ó»3ÏÙO÷ôoºŸîžžžªêBW] t%Е@W] t%Е@W] t%Е@W] t%Е@W] t%Е@W] t%Е@W] t%Е@W] œ%0p:\Dw ] t%P½Þuy¬+ó®º8µKàõn4NíÒêrß•À¾N÷:Ùu~ô÷P—BWÓ*Ó½±™V!tJ] ¼N%ÐÕ·ét×I˜^9uZ] ¼¦|M±»È] t%Е@W] t%pJ–@7"9%¶.Ó'q œè:u¢í½ÞEw¢Gó'ÚÞë]]z] œ4%pª7.'MAv9£JàµÖ›×ÿt-ì×êÜ_küÓµ\»ëêJ µº†¨µX:fWu o9ÞxuÂ}a8KÛ2~"艰ÑvéÇk÷xãµå¡ãu%pZ•€€Óꢺ‹éJà8K ¬8Ž’îg¦“™N¼Ò^› ämvŽE·LãDãÍkv~Ûøm¼~×1Y>›v¦kƒxeY6íL–f'ëJà´-²Rœ¶Ù]XWÓ(㩠ǧ™•ÉlXfØt\æcÓxSçXÒCwºñK½ÇF3/M9:“ɬ3<Çgª|tò®N©pE=¥2Ýe¶+×XÇzß«¾³×/^¿7•ËN–vN¤cœÌV›¬÷ZòÔÏ^¿ò:Vý~v:~W§D LÕœÑe²+IJàXïñ¡ßfc2ާMÎeµñÛx.‚RÖæÐJ¹ã”pª8mrÇo“µñЇT鼞óÓÛì¶é™w¬úŽ×Á®Núp<é3Úe°+c(éÞ×ÓÕsÒMý&}¬zè7m+í4ûAì¯;ž¸Í´Ž•.¯cª¸Ö®žõ›°¿)7=]=ëw°+“ºšÍIÙ.s] L£Žåžž®nSÏ´!Ù2Ž“0^f·äM/ã/ã•<óKežõ^+l³ ¯ä;6žeÀR>ÜqÑõuµÅ+yÍtl£ 6ãµé˜w,ºŽÓÁ®NÊpe:)3×eª+i–ÀtïããÕ+ãõÃˬ¶é˜gh}hœ °M†^“¯ mò6^çXñ6Çׯ+íZnh4yl“¡g¾aÉ/C?’~“.m”ø‰Ö+mwxW'M œèF⤹°.#§u L美…TêMwÁ¶é¶ñšúèL¦‡~›¼gÛ†¥ŽyÀ~üR§ ïç§Ã·Ž!öKÜé•YœéÄsü¶Ù/åM»MYGw%pÒ•Àñ6 'Ý…t:­K`:÷éñè”qŒiìM— ¯ä7it-ïm¯·I—¶ŒÛfiø!:vV¶iÚ:Ó…¶åø¦›ñ-7ºä•8:mò6ë¶Ax„騲^™ÿ2½·.p²ÐŒÓ¦;¶x¯+×¥¨]èJàd,7Ö“Ý£Ö!ÿýôšü’6nX–CÉ7mè4›´ù¶å¸†æšo;†My“ƹ4u­,e%^êL…—¬Ä›ñœ`©×¤‰g¹e¦K›¥¬”ïÛìÛ®ã˜nêšßÔkÒM=ʶŸŽÓ˜JÇ6;Ø•ÀëZÇÛ0¼®™ì;ãJ`:÷åT:¥¼ /y°i ñ&š`¦žiË“öD}ë•¶šx3n?ºä·áðÊôúé´9±&¯¤KÜ6á•ü&^É+qË Ûdð,oÂ6ýRÇxÓ|Bß¼RÊ-§R·E¬éèô‹Ûñ»8á%ÐÖ0œðD:ƒ] LQÓ¹UÇú†d¡ 7hܺ¦›°”—ñŒ7õÍ/ã7,u&ãY$4Ó*y¡ÐçäxOÇ9•:Æ ±cÜÐ<èÉx–•zM¼Í–u Ñ!@JhÜ|ÓMh90•J[%¯ ‡G°Ìtéð‘C[Vêš7™Ü:ØoÚn³g]ǵ|‡RÖäA[nY¾VyÓ^Gw%pÜ%p¬Âq'ÔEìJ —ÀT÷\?yÉoâ%M2Ðæ5ñRÞÔiêB·9ëR¯ì(/íš×L×:–›FÏ<Ç1,Ó-õK¾u¥®yÓMgUÒàM›æ[VB;áRϼ2^3NIƒ—qJ¼ÍróËtûñ¬cˆ^Ϭqv­SÊŒ—ÐöJ^‰O%/u;¼+×TÇÛ0¼¦D»Èg\ LuŸM&·¬ )Ä’W⥠ÜÕ:–›oyó@‡`~I;¾eSÁ2îdºNϰŒgñgt\y˜×Ô)ùSáMgdÚøÆ%ŽÌμ”Y· –ñÚäm¼2ãèMe 9zŽMpðl¯„%>™žeMèøM¾é©äÖë`WÇUnDŽ+r©+IJ`²{k2&K9x“¶Žùm:æ5uìL±nÚúÐeÓð¬ ,qÇ;ø¦Ë¸MY©ƒÌt ›ü&nL7¡uÌ7Ý6é&t|øå2Ú2ôì|ÍÚñšWê´ÉlDz¦>v¶m»ðÐu<ӥܸ!:„’nâIc¼ŽãXÖ±Ó/L&ë§ãw%0i L·A˜ÔH'ìJ —ÀT÷S?yÉoâ%M2ÐæïGO怱U:rl”ú¦K=§Wê[§”·ñ­7™Ì:@Û34ϰä›WÂÉpdýBélÚð&Ú¼šož­¡å¦ÆûÉÚø%¯Äm h>дÐqë8^?>Áò&¤éló¬ ]â–OÆ·N¿x–w°+i• Hºx­%0Õ}ÔOŸÆ h&tÞ¬ch~éŒáY^òÍ3lsÀðJ~'.¼ÒF¯©£(ã:¥¼´…^?Ú|t¶ŽÌÁx"7ϺSÁÒÉoBl”<;Nó‘ù€gÇë8%m=Û–x)/ã7DÏ8ƒÐ†—6­c^Ú&|‚å¶_ò, Å|*㕺ü.–•úÖiòJº_¼R§Ã»è[ÇÚ(ô5Ô ÎØ˜êê'/ùàMš5ßÐ… Ýt‚æ9žåæÛFÉ· Á¸e†Žgh¾¡ù†æ›¸yýøØ ”zà„6^’ôd%í8æ ´ƒ1$.xI—¼Rfܽ҉šßÆCVòÛpxæ–ñÚäæ9/¦—2~?ÜqºÔ…&Xnh^ yI/!ñ' SÉ'‹ÛÉÎðp£r†CwùÇQSÝ;ýä%ÜG™ó ‘/£­å¦Ñ5ä /m•=Û¨é  ]{íµg­]»vø¼óÎ^¶lÙüE‹Í_¸pá¼áááyóçÏŸ;GaöìÙ„Y3gΜ544Ä1¤´â ÙH~0CÒˆ0::Z ŒŽfxTa„#‡ÃGŽ9|èÐ!eû°²ýê!Nû÷ï?¸wïÞ{öì9°k×®ý/¾øâþ-[¶ìÝ´iÓžx`÷¾}ûF”€Té¨I7he}`Ö¬YcJ+2€>‰gŽ BÓa—ôd8é7å%tÉÙÇ:@B“.mYŽŽxæJÜ<`?¾u¦’[¯ƒ] Ô%@cÓ…®Ž¥&»g¦’•rð6Ú<ËväÜ4°©g}C럑ãÁ3߸é©t¬Î\Nv†œXà8Zp¼œ"ÁºÎ«Ó¸ø’K\vé¥ åÜʹ/\²dÉB9÷rîóåÜȹϗŸ‰}|yê;ÌÊG޾Ðjp`P¨œ<^G*#‘ƒ•4ÀI!š0&|` àÐ ˆ.A† Mß8T§}‡#ê$ìW!ŽW^yeïŽ;öª“°ç™gžÙýè£îyüñÇ÷)M.'ù žÊƒò¬ÔÁ>¡RÙBÔë°Ž/xt( ¹Ó*ÓµŽã–ºæ} 'ØV¢&^|Ç:˜×6ß°Œkžád2ët°+(Ôt…Ñ•Àä%0Ù}ÒOVòÁ›´S„oÙWêØ–ã”иõ¡Í+:ò’¶^›®e_›œÒ ùÈÁ½û÷EPêj4>ãŠ+®X¸nݺ+W®^ºtiŒÞÏ>ûìù‹/^xÖYgi€¿`ÞÜyó,˜?oÞœ9sçÍÖiö¬Ysqê¤E¢o»>¾áܳ±,©­JÏî"÷ˆ™£¤D³|LýƒÒòôòÔˇ:crÚ5iÀq@~Áþ}:vïÞ½o×Î{Ô9ØÏ,‚:Ì"ì{úé§÷<üð#{zÕ³¥ƒ=ª~V¥>‰ŒÊvô r§ ©ono;†\N)kÒ¥²’7èÃ|±ÆÉ¡ eœÄI¼GÇ¡ÄÍöãO%+mtøZãÚŠ3´ ºËž¼&»GúÉÌŸ –2ð©;Vrkœ8à†æ›×ÏÉ—rÇÁ†õkˆ³/øƒr0­ii´>kùгäÔç,^´h΂ù f®Y»æì‹/¾x©Föç,_¾|‰d‹åðÏ–î"¦æe7‚[ðµå¸Yšô<ØÖ;œ z1F×@žqy¢ƒ9H,ò$… 8ë4¤Ç˜Bô%°‹ýp椢t‚-¬Ç%"©ÔŠ=$1$zVŒhVˆŒ)[iR"whH‡`-pxLèqÂ.u^QGàåíÛ·¿´yóó;7n|jûÓOoÚ­îÂá];w½úòË/Þ¶}pôvÆ5Ôo†iŽpêúÍÜ!°“7tœ&m>°¶Ó’2ë8ŽXužœ© q¬c|2ˆ¬ Äí&“õ‹ÓñÏp]nð,¼¦Lư…T0Ói­“N¸rE#[Šëˆ˜Î| B+ë½7©Š‹ f…B°8%¶»(µR’“0 "Ýq™cÃVÊñ¥P\Œeù D‚iÞ`ÿK/íØþâö·>ÿüóÛŸÜøäÖo~óÞ-_øÂ·k]ˆÖ<ð8@ £8s;e;vóì¤Û`©KF'‹cy ›¸ó`ˆÜ8Wm>XÁ,xÐ¥®å%´¼äŸLfža%àÚv†]vw¹“”@¿{¢S–K¼Mf9޶Ô/i;aËKÚ¸!:8jÓ%ìÇG‡x—éüðêéùýœI¥çÖR©í†îOüäO®xëo9ÃÅ­8ÿüóÏ]±lÙò9sç.˜53 i¹|ÑÌ™Dì1ËKêd.-t=ŠæZI’*’i7ßa%«» #Á¶‘’Î8 Å BN5ÿ˜Žçl”МNàB’<ƒéÁíå‘l¡™æBA§à°àë Õ =|ðàÁ}êlW§à…§6>õÂÝwcËg>ó·[rœ,ÇQ­© ×6ª8Šš eÝÖÙÇÏÐö ×´!™-ñ~´ÔâÂ,/ix¦›°)KšãÏÖÏõÐävôY¹º‘×Þ]ôÄèw?´ñK¸é6XÊq¾¦%]â}›WÒðLÛÉ7it|àì”Ãâ¯ÆÙÈÙÓ`"³½­¼_tóÍ7¯Øpцek.X³Lû¥šÂ_8¼`xá¼ùó†µ>o˜•vŒ·ÓhžËÒߨ´ËÓ‰ƒSÓÐY'M߃0]ž}ÏA£˜æÑ#Fj¦ÅLR[Î9 ®e} “´þ„FHrñžٌÜÁ“^ô;DG¼àGÔ|Jº)Ùha¿ÖÄ B¡‘h1ÒUe¸‘¬5“eØ©¿‘øœõØ#”¥}Š„ë!˜z¡Œ°ìŽRP:i)Á^Ž}ûöîÙ³{ÏîmÛ¶ïxö¹g·?ñä;¾q÷7^¸ÿþûw+ Ô#{u ô$&Ö°¨0wì¸m;}ÛiÒé"z‹&MöÍk⦥— ÍAhƒæ•òPΧR>~©Óág@ ôjåp±Ý%¶–Àd÷@?™ù%,q‚Æ¡:€Ãóah=ã†èNæØ­‡Ž7¼ t ïó ‡¯Æ¾Œ‹sºþúëm¸è¢³Î_½ú,-Ú[²~ý…+Ï;oå¹Z™¿T+Ñ—Ên’R+¯åÂCéºpR„¸ByAÐzñM1WC;ž¼÷x=” =`„<:Ç=[-§“åÉC¸˜½s™|²ß“õ°ZK¬ïiVˆ"Û ñ¸|²¸,ÑDÏÙI .‡¬¯Å§D*’ÑŠÞ•4 fêí Á |rzã•/ï|éÅ-›_ØúÔŸzA¯&¾ôÜsÏí~jãÆ]ß¹ï¾]YÀ™s_ ú}ÇXHÈÁê-[^ØüðÃoºãŽ;~øÙÏ~v«”¹âQ€ ò–tY/»$Jf‡>ÝNzÄq®¼ =Û‰‹ž40“NÇåddCçÒ˜âD*5»'$•Qp@ E¾C{â§ë4Ž*ˆDÄlý„¦8ȑƕ$qdŒG90PgãÄtÕP+¼¼ã¥­/îØ±] ^xòÉ›ðƒl½ýöÏoÞºuëAYÇ­Næ¨:‘lnt4wŽX&h'ïNA“¶óo:ü&MŽá‚¬—¨žz„©`ÒJgë–<ð~ü¦^GŸÂ%@]éÂé_“ýÎ¥ œŠôAéXǼ~ÐØrÓm°tê–—«õëѻү;ŒôMkb–xµì /œ¯Wó†ë™þöׯY³f½vÙî5gcczL»Èɓ꺄ç&O—/IŒES¯ ˆx¸ŒÔ*êœT2›VÇËÃ¶Ü )*Ôþ+‘:;nV¨S!½4²Eޏp”=¢†´¶¿–Mš•lbzq ,YÕ9¶qµá¤I~‚Õã;‹‘ód$û÷¬/‡Ô¹ T”æ×к=ó¾¢P‰ntR’’~Ó^-2#Uì6’}ôx‚i¿e>ÜRUÜûìÕš§yôѧ¾öµ¯>©5;µ…ñþ§žzj¯lÔ^÷%N9hÝ—ÌXf~¹ °ÄËÎ{$÷–™î¥Z_q©Ó, dýÂd²~q:þ)TéF?…2Üeõ˜J ßïÛÆ/yà>HÐ2n‡¶7nh¾u];ðlÚNÞñštÄaªŸ¹5¸C?û³?»ê¶Ûn»ü oxÃåk×®½¾ìhß#Ó©5Ã=(d'ØÖ™‡^rPâˆHt‰Ò¶ZsBЍH‘šÄ=m‹"ÎDñ8SŽe5„\!_IàõÉrz&ë‘pÄSÞCW”¢*Ÿ)|]rö!Íå²|-å•§q´S‘©º“ É Žr(´"M‹SÆŸ‘¡ïkŒ41Sçaü父ް:½ÊÉfï*l3 ¥è)9™ŒnbNIïŒÈù?ñàƒ>¢Çüå_þå³éö‹ˆú9ýÒÁ[Ç‚f'ÁÎÝò’ç"KhÜ|.¡‰›ŸÃ¡Ä'ã!kÓuœžÂ%Ш-§ð•tYo–@ÛoÛÆ#|ËJÜ2±åÆ­g§mˆ†4m9½ÄKqÎ^ l|GÏögâðµ_L¥gðh&§ŸcÀ˜Ü\’ÚUÙ Vdɉт^G:rüc£a Òt¹ž¤¤³0LÛL ëfV.ÔQíufl „¡‘ÏöÓ@)Ïö=D–çH½\¦´¥Ùq_‹Ô#×¥„Òl¼ôkÔêS"ãÓågO¥Ñ+4bMA`t-ÄQJŽªhC§G=©¤CS™Œ[LëcR ÊSºZ8¸Sß-Ð7 6oþþ÷¿ÿÔ¿øÅ'o¿ýöí2£{u\GÕ1¥Ó0R¬YÖ±ã7Ï´»¿¡ù%”©HLû@N¦‹KžñˆOm<Ë'“Y§ƒ§@ Ô·ÿ)×.‹“—@ÛoÙÆ³d>àY×¼–Îß8Î;wCøà%lâåïFûZÔ7S£+V[c½¡Ë/¿â¬7¿éM˯ºêÊo|Ó —®[·~ý’sÎY.Y4Û±åžòÑðë»3±²¹©ÂÁÐ b”P·`ÔT/Et'wœIÞ³™À©GèÙÍŒñ@â¤!×£(¤ÍUxvVD~ÄCyˆ'–u¢ "&­O7Y¯ã5‘,ÐOµä×xÔÍq~êŒIÃŽØ:É"5B©[ã5â0¸ÞÜN:¥r«#e‰4"í”/˜iÖ%ÇŒû¦÷{ð<)>Ф3MÜmÙP¥7O¶?½éé§îûÎ}ýàá‡_øÖ·¾µýáüàÉcd¯û{T÷7‹cf@|;}CÏØÙ›ÏÏXò Ëƒ‹B„lâæ %´,I&ž­[JÚx¥¼ÃO¨oÞS ¯]'/¶ßr22¶ mÏN½ÄÍ3$NÓÁ#«{!osü<ÇŸ©iRÇ yóæÍZ»víðM7½uÅ{ÞóÞKßtà ×jeÿùÑê(E½Ë {5ѱïK4iS¶JYÁ.l|ÈWEÃ…ÕÏÛÁƒgyŽ…=)6ÓÅo”!Òƒ‘ùa»Tȸ£!wp'¤7+‘$Öqœäé3‘Æ„ÉyŒ²=´r&'Øá:Ý£”÷T‘rd~|,KSéHbJŒ²3 Óxg=ˆñ!Åw~Ky„OçÉZ’c· Ë5é§»¯N¿´“Í8›É$×ÑãD?#:*$™£?RÕþÏÞ÷ÝûîÿÒw>öo|ã…§Ÿ~zŸ>|Xfk'¯û^·ýQÞ°£7ô£Ó†ÜóMœ$ËÎ@‰KTËÀ­ë‹0Ú¡;ü‹¿ø‹W½ãï¸Vûð_:wöœy´³L¿J^7ðvÎÁËMTø­pZÙ%‡Bïñhìi·Ýèg=.˜PÇb<9`iØ»[¢ÔgضS2Cè0#À¤™²ÓÇ`m$!ÖJ1u}²åÓÕ"ÉZðri·—} ÃÒ^Ìž­Z\Ûw’=?Rwr¢“»ÏN¿H;1ˆ„“¹|ÎYIja/éX“ŽHJ7'–2?ÎF¨c…VÜY¿Î¯Uz‘êû!¥ ôòï3>Ï9"×!yPu¡&™Js”­õøê€>RôÈ×¾öµþÛûoßüñÇù&ÁQ½ºÊŒ{ pøuÇy m^³`ºtüà\‚yÆÓe%>¸i¡›Wò‘ÚxI2¹Ì:< K ¥ œ„¹ì²ÔVm¿ÝT<äÖ1Þ„8YxÀäpÇ;zxvô%§nžé²3rFüz6:‹ƒðÈñG§à£ý躟ø‰Ÿ¸êòË/¿hé’¥+´Iϰ>áFœäê’³SÛ™.#Z¥àI–›°ˆÑk³bÚŽ7dAè”ìDÄÜØ—6™ [;üãëNDi³— ÜfHR;´ì~óupq2+pÃHU…¬4õáB¦¡3.¨þ’ß©Y3Ý×B«Vñ½–X°NùJðèÑ1ÍÈè“ú‹ |‡[ÌпUÌ–S`I¥—ŠŽë7d²Ÿåa+›L?Í8IˆãÔ‹(2E(YÉl6rã=`ÄAB*9Ù Çk9÷ÒËfl-Ý›ÜÁI=¡£#GG8¸ç¥—^Úöè£>¡ƒßû³?û³§e—ÏêS•: |Ùð0½ø:<Òovpê–ƒ£gG_â–I\˹,øÀ¶CìšNpQ$*§Ë+ãtøIX¾wO¬uYš¤Ú~·~<ó›G ¯<Ìú@nÜŽ¿„vô%§Ýtü3Øw§¯†n0;ý!}aoîG?ú‘ ×]ýº+.¿â¢U«W­™;gî|g–Q­ÿl㦖U‚Ôú€f—#F4Æò"i•´ÒY¹‰ˆ¢l'kýçþçOnݶ퀄#¬PYŽz§Ax:Ú:vî¥Ü¼ÚÙ}%óÁ}À#@Jh4z”O˜9ô|`Õ/üÂ/¼å†7Þðf9”arÃ`)¾S9KM?mS4êjj=ðÔ¥3­Z¡¡ä²{®‡÷Ö’Y&Ãá ÏÚ‹`n¶šã¤gЊSš$Yè‚Ý–¸ Ù擽Ñ1¾åìb9¹Œi+âÚ¦V’³€¬zIð¥;*M³º¼Ò»fÕ®;«:vïÞÇV•Ž;ý õ"B>YR…_ŠÆÑ–µAóèHЉ`:{ñâÅÕ¢E‹«Å‹U眳¸Z²ti:Î9§Z±b¹xç(­”gò¡| ¾&ç©W¼Ê_Îbkä6KÓô&ÃnˆÆ$Y²Á¯@”ºg²Á¸T­éŒ˜uã{–i&`œv¸cSæ…«¤ñ¹qý¡©Å{¿ûÝï~óüÿq÷ç>÷¹-š©9Â#)Eg öN@“gç$Óý:dÊ2¡‘Ih2¦Kî.ÎT‚m¼ñõc/Æ­úcÏO—öhûúñÌ6qóìä¡Á Í·ƒoÒæÛÁ—ÎÞx8üY3gÍœ7ÞlJ<Ýÿ«¿ú«—ýãôß¶vݺ‹Ô˜«ÎÀ¬òry¶ÌDÊ5ŽÑRäö}Fbçú¨„ZÒEí†7E–Ž·çI³NÐņ~Ï^`aB§¬Bòyä£YLá3¯i}Ö/êKƒš–?Ré•qþ¶Æ³ß¾}{¥Me2¥Sæš&gÊÞ¸öš‹Ñ±iÒÀyÚ‰÷ƒÖ:X×t ë2ÍÌ’6ÞÍó53Ðõã xt,Ó—«åËWTú³àru VTšŠã¼U«bæ€5t*F4#À£Š¸î˜R‡A…·IëZ~~ƒüÉAK;8ð“ÓÏ:ã~ÿ"^D®gÏN.’m[ Þ8[ã”#ýÈSO§ÌTduDÏk4³spÓ¦MO|ò“Ÿüê'>ñ‰Gd…5G,X0¦ztˆ×áåÃν| Pâ–ÛÁ7iøT Ã÷!´/ŽÌýfhã5u:úÇXéNþ1f KzÒè÷û4ù¦KÞvØá#ÃiÚÙÚÉC£gßÄë¾ø™óçÏg”?K£V*ØøÃ?ü£7¾ãío»fÕêÕç/]ºt_ÝÍ-±üF¸Ž´>L1Ƶ8ÖœA,SÛ©†?›|­®`\D³r|žÉJµ¤—øeÜ2­Z/"/%B'…ãèÁ“õuâÒzE;W¦é·mÛ*Ǿ=û¶mrò;^Ô(~W¥…š¶OSöLÛûÐH0lã(é@LvDÞÉV*“ÛxV˜Lf_ƒé¶ÉJN{²GŽ>t¯Œ;äèô(a¡fEç`…:Ë4c°|Yê$°þ€@¹k¬G#ZÛ ƒ!ÖD‡ ?NÉ¿›>_CI×÷6óà×kY¾¯¯w8‚“YõO4ÝÝfR‹[(g$õt;åwXÓ­£ŽÞQÍúlݲyó³_ûú×ü­ßú­û%œ¾fNÔ)<¬{‡Å‚ÍÎ@éüKÇc‡,d\€!x['¾¡©v¶ÐߦÛñ^ÇÈ·ìë˜b—ÔtK ùÛ4iÛ‰ö%m¸yvìÐà†vðMh‡o¾;µÃ—àáøõ¬x¶F(õhÿÒK/=ë–[nY{ão¹èºë®»zå¹+VG+ “Þzш˜–:/û“ÜÔ.?X…£œP­[S½/£>l¡¨&½4j+Çn½ø=' HÄ×­!&9N9{-ÊÒ³í¡¡™Ê>Â*¦äwîÒÔü+»ãxe÷+rþ8þmÕMã3º÷'ƒ÷3wã†Ø³“ž ¢{²w¦‚žÝ(¡q:Ì 0CÀ¡Nc=SpöÙgU Ï:»:[è(œ}öÙ©ôˆýÈÈQuÒBG~9òPÿñ[ùË…Æ/¬Þ-÷7ZÍR¼ü3Œ8Šä~ÔººÙæS*‘iä›ସ£ ˜cGãØè¨:‘Ô©ÐÙºuÛs}ï¡ï½÷Þ'?ÿ¹Ï?ýÈ£ð a¼9 ÎŸ%¦#øÅCw'€[ظ;Mh§_BãéRÓ%ËT¾t°ñxâôÎQ,=2SAwèIP¾·O‚¬tY(J íwéÇ3Xâvò˜Å™#ƒg><Ó@ì›Ð~‰³GÐj„,›©QÊ}…oÑ?ûgÿìMZà÷v=æ/ӸѠÈå;ýÜp¦v‚ÆÚýqöÓŒðì&L2w ÆËj %â*¤NB"k»!!qȩ ¾ 70Àh\«ä5ϰž…u,¨c¥½¾MP=ýôÓ•¦n«çŸ>à³Ï>:å´·qú=\§¯µ‰§”OÿsÙAhâÌ 0Sàq$^pÁÕšµk«óW¯®Ö êqRt˜˜o)ðvÂlýnÚ‘Ç&ùUEf â.â>Ã)sP#²cvÅ ^kñs/(NáÈQ«ãÕq²^‹¤§"¹ë |œÝÊ´ngÝ+<í`Æè¥¯|å+_ý¯ÿõ¿~ëá‡Þ¥‚xk@&FTqæ*¢£¬°ã·Ãw§ t²¼Ä¹Ó}À§LƒÃ#˜$Ûðæ“åSñJy‡¿Î%0îÎ{Óî’k/¶ß¤¾eÆ›çl^Óá÷sü8vwŒ—ΜóÌd'¿!žóßpà çüÖoþæÛß|ãoÖ"°%j„e?µi´ÓkÔ'Gœ¹Q¶¶¬FÈúŽÆU`¯Á¶©UŒdóˆê€~ê ÐG q’•²ÀüÉ+œiû'¼zü‰'ªþð‡ÕÆ'Ÿ¬ž“Ó§oÃ3 WéÐ뱂‘.S P–Í@9r¸#\­ÎÀE]T­_¿¾Ò&RÕ%—\R-Ðã:XÀLmK?CØÄ¿ýø_&QéÝ“^§tÂïçH²=^FЦÜ'9™Èü¸ù„[ ús‡€X=›IO}‚QÍ0íÐ[ßüøÇÿø«ßúÖ·_b}€Âˆty[À‹qÚ¥ãwgÀ|`óÀéóór!ð7¡DãdÐe@¿ÚxMŽ~JÀ·àë”\—Ì%Ðö{”¼6'N@ÆaXÈpêæÚÉ÷súvþ5›¥ç·³ósþ™ïxûÛ—ýÂ/þâ o|ã¯Ö”íy³çÌž—š4µ<äŒöŒä{u?°™e4‚)x„Ö®‡NÖ P[¨m•éÌô¬š¢FŠ^Sãw^]#lÙòBµqã“ÉÙoÜXé»ðzÅî@¼bäÙ=³8!¦ðÆÃ@qªPÁëÐÉK ç{zrtõãŒþYOÀ£ö$˜'|f 6lØàyçFX›‘öG¿Ô-¿™ú©±%ªÔì u+á³¹sSHX–ËÎDc_ljنò¦ CÖÖ=ñéŠô¬:µÈˆjÊ¡köé€+mþîwïèOþä¿û®»îÚ&ýÖèž<Äcè–G^vJºÙ°Ó/;©’¤N•ÌÍ|ÓÕ²&M(u§;ÿXJ í®û±dä N´í7hãQDð}4ió8y Ýž¾q;| ;ãœ}æ§Ïh_S’Äâ9¿¶ì½úæ›n¾†•ýóçÏŽ–AÞŽ V+=ã'®óRˆFOÿ!FEHŒÊhôLKšÁCt/0aü(*³’H*ÊR~õŽÖ±pôz[!6¹A‰ùÏ=÷|õÂÖ-Z©¿¥z^x,äÓâ½­/l­ôÕ·G¾âÀñý"ÈSÇò5bÁx’dyùÇŸÚñÅ$?Ü ÿ MXÊBñøOá ól Λ@G€Å€<šá`}À¹+WVË´~€· Î?µ:«ª•â1cÀ[6HŠ8êÈq!<îI+R±/Äþ7yk]sº?CêßD^:Å§ÊÆ7»¬¡ê7O¢ƒ f¶Q{yîSØ‘nÄÒJi±`t$Ó^{7mzæ‰{-†Òƒ¬8¢E”êsŽh¶Š jë¸`ˆ³÷ÁÅmHVÊtÛ!ö8>t3¯ÚxMŽþ•@¾õ~DÖ;³S•@[ùOÆCæÛ´R¦ Írࡕξþ”Î_#âYšNRúçŸþðOèCëß÷þ÷_ý…… yÎ?"i Ô†ª1ÌU›Äi4MCp–èô(sa¦Æ°àôПx½ø c9®pÒbÛ\¦òqúz¾Ïóû/ï¬öíݧ÷î_ª{ì±êIMëoÔhŸçù<ëÇÑÇóe9}âb§œê/¯¡—±i`Q(YÏ8ÃÍí4Ì„ Z ?À"yðYr%@Ðäc ºp2î_Ýw†TÆ5÷á2Tn@Ö™çcŒqåa¤à®…Þt×ÅÝÃï¿M…Ö<ðc Ük>è€3ýcO;"‰] qüÌp¨óZ-Y²D› Ǿgiq¡&«bÃ¥£êÄ’Ù¡˜d0Žä´s渞ø±2='kp½¡Lu\ÁÊB@Áç:',SÇSw0 P÷¼¬M„î¿ãÎ;úëOz£ÖŸì;ÖHåˆÊ |[ ~]ä:¸Ë ÛéCû.ºc@îÀåPòL‡ 8¡Ó m¼¦NGÿJ`Âö#H£3Ù^ýʾ¯<°Mûd¾q`yÐì¢ã¦ÚG¸…,³Ïö ~Í[áú³~÷w~÷¦÷¿ÿýïÖhc‘t´¾jT{䨹RH5¹QŸ!•ƒä¯O-†¤‘Aœ©Û¼°àS1òrY¦Í‡±pù\mVP–Â!qÜŒI‡ózèAUj@Ãé3’,ë‘/úÇí蓉tÆË̛ׄ\¿‚ùxg®ëÉüj®dó¤œ#ݹ’Ñ pg`¶d¹#qêÎÆ'†±eÌßÎ'Hן~Àƒ¢_ÿ Êæ€âFÇ â6¢Ó üé^³T#LGߺ} {‹ùýù9è(lØpauõÕWW×\ó×ÄãâÐéc·BÖ ²O§¹{|_þ˜”¤îÚœ„"կл^D!†—.*‘qvÖ¬Ya¡–™Mì„ÇLä=mªžv„ÜyçwÜù±}ìëêÀR/>¸yvø¥ÜNßi”€~w ¤q"o‡Wdm¡¿M·ã ð½u‚Ìuf¦YmåÞÆÃüæßl:èÒá7ñf' œ½Fjõ†»‚—A=cœû±ßÿý·hÔ³žó¯{vª±:7ª®šË<ÂI—ÝSIú4´D ~&ÐkNyVPÆL )^Ž@¤GSÐÑÆê[ÊJ¦ë¨æèÙþLàYÿЃU÷?p8}vÔó´1 ‘ûÖÏÓ.r#•6T©~ðý‡+=#Å|Ûôn>ÛëràèXð‡ £@p> ƒ9Ý¿Á<æ|ß'•êÀRç¨Q_¢CPÂáÏVd~%lø×4.x<ÎÙɾ0ÊkÆ×Ï3öŠ:/ Ò!¾Ô±#Çif’kç ¸, wÚg—!3î’o p°o\vùeÕ•W^U­ÒBîÄ÷Ðe§Ãº£Hþ”§È¦Nál÷. ñao:ËkÊž¾ß1ŸDêôxæ¯Í„X½Âž½{v>õÔý›¿ù?÷~âÿéâŽhá.:ªu,ÌÝØñ3#`‡ož¡>°Äù59à‘kp‚i_å%4ÓÙü©x¥¼ÃO` øž9&;S“”@[y7yÐæ•8îÀ4дÝ >îÃ<;|w íìá•´ɳxµo–ÿ‡~úCçÿÃðßpÝõ×½aÕÊUk¤«úµ‰]Vf°X*j¸F.å(Üxã¢G¨k}€’y‚ÜAjV‚ÃØGœ˜ °VONŸ'¢4ÆLÝóÂsÏ?'Ç¿%ÞÏç™þzmïÉ'ž¬Ô0†³g¥8ŽŸPŽƒ1yp>(i7‰Í¸ÒX¢c±œ¼ºMáð…WÅÇ”½¦èTD¦íã©nÓÈDºv²E=Žó×ãÔ˜`ÍÈHØm2M׿• ȵ¶ QjÚõ&­!Ðc…x”À#ܳ;…Ó!Ðgq‰Äjßá”=6½¶ŸÈ/ŽœÀš¶b¦SÀÚ€K.¾D¯^T]¨W ×­]W­Ö6Å«W¯âBãLìFÈ;.ÔÛ—eŒTOÊŸ$:¶œ¹ÂÈŠ®A×n{ù’B“ž…d7ÐL¢ê¹ºañ…-[7=ðÀý|úÓŸ¾ÿ¯þꯞ•Nt¤ÇÚÏ4 ˜¶“w‡Àw94Ùr§>´!¸i¡C,KT:[6¯”wø *Þís‚ vfú–@¿².ùÆ>0H eÚ¸›@ ?Ð|w€vø%/:ÝÌÒhÈò™Ú’uî‡?üáõÿôŸþÓw]uÕUoTÜÞ?M„æVM\²¤KÑxe*ª¹ð®ÞR­ -W “zJVqÖ«å¤&‚—QN|ïÞ}1:{é¥Õ½ßüf¥/«U<òHìÒÇ+b8|zò`§ïÔ§„\&!].—<1ð¬žéú…* ¦è™¶ÇékD?n_ ôú…ج†f”Pªµà\ÿÉêß¶,›6\w%Séý˜Ž{|ÀŒ#°¼mO–㾚³N×°©×‡vgÈ=Â÷ëËŠ<¸üŠ+ªë¯»¾ºñÍo®–.[ª¯ÎÖ^ âžb&!>w¬{k`Œ ŒKl¿NêJíûûäeìÞÃjXºÖòý,$¨nP§+môÿù§ú¥ÿܸoïÞz@böÀáÛéÛÙqð%Í Ï¥[vÜ0Dî»܇ÐújâÐèwáG\ÅÝô#NéÌ5ßVÆ“ñõ;pêÈ—߸¡=´q`^–fõ«Á ·½ím+ôÑž·½å-o¹qáðÂÅÔBgVMoÔÜr<Í”¾^mM]‚Ä,$ñ‰ðžD‰dªæÕ²‚E#+R@#f¶Û}ð+½]ýàáÄ(®vHŽ|¬0鉒gÑ]^l7€Ó_©ã\&—)_Ë…ëy}[ˆÑ/Í"b«ðdsìm×ðZxñ»ø·-!¸Êµß,H¬+Ø®ÎÛ‹úÉ·ê~{A¿‘€[³»ifòuZMÙ¤túaüØŸ:Í×^Úé²zûÛßk–ê‘?äQ}Ñ‘™p'ÅmB€¦ž¸–Lø³³x:Eª:m ;mjƒSŒtêÈdÙ» îüö·¿}ÏþÏÿYÕä®í²6¢z?¢z_Î4;8xwìø )í‡& @ãÐm‡Øã/ †ºÍÐÆkêtôq–@ºKŽ3rmÊh+ßÉxÈÜnú@æ‡ ‡¯túvøÀrÊ¿tüCì߯ÍSæð‰Yô>þñ¿ù'ò§Þ¥w¤×hë[âF`4¡”ÜΘd:“‰v‡››¼fÎt0ÎN$ð¦ïÊÏ©æë£1¯ìÞ¥…|߯¾óíoW÷ß<Ógcvëc¤Ïà‡ƒÆ¶=o‘ýéŸâ"¥®éûÁ5rô«U «uðü¾Ê4'4øMåŽn-©Êu£›ué}^Ç3ºkô!Ý­&‰Éï–~;î?½AÀו-bV‰õ×iÑàõo|cuåWÆL3¼zˆ³Ö™9+ØII§{<IJ•ïvÉÖ¼¯ö‰¡Ô’ÿží$LqHGåç·"rö#[·n}泟ýÌ—ã7þÕ½R9¢Å½lnõj±gpî8hã%ÇÑ£Cr¦Û:îH­î€¯¦ËkÆëèã(æw&º(}J ­l›<Ó@˜Ç©›g§oιtúÐt Ë¿ñèàøµð-V÷Kæ-üટÿ'ÿäÍ×_ýµ¬ðg›ª´ñ|s0j$'õÔ¥œr‹'ž«m†¼3Qt4b<ë?!˜KPÌÑR"R¥ñg´ÌûØ3g¦ïÌ£õøãOè}ýÇ+}EÏö¯ž{ö¹ø¬. 5ÓÑY¨@IDAT2 úLá–$˜m'J˜@i’ šª2èEÇÁ󳣿@ëR$Á¦ýû<·o¦ÝuÊBŸnùÅŒ cXcrUc;tß<›:£ÏéÝÕHË5È¿sïÆk(ê·Í^H§’O®oYÅ.Ü¿ç®8W› ]P]|QÚ[€­ˆ7hͳ{ò[&ìˆÍ·$âþW*ñ€>ª5ƒ°— ߎ©ÇrÊV¸õˆ“tky‡:É”t"^Ь|ÆÕj†"2±ã¥[xàïþÙŸýÙ7ÿïgÿïó2iíÃau˜SiÎxØì@ÛùÉ éù€6H0¯¤“$ÍŸŠWÊ;ü8J Ý!DZ‹2i ´•k“m^w3‡„Çmó¯Gýǧz½Èoݺu ?úÑ^þÁ[?ø5\Wé;=ƒòûÚuLÏfè³w8kRvu Z xà 4„ É è¬B;ë%mQ’ETsÄÁñë ±e½‰¨Oèé=¿ùùJ{¡Çóýgžy&6{aûWôˆÃ³Ø¦ãÈId“$Ðd•a¡DšÊi|¦óY¥Ï+yâ,ðõõ"Dzv& Úyô4;ìµ”@”s¾ÍêÛH¿a[9íÓ=¤7áãMÞ2à±ôè \ öø m?¶nUD¦u3kõ«zúôóá#Z8¨=%è¤ò¡¢kµN@ßĨÎ[yž¾b¸$v$d½Òx•0ŒåD\‰LÖ›IŠ{*çAÏœMs#JèIÂÈìxËb©~Õþô´xvT c¿wûí_¸ûýùŸ?òÔŸâÅÍÞ``±6 ­3@­qgÜ¥^:hrÅQ–²yb×rp‡¸¶ñ*y,%·Í±Dèt§,¶2mò Í3Þ„¸(øvúÐÆ¥Ó7Ã÷ˆhÚãÖ åü‡ï÷~ïøÀûß«‘óB­:ª„ØFMQrÞpÆÉ{ëƒØ ¹.¢n: œ9^=‹ëH9JV‘Ü ªgAÓÆ ç?¦gªìۮ׫µYϾð…êÁˆ½øYñÏH§àc\:mD\–NÖ:8¶3bÑžçÆhŸçùLíkc2Ä"=š3~bmN(äÝéGZu§À¿)îEµ ¹ÈpŒ‹xL uÌ ŒñfAìM Hœ2äŸ<Ò ËÚWÕpâ‚+-MóóìÐ#š`ÀÕW_S½ç=ïÖk„WjBÞ:™­û:-.œx’Œ•Ix•RM:¾`¤)^Ò£ÁÉüÀ³.™MAÅoL¯ðΘ¡Çf{¾ü¥/}á÷þÝïÝ¥\ñ‡ÏœŠ¿¡gLSÜ 0¤4Ë€?|ŽH¸ŠUËÀræMlãSèˆé—@}WL?J§9I ´•g“mžqC» üÒá7ñÒÑ7ñÚñË¡Îæ?uìÙÎwæÏÿüϯÿå_ú¥[Ö_xá%ⳜMAõ*ª–³–“, $Oõ¯–¦)$/Ρ¥†¦¶#µªa&Ü}2±®k×UäŒ8éOL(t0¢Äǯª#­ž1{¥7OVê›,|ç;ßQéñZtjYÏ‚m^_M!Õ“èlcC$t@ÜñHÜtFÆE’K…„&QœS\êZh„‚ -µ ¯nÚôô£ÿå¿ü—ÏýéŸþÏ'U¿FÔ.Õ#CêÔÐÀÁÛñ»#Є8{;ÿ&NÎÜ ÚÚ€Gš4²6^§Ã§Yãï„iFêÔZK ­,›¯¾:¤c†>™ºðßþÛû­ô‹ÿ,Óˆ„xöu׺…xן†“´~an"šóçMÛæ®Sã{¥f6¨YÔ®{”`sÔ8ÑXÇ9J fsäÂØpt£:?PgàiÝ$óM#Î^u 8œÄˆ¿$HxHÅUíáâf˜Áb¶Šuçž»¼ºüòË«›o~[uée—Å:¬ÒñE§ì ¸œ„¹y9ÂzÃWX%™i1ôøÏ øI¨´õ¨/Õ{fõ]Œí÷Ü{ÏÝðð5íñŠê\<È›¹#àÎ=´¡ñfÀΟL #Àó…™–§Z”µ¤m¾ãB—róMÝ~¼2N‡O£¸­»ðÚJ`:7':Ö3´»ª›ñøMLƒûpgÀˆÓoÒ³T™gËÉ1å/ùì|ä#~í×~íV­ò‹$¾ìwD‹³ÐVä[òòAž¿þ›¿®>õ©OU=þx¼>5<¼P##­Cd·¿©Vô“(%idHJçŠmǽø«t,×ã}@g`H¼lÇ8Dº„ÁQùHÜî|ª—€OøW¹Wƒ*ð]î‰A¶fæÕNuØÓ!¾½ ùØ^á8þPÖý£{-p± [ñG}à`Ã̱xÀý̃ìJù}}|JóXàWU糩 æ…¬É> öBð8Ù£7”ÈJ ‰ }Bä>¡A/©)ëôx4 ×4õ?W_ûÔæ‡-Ò †¾4øÐ+z­QE6¦3´H¸6âDRB)Áw:æE’:a€ÚêPÊOÏ6:8h+üiDëTr ´•_“íƒhàTó€8ù&´ãGGÞvàüá—€תä…ÿæßü›o¹å–÷jÔ¯ÝçcMSFZQeˆê]Ÿ„€ç–%Õ}¡PGëµ ‰Òˆ—U¢5®TqæLy²oÿw¾s_õùÏN{õ?¦Ki\È0EáU¾l£ „rC çúLëóžþà%rø—ëj‹Å|ñÅ;¢ä_À wÃJGž!%{Ìu X<8úˆîÅ'uO>¯ûJëäø<†êZ¡ ɽ‚À™I …Еñ‰ðÎw½ë=šBö¾½´VRQÀÑ&,E$ºFJ)Zõó £—XR#¤·ô<49r^ÙcÄ6½Ÿü‹¿¨nÿüíÕÆ§žª”çpúŒ€È[JdµY¤o DÕûúCšæzŸ¾ ðV­ÎÖjþ(ÁtQ)¦}cÞãl·$×±NW‡»ãž` ì'÷ŒjÚàyêD^©Îä:Ý_|zù°¶$fË_îÏú&Qïv…öAYe\i¨úJŸv±|©z\³r°ÕÞlY¾bE<ö:¬õìv²© yäÌÏ„A2¯ +Y Ø”ÁˆÛ®;¡©jÍ¿ôÒK/ך¡e/¿¼kûã?¾k‹-šÉ£lÝÐG–ƒy%¿‰;¾sJTëÚ^ Ûdm¼2N‡÷)L޽š7t?^)K­FÒ§ü íðíüíèqè%^õdu÷ÝwW_¸ý Õ×¾öµêùçŸ×ôç¬騉ï­ÓHaœ›#º¤’JʃžÓ²˜oèíyQ¯ó± K »Â„:Ð8v¡+c)ä\uqib:fX4¨·HªX# x6ˆbçßý’ÅÃ5Ýkî èž® PÛuwÇ_ºÉez,‡ñ† ¸¾ÞW=ûì3Ñ1>°ÿ@5c¦Éž³¸bæL_íQ':ÏPKÂFœd7ßëù>O\ι>EÊùÄ,‚£‰åÐ`éòeLÿš±Q'~†ÞZ±vÝÚåÚàè¾)ð¢Þ Quê¼7Ú¬¡ÍÂ/që5áT:My“nÚëèF t€FLA¶Ý`Mty`’êOYÃ/¿ù†8|ôš£~öÝ1篕Ã3U Ñ}á…ëÏúøøïþ™Ÿù™ŸXrÎ’øS> ¦º«wŒ¤¡Í„*wéüÁiœ’#F!…gDnr %PÙÐÃÐàP;¤éÓª¬îÿô§>]ýõ_ÿuµùùÍñ: ¬|އ•¡ÈyFàùKJ…@£Å!zà<ò‡nÔˆÿ­ñ_ ñ–vè‹{$Àžò‘Ú°°œìt箎¡|ÿp/E½Õ½Dg@5t`¾-2¥À.‘Õ,Ý{8`í)0¨ÇñÆ w5÷°@¬ @xš%@»<&ã¹ÿ\fÂÔWö¹çô‹ïU»µPðì³ÎŽ™ôØ“u$0Œ™®@­ŽDêsâ’n/P7¨ñ)n-R~Æ›ã›ßƒ3”6W=°pxÁ"Í*^tÙe— ê£[Û5ãx$ò¯¼©a`™›-a/sÎd/S%V×|1¿”—xSÞ¤KÝo”@×hÈ$dóÆ‚îÇ+eàMçÝ<<Ú·“DZû0 :ÚþvަÿÀ>°êŸøOÿðæ›oz‡>SÊ28©…Cü¥j©ºWä*>N54S½ú,‚%­­Ô‹Hù„qy®É?¦óŸÒ³ý¿üË¿¬ôòê™gŸ ù É¢-Q¤Ò®šžIPÁ‰Ç.Ÿï÷ vXW¯½¢3@TGÔ'îMÜ¢îÍŠŽÀ:´“d8þCº{U·ÕaÀç3}OˆiüÀ¤‡³å€yÔ"Íž©Ñgo‡èUö7þÜÏ}ø«Î[µ^rÀµ[c”ÂAöêŸ$ªFÔ¤}+W©fÒËOX¯Þf%Œ’{äVT Á`´ÂB&mRí~ewuï½÷VŸûÜ窻¿ñJûŠG#5O_ècz350 #ÉÖRɘÐEi°3ãrúï×Hê ¼£MCééØˆ+]Z¸.t%ðã*ò–vmW§µZ)‡z±¦ÎÖz˜Wtß”{•okÒ pŸs?s÷ Ëû™3>tÅç…·¿¸½Ú¾}›¾|¹-¯£ ´ÎY|Nì€óÅtª :Gž¨×)¤Á~ʨíG§ ËÍ«#dŠPÆ#vRÕ‚uUïÏY¿~ý ½n|ô›ßüæv½6xTß’šöàM¿ˆîlÂw(yÆÆÑ3NŒÃo†6Y¯»Àä?Û ÔäA—¡)[ ÍBÓé#óÓ·Ã/¡Gþðø|ïlõø‡tÌ\²té‚?ø÷ÿþmú©ŸúûËÕ—#ÖcD=ïLÏûSUWâ1W©ØE豬•¡€9=„ˆ‰[ËD³‹Ó”|í°F!lÙ{÷×ï®>­çüßüö·45:³<ÏäU&œ¿C=ÎPÉ0 ˆAbž¯VŠ­Íåôß ‚ãó»³Õ›öt£þTTÝù$(n[ò÷ñ&;٢뮱ºã—êg±àíF©‰»èàrÿç«À·Æ_öä¹6DÝavm®:Ðç°«¶jGA–öÌŸ7?¾9Àwòºa‡ê„™^Ñ«‚$t¨”zÂÓLäMa6ˆ Sjnx10<<¼xÝúu«µ@ð¨öõتNÀ9ÿ-òtªe*d šÃró€q™(u›¶¬l“µñÊ8g4Þuúÿüm7N“]XƒžŽó÷hßCéü×´ »û©®Ïžó'ò'üûøÀû¨|j^ôU/:ú”o¤®sY­ÄKþw"³Á±â¸è1rwå—¡ˆ­C_‹ÅJŸüä'«»þî®jÏÞ½Õüñ+MÞ =$@§“ püðš@Ó=Rc9°VèÍ*<½ÎŸãå¿öIÇŽŸ¨]èJà¤+njîe‚;øfuÆ6ˆ1O7»:ƒ±uŽÜ={ D]_q£àº&F ÌxÉõ};5®ö©Þ=ñØÕsZ,H}Ó&_ÚrX_Ç$ݲñèV¤`ˆž;!Y xãÄHV»Ê yÕ’ußõ¼qî¼¹Ãjò•W\9ó³Ÿýìý³HíÕ8k"0cSMYIƒ7õLGö›Êm½‚5ÁV);£ñ®ÐþóOç&B§<°Dõo:xæ#ã°ó÷ˆßÎß4ÇïøkƒŸùr¶3.¿ü²sþüý¯Ÿ{Ó oz«6éX yTdê#x jlbÎ/U\ó¨ÇþÚXîD£âX5Lµ;U{pL럕Ê#Zäq–¦üŸßü|¬ìÿÌß~&pvösÝ„œ¼ (LÙŒ‘Ž,…Š^™Ø «ïÕñ”6ï‰dã$¯^©áÃvº8%J€[Õµž›<×…±åê_~´=G÷¼>:4°GOë4ó¥³j•Ýã¾Íã~—ßõ®[¡ ºqTðú®Àc=^iÔ­ ›¾6¨Mµp¼áøÃ[»Äl‰ÌÏX=1‚¢bÃ"µ+uì|MIo`€ÊU«Î[ýîw¿{ùƒ>¸I ëF¾@ÚÞΤlÝÖ “¤ý\ê·k$n›­6Þd6ÎYטø3·Ý(Mty`ÅU¾qC;~;C;ürÄΟ¿VÔGçÿ¡}h¾Òõ“W_uõõš^›Cý=ªoùàë€óOíMLÿçÖ$ûÒT™ƒp'A³>£ò^]L(ä|±…I|Üä€FßÐ3þÏi _vôcãd|úTÍWªìt@¢tÂBãë‚ñ…AJ„†• j¦Þ'úm:V‰Îwã¸FP¢.t%pÊ–€k§¡îÿ±%ràëõ ìŠÑjðÜÿ+8ñ©QqtÇ›:¾áüC„’®yÛ†‘öîÝ»«—Õئ…‚ì¸té’jÁðpÈX€.n;ª½¢§l'½N†”ƒ„™¦NõãÐSFP—’¬kd Wõ-\yà 7,Ù¿oßK÷ßÿnås€#¢—Ïsêðu8”¸yý Ëdúm²6^?ûg?7¹gĵNç"Ûn&º<°kG߸!eìÃ#ø¥óÏ£þš8›­ >“iÿ_ø…_¼ì_ü‹_¹õŠ+®¸N5z†xªsJ(†Æ²  ªD„^ÕÅÑš*°(r™òž†¹â¨Å ÏÞçÏOoòjß]_¹«ºýöÛ«G}$F!Ú|(+ž?¢€l&påêIV‰qŒ­V{ñ67й^ü9:ȧX©¥‚.2™íu +S²¸•©>h¨áKµMöJfôÖŽffìÑ~]TNäª3TÕñ^uUypøš&_œ‹7kFnËæ-ú‚æ¡X8ÈW6Ùa3Þ ÝTù£¢•d2E僋^é\G•,ë¥ÄhÔZDƒ¡ÁÀÖ%­¾ä’KkkãW¾óíoïT;¦ê¬¡ŠBÑÈQs³1!Á2ãðȾ89ÃãsPIÇÝß–ŠØUFFrŒˆSW¥Œ¤Xj4zÀ"Èc=¯ö}á‹_¬øŽ9Ï"qÒŒB0\§ë†[D'OáØåø5Z]›ÿ•ÂymJW¡7 #ðŽ569ºÐ•ÀiUÜÒ>¨´ ôXà\už—hÚ~®ê‘¾50c¿:±> Ç¡ãLàŸúH®ºª{ÌÀñ¶³|MsŸÖâð™aÞÎa€ýTÛríÎ6“¤Œ¦s ÄN£^F>ÂjÊÙ‹]ǵ.`ÉÒ%+µiÐ*=Ø©-ÀwêQ€|ÿؘòZv"|" g ä—8ò”¹,y¥®që›¶ñJùƒw€ÞOݼ)Úhx>ˆ NšÎq\Î_ñfæ ~†çþú¯ÿúµ¿üË¿ü4­·’Š}èÆÍ‹³v½Ú£S@«CðŠY ¥†¡V6"‹8t]²ýû÷UwÞù%}®÷oôjßS1"aúB½¶ Ššsx¿ŸCSgÃüÇûm·ÞöÁ³Îf¬¬iöÑ£ªÜñ[áwýæaøáÈ1'\ov¾Æâ;²žjeÒ 9Ä,€¦!óîg/ZTíÑäßÐÇ{î¼óÎJ yªƒ¯¬æÌ£‹¼ü :äà”ùÊÛŸbS¯<Þ8’œ?;£ñ:TäS‘q¡]èJàŒ/Ò•ih0º\ÿY¥ŽólMñïÔöÚ#šÀaÓ HWƒì¨gɇ«ÆåúDÝ£“ÎlÀ^=ªÛ¶uk¼%À[Ë–.ãS¾Õ¡#‡bm‘Â&öôGÛAç?BX$IÍÔ^ðB5é­Sš!Ðãµ[š³Ð¢å¹k×®½`Æ Gõ!!ö àCB¬_d¡¢‚SKiöè&¿)7 lê6ééê”6O{üLï4o’6‡¾!<;}xàÍ£í—8£ÿpþ¼>£çcAŸwÞyÿû;¿ó¶[o½õü©rŒ¨‚¦Å3¾¿‹š˜« L¥Øò ]©¹ãn,ˆEE¤`š—‡^Ò÷É¿þõ¯ÇB¿7ª’ÎÑ>ßsãYìãŸÓÂvâ…#? …œÿ˜ð£zÕéèÛT£WiÔ¯ýÑ£äòsþ¸ J® ] t%Ð+× :ÇÔ]µ$cêKËFª£ê f¼œÖàüÝPwã„»žóq! nÕF]tÐc6@ß ©†´‰ |]ÿsô”vå)eŽúîl2̨ãŒÃ¥cUÚ8ðè¼ys¬Y³æÍև޶iAó6¦ÎÁ@¬#*“+, u‹6Q£§ggÏt·ÉkÒ¥îiã°ÎÔÐö× BD–ëv¨y¹îc”ttü-Iúh¬œŒö˜µN½€óÏ?DmÙµk׈ÞV˜¡ïÐå)£‰Œ`ž7¿ -·~)Ÿ¯M§´qZâ4ÏgZhû¡á•|Ó%´³‡g·¯éøqø¥Ó©~ñjçÏ´¿zß³qþš[ø±}ìï½ï}ï{¯ž‰Åî~ÚßGÛûÒß§º/u28®qáÆÉM¾õ“ãv=Ȫã‚ÄiÊm@ß?KÙ«ü¿¢÷û7?ÿ|TàY³Èª »‘+»-“n¼¥$F4Lo=Ryž+òšù!{ãÁèN] t%pÌ%@Š–@Pøè¢£Õá µ»žªèЋú¦ž²ipœ¿– †_ŽNêQo%Q|šFütú÷èuAfÔþè3ÃK´ƒàR kÁd§¬¶ /9é <I”yYtÕv8¹bMûhbsÖœÁÚµkGõ¹ã-Ê˵‰trÛT[Å ›3'fÆÉOÔuœéÂ~¶§ÿ”Óë:Q­Æýn¾ ì 9 9(3èÒñÃo:~è Ο<ógÚÕªÕáüßóž÷¼[¯Ê,Ôtœª êszvVçÌêUªõmIõ?Þù#“NDO!¦é¥¨Ï ÄŠ`¾Ø÷å/YÏüïŽoëµX@Ä+Ee3ªÔ^Lëeü躑êðÛUG¯8RUss GH¥•ïJ +ã.\Ý£åQËrtÉH5¢cP¯ Î|…Om«~æ‹yÐSb8aüq´²Áã€Ùz´§‘·¾.¸½:¤Ù?öôÐvãjpÀšÁËÎÛ-G]ÿà ¶RÀnê2@'íÚé;²uÕ¬ ¨…Ó²¡±1åcŽ?|DhŒN€Þ2b©@¹00Ç €5²Áaˆ‘½Ì7.rZáXõ§eôTR:Ó;Íš#W¥ú¦3]:xîØù2„öˆßmò3ÈýB¦gþšöÿwïbäó?¢®¹äÉ>P|‡ ­ KR`|½¬+q£W5éÝk çqz?øp<ü?û·Õwµ/ßW¿$^ó£“[Œä!,(géÝ~B­Ž¼ápuä-‡ªÑušë犘òGÙ%$´ ] t%p‚JÀ-l -ŽjmÀÈ­·™©7X m…c¸OŒú®Hú§Cu_xÔo‰Ù%Qÿóšõ{éå—ªEg/ª†‡Ó€¯ (Þ¸VŽÿ…ÐËm ê`"µX©M!„j`äè‘-Fœ«>Àùç¯>ÿ¨Þ6Ú¬5ú ù›ñ‘¡ÔôÔöú"Nâ@Â+C¿Œ_êž8·Ê™š?vIâÜ @ãvm8txÐvú%´ó7Œ™ÞóW`h¥œÿïüöo¿íƒÔ3ÿ9sè^Õ38VäÈ\¨XdGü,ªGõ…jDsE…‡ã¦Ë¢>ÑzõPußw¿SýÕ_}:Þ¦6òþ0ûIu‡s2î´£)ˆ„Y•¬)Èëåü5òg•ò¸E~.©ò:¼+®N\ ¸uÊQípxÍ¡¨ºƒû†ª¡ƒ©™¢^S‹cï<.9à¸Õwÿ²±ÐÎ;µÉ×Ó±­ð’%Kz‹#’#¦v!ÍFöÜì¤ÐUP¼:*¤ˆhQt (Zí›Î𳿂5kÏÕÂÀ‡zè: ª Õ°7ù©Ô#Y7dðÇšùàÍ0™¬©{ÊÓÜ-gJ(Xð&M9˜oˆKOµ*A;;|wpöð€vü@>èŸñe“Ÿ¥K– ÿ¶œÿ­Zí¯×ëæãôUâMwrî1‹€Ë‘*Yêe×:©'  …R®XBÐM:JYYOÏûÇâ»á»v½¬çýwT_üâ1ˆqR ;\)‘£÷ဆl]0R½ú÷V#Lù[—«ï¿ ªƒ] ¼>%@ý£ÞQ_…Yy$ffìÖ‡¹öjç?ñêïàôñÂQ‹ \Îï̬<ë6mÚ-_±¢âq †ãñeÁ ¨­`V+M ~š1ÈZµrêŒ@ÖqR;¥†…͘µvíÚuË—/?øïÜ·…}40¢ƒà™L÷š5¨íT¢å:—Ê„8Ö%~oÒèœöáLéLöã"k;ìüvuvø†vømÎ?¦ýõL}¦¦¶b{ß?ü£?|ûoýà-óçÍæîÒýFå„e?×r’oåäòÑÊw+½F(uEg!b½g[ÍÖJßMÏlªþîï¾RÝsÏ=áüçêÝ~Vþ¦¾GÄÔ GŸ=}þh8FõíÁÃozµ:tÓÁjt…æú­äh9NºèJàÇP¹>Žkvî<-œ5VÍ|yÖÿÏÞ{ir\ç‚5mÇÆ;;„'hàP$EOqEò’"Šcå6´WR(äB!J¢B/z½û¦·}ÚûÀ«kZ‰ž+Q Š­h@€f€qífº{zÚî÷“_Ö©üëïîŒéntvןÇgæÉ¬Ì¬ª¬,lyè]|àÅ/q’9˜ÁÁÅÜBøÜØ9l#|w §ªmøŽïPvzzü0TPf0˰‘–}£…â[oE¢]PA!£6i ðs\ðŒ€ýXxä^üü>ÿ"îTòº(> B J.ßRRêˆÏ¸iŽzk µº¦Ê SVdÄ ̘¾bÂ<è+ºúWÌI€&Zøgƒ?èƒhÇÜãÓg?ûÙ'qåÿ>¼þ·»ûa“Þãà \æyÒAkõSóOzDyŽa^aÏõy’ÿô¹ŸVŸÇb¿ùú׫)œÜ;¶ï°;È¥CÒ´ JÂýfqe1õ¶ÉjútËŸdÞdcˆyvÊúïºÖ=p-=Às§1ô$ ›°•ðéjî|Âz'±‹ únÐÅ3ÝÎvè$¨Î)i.œœœ´7ƒl€Ï sç@n„ ³C&å3 ÆDb0ã‘`"s]² b>9< À—θO^¼ñð7N}ùK_:λ—ì¿ì.¥wVµ¢§àFܘ`çtþ* ‘ÓF»~”]•ðZŸ´Up¤n;4è3&Ÿ~⯠€p úšpÃ[ÝÄk-öI_<úßø{¿÷{ÿò§>õ þ»p2Íöõq“´ît"0âm;›J#Rð‹~oÛù—²I@ê4tKÍøý8qüãWŸû»ÏUßûÞ÷íj'Y5‹M,0½xëŽ|™ÈvõëE3·OWSoÁ-ÿ» Ï’rSêÈ+Iw=Z÷Àº®£xNZߘ‹qÑqqàÜ6 ÚXØw“~œ <Çùh€ƒ«bÂøà˜ïŠ‹¾&8„»»±iÐî]»ñ(€ƒ1»'%¤È ¹ýJ„,£…Ÿ”f P ›mèÁ7 pëÓÖÛn»íVLFÎ|ãß8ƒ…ŠóèGµYÕd](c™&GÌB„%·T|9:KÙ\1ü×Û ¬Lâq8̘¾!?ú‚5àk°gÌ+ÿÆÕ?f®ƒ|Ýï7~ã7Þ€Ïú~rÛÖ­»­EòlÓÝö4‚[+Æ hç“u‚ÅNƒuòqäsÈd@'Ë~œÏ[ýx—ÿ_ÿõߪÿþ?þû½dWüi&ígŒð¯€i‰ñâúÞ©êÂS“ÕÜa¾”Gj¥uhÝëX)àéÌž+Ý¡›Û9[Í쟮6\ì­†úí|çB`Šñ\çïð´wEÆœ  {Â$à>+|âÄ lÔ_áªÜ×Yÿc Nv–:"ÂÜ¡Ð;(ÿ6@â+²ô]»è°,mØ0ˆo;vìLNàÛgùˆ‚¹Dßz<jÄm|Ò˜»ÈÊ‘N\%!¼æ»ôµÊŠŒ8a8U1<=˜6BO«,³¤óBÁ^JÂ$à<> væÌ[xðà!Lüñ!¤)«`¸Æâå«Ê‹(AàÄAžÐ=öàÁÍGÞycþÉû·Æ£ˆ>,Td‡TtŠ©û”É:–S,®h!õ˜“L('k"^«€Xa„#ΊM±âðcÌ>ñÊ_°ÝÐ.üá~á6\ùàÖ[n½ +mÙ¹ÉOo#3 ú«5à2ä¦Jy‰hh­眭sfÏ“sttÄú}ñ‹_²>¸Ÿ?ßý·ý7h*œ„6h ðý~¦Åg‡“ÏŒWïÆkEXøgQ¬ÓÖ¯™VÚÀ~% ¾>9¸^\†tîrŸ€™=xuw× ôW}¸`#½Ÿãì;x’“”iÀ}¸hèÁ1:6ŠGg,A>àZîÈNƒ]ƒ®ð­^íŽ&î ìšœ)ãÇ»0Ñ¥Ô½3¦¼ñqq„%„·âqéMØ)‹_ù÷oþûhËnn‚Âí!ò#Lé”C‹¥ra(á—Üš‰×â V+*âªÔ2ö3Àÿ8ðÓ?<ÊÁ_Wüºú×àÏÏúöñ¶ÿÝwß½ëÏÿüÏ?|ï½÷>b³Z¸ýÿù¯ÿõ{˜,pô³,‘5³vGÁ¯%¶ì¾+Iw­MÊÊiÃãЦAŸr‚5è+Ö`¯8úvåÝ~|bs v±êÅ»³[ÿËù¿Þÿè£>Ήîf¡aú)•+uøjµÌ€]ÕKH2 Ýì´ð :pÕÏ;üÂ×ÿü_ÿ«úæ7¾á§“Ó –ô•ƒl"ó}xÞÿÐd5+ÿùí8Ÿ¸‚˜¥ffÖÃUó€êþ5'ÀzZκܺ-õ–“n©óq ðú„àœÕMTõ…s|~ó|5uð‚] œL_d—!OC˜0zD»n!l,Øk‚ç±@÷؆AzùÚ±½];˜”Ü]ÌÍ€fI¦Ls0K™"1^6ñ*û•ôݰkçÞ‡zxà«_ýêϰQÐÌîÝ»µ&Àä»ü)™TH¹ÎZ¡¿™Beu¢kiÐlA©­…j!¿<4а§AŸ±î0î:øcfŒ7ë7â„èÁ³±Í¸íÿÔ»°Á?>뻕6Çl´c®`­s¤æ¥ÛÀÎ ããÇè¼Ò§ªÛâé„̓*ìže·þ¹—÷?þã?Ußþö·±Pg¦â?ùÊßNZ([àÄ6ø>0nûÏc±ßÄç« LT ›S†ä©¬ÇWÌ—5è«>ºÅW,wWÑP·¼‹~™Ik¨ºLõׯZò;×\܃ya2°ñ4>†¯ Î÷¡oÀAŽ»ì:|m€»Êpô=¸½‰.âÁ!{ÌÈ}¶ã«¢ÔÑ{ûì­(ï=š'h((¤§Þ&É8‡õéýœ§”fŒjŸ>pðÀáƒN~í+_}ytlL»²¿“IÅÉHNJ†DW,yÅ¢—±g²¦–xÍYeÐZ™”Ò†“ÅMÊÁŸWü¼`1<ßç.v'à3ŸùÌ[?ø¡¾—ïú³é·™F^ÒwˈÿåökgÛbÝ#d´uîàG³/¾ôRõÅ/~±úÖ7¿i³õ-›7ãsŸ|Çß“´_Ø\à}}"H¼ÇÌ®Ùjü­çª©»/T ZìÇjôÈzx͸¤ÁžõÒíxÍ9IºÙ­ô+‘¿ny¸ ÛDÖà‹x®#^À€?½“€­³öEÁþ èÆÈW?€[D}0§w¹WÀ¼}3€Ûã™<¾&8/ î¨vlÛn|öeµ”ÙáÁ€ïÀˬ(Nd©Q—O¸dÙ°¡¿¯ððáîÙ»wâóŸÿü ô»X&`»¦‹3ÆŸf'šÉ¨KeÌd7¥:1ÏbM/ñš³Š µ8(+†x`ÅJꆋÎX }ø‰—ƒ9ðëöÿ¡lÁ¾ÙýضrÇ_ÿõ_ìö;î¸wýùq ¬³÷vÄ­¥YÛôÌzqDéÅ`*0À×bÙúy² UÿðÿP}÷;ß5>§II7…Ûmvj2QRaÀpäfêÖÉjô™‘jvoÚ˜,ñz¸,”uÕj„õV¡%ˆQ_0U¯¦ ¼·Å—Rލ¿ =ª–!øzQ;B<‹GS'mŸ€sX{Šóàû¤4ðVg‡ÎV““Õ¡C‡ðˆ`£÷]ì c=!õ]-ᆚ-ã“—d\ ¥£†Îôð‡>Í6=ÁcÖÝw½k+>nö£‘‘ái,¾îGÌeÌ2ç Ô¸èŒyx†ò‰/SGz,ñE•W"s5ObE¨2¢U9e',?Ò8’–‡nùÛU?øïرcóÈÈýmúË¿üËw<öØcO£ñör/kì Õ›H÷¡ªöWß1 Òñ'…zx!Ä?.¾áàÏ×ü¾ù­obÁÍ,fÛ[°ðoήî£3\;|ü'Þp¾}|+ýy~ ÔYq|ýwQ¸?i2ÕâšÔî˜äÛâîZËä ²Ù±šíCÝî1æeºÑ‚Œ9mtYÁÒ‡¦â呼âeè­Oqü8?ˆ7ƒ\À—{ªg7¢cD×Hÿ"p€6W«­cõóû"/^Dÿ„ ôGûöí«¶lEß„wöÌôt.™%½õ$€i˜~í?H“aÍ·§gnžýlOÏ®];¤)®aÑ‹=›µD‰×œ­Õ +D•¢ë\ ü¤kÐ×€?aÆôsÀ×$ _ý£±“?øÇüÇb·¿áØÌ)/Ú7ÞÊ«øÎÁ­14µÞ¿‹•1› ðyÛØØööÿ×êÿûÚ×ÀÜ€Õþñ>.ÞÛW)Il·ÎìNNf¬ôà\uîcÕüœ:艠§ÔÖã¦Ôý4©F?ê k9~•¼d¦»¢qж F%#öµWH+ï ±1´° Év”͈mÍÒ1tç6™1ѽ±]f R“-ÅíÙ©©Q¯¦¶B>”µ²^Dù7 xMp^Äßà&óö@À.“ìU¿ägòÙßa±³Móq7êáŽøšñlâ` &¹ú|Û ìÿ"L í¸€³“¥ ¬‡êÅ¢À±`F'Â௞Ž1ƒâ§Œ¯Ê‘ÎCA4áÝâ¨ÓMfÅÑ×â@¡ŠT¬ao±ÁŸ»ÿràïçs Ðvà÷ÿ÷þèG?ú^¼ÿ€µÊg^Ö,‰Ž-®ƒá¼$шj$[ «]+ì‡]}åË_±¦‰y5x'Œäò¦>°l'%ΚÙs¸ò?W=:j+ý7ÌÀ š1ëaQdÿ/&¥–&™=Æ¥L‰GÙ6Ø2 a“Mí'éÉ´¨Ä'‘FO'Úb±ô¶˜Sò_KÅ@á‹YìÂkSm£•ê’Q\òÎse=$wl˜Ã Ž;Sû/T½¸00†]m€m.M¼¿K‹’Yç\ ÌG¯¼ò*&¶O[æ~IY6; éL£¿g!M€ØHŸES-lÊx3 ¿ã‡ïÁ"Ä1<8Qì`먥æãw“QB’¥ƒèŽ­ßÕ:(+¢Ä5Ø“ÎC¸†@–»íè6øpðç+Ðxï{ß{äWõWßwäÈ‘cÀ-X#Gsɇ5Œ9=Og=ɰ™Èÿ옸Җ¡÷ùú׿^ýó?ÿs5y·æ¸;‡I˜TÝ$q¦è¼šÄk~üGß<â÷/x'Ž%+½Òzhz ®±&½©U5ˆ]É*î"ÖAf[0üd8Hlm&‘(º4¯(hYLiV`^Øy[“Glý»õæ*%É0‡ ©é@IDATI©-3KIKŽñ2Âú„N¢¯ÐÅpÀ×'LÚ$`[÷ΡË4ŸÂS¨K;¬ÝÑœð„'ù8Ÿñ­nºéF» À $nZæÁë\Xêô+QÅÌ}¦'mBnGüfÀÎ={ön>{vèåÿøGZh¯ @ž‰ñP.šbò+–ŽâHÏ鋹ãÕ8ˆŽ'܆“¦A?ÂôÉ#¬+~Åà5 °+}àö·™6áÛÔ}7ß|óŽ¿ú«¿ú(öú„ÍkR8R§Ñ—íC-yÊïu¡£#×q PÆ:A£Æ{úx—ŸÏÕúúûªo=ûlõÏØßÿ왳6«¶…}’ç‰@»ÉžÝ蟫ÆïMƒ¿j×ÒHö×£„šéàAþSÜ.US%Çx9õ™u¨~ ]µŠ nˆd ^¸‘™N„YbPÞóx`íŒè+€‰wÿ‰eì.åœK]ŸÀeòzÊÉý˜L÷Uù8€C=ùÀ–‡0åsàç‚ÀññqlòKë?.ì›´O o>»Ù÷ÁH¯rÄFà¯]å§IÀÄø„­]âzô¡6øçÝ]%i!²FŸˆ9å(à`b"²‹lŽÖ‹Ç±wíÞ=ö÷ÿ÷?Ãà¿€E½X •ˆL¡<µJšøLH0Šp™ òWmXm€Òù'\úHc95ðG˜ƒ·&ñêßnõƒÇçþØÏa ·ÿãÿøÑOúÓÃÇ(¶Æ]þð% vi(IÍD;üA?]ô‹à8BÓÒ€Ä À‹/¾X}÷áF üˆ°]éó¬c0UW¶``9ÿ‡GðÌg.›=E×z«#È× ’›…¶ÆË‘ Ь­Ž>,ð/1­Em­p&‹ªÓ"Û€Œ-^‚eŠ-nĹ¯ûI*‚[‡óqÀÔÎ)¼Ð[mÆ—Ù¹ø?aëjè1}ó”öq€ísœ±cà ¾]p–N5@;šíq‚L;I†ç‹QÀ˜ƒÁ‰€‹0~B˜w°(ðð!Üxþ»ßýî(„ÙMÛn„ÃA;œÞxbuL:C7ºsÿÙ\\rpWÓ tl®Ÿ<Ä˃eÖ Ï €Žxõ¯ ´ b6Ùÿ‘_üÅÛë·þ:tø65%Î>ÕV¬ÅX޼ñ—mY˜Ã&‚¨.àülÐÀ@uüøñê_ðÜÿ'?ù‰=?³ÛfàÇSÀæ(ÙÜ’ã„còÖ‰jø-g±Ú®²,•’¸ÜËø—òùKÉÈá¬x“ÇàRí£L”2n­R¤,S‰·‹"9˜<~èÓOf)–aÛ£\a"¢ñÜ‹ô5Ó?ìgfÓÂÀ]ªóƒÕ E}Y u‡èÓ¨ÂÀ~‹oðÃAÓ¸ýÏwlߎ»œé¢< JÃÞ$ÉÚnÝžu—ÎKµÎ~3…úlS‘ƒƒ[n½õÖØà¾2ŒG©¸e°[QÏ݈eJ§bJQäÖX²bÖrJ‰KnÅÅ«eP:´ '-øYFÁì5(¯úóÀÆk+þ1ƒ¸ýöÛwüŸÿù?¿óÁ‡|ÌkÈô#Ek¤–´Ñ­’s+rf®x€ŠæÃ“Ol&Í×ý¾þ¯_¯¾ýì·™N"f•År%ƒøcÁO X¨sö‰3ÕÜ6¼H1–6˸äú/]S8^N¡¯tˆVÆâ/å×<à'›ˆ”*U›ùhS°1.ãGú×;¾Œ¬g•˜÷L¬²èCëøù#ã,ò³¹åÈ0í×ãÉEß ÌãÁ {&«¸ 081èþGà?¼â}˜M@ãÝÜ»gxhÈü¶gï{M¶Ø¿…k(’RH‰Ñ¦U6ÈFJtJ‘gÒFq »vï:ˆG3ß~öÙΜ9;ƒtôf€N?Æ‚i%ÂÂI£qñ“ß@‰·é\wÚZ˜ÐÑm‡A þ1&¬‰ãxåŸþaæ8ˆ™c?ÚÎÀg?ûÙw=ñÄãÏô÷÷ Øý¤Ôn¡›ªÙÛ´µû/ þNÂ/²pàÔp =øÀVØßºzƒÿôÅilþìQG˜õRs¾ŸßÄ«9S{/Tg;SÍ잆œ‹ÚÀ±×ýo×AŸžQ«YÌK—"c½Õ"ÆdKñ"¢,ét‹;®¡[þD_n¶$¯¸Ð#YgëÒ¯@ “%(‚ì)=Æâu1AÑ×Ý$€¾Hnœ«¦wLUG7Wø€ø:$ÈpÐ÷Ê0ÖI_OŸíY2:†W’Ñ'b÷[(È/¥³[s³îìÜÍÙ-Nú\í2F²:Hý­)™‚õ¸Hv1^ó¾kpAe;™Ú‚PO®ãö’Ù!!Èrr' `ðv#œAŸºi7n ÍŸ}ç;ßFß½€n=> !–f±C2Œ—rÖ“p‰/ÇÆ5•Y+{tvŒY6Ñ8˜—ƒ~·«ÿ~4{åïÈ-G¶cÕÿÇó¹?Z6f³¸QŸZ_ËÕ}ãÍ*/´&†i€52xxËC¾°y¿î÷Vü¡Çó²ÁANfòöc‰ú8ÀÑâç°Ðoø‘3ÕøçkC4æâ„ÖC›–òøŠÛlf|Vl3tP–²ÓTwìrtÚì¬Úå–7è°Yjctå^¿®&Á»o˜Â®; Õ–3[«Þy¬‡f÷ W󪟷Ãhß-w4q …ÇÃØ.xµ}Çvß@Í$Ó)eöi„vØUÚ=QÝ* [hzžd;ÿðŠ“ÅUÿæ[n½eÇW¾ò•ïóÕÀM›7ñÎj\@+TÑi«˜tñ²i'½¤%V*Œ°¯æ + <â€<ò¤±œ:HÓàÏØÞóWÌ«, é½å–[vüáüá3o|ã£õõõâ-€yûÈSÔ­~küáÈŸšŠ]íSdÑÍ 0k ¶a¾'P8q¢úêW¾bÛhr! gĦÃ3€h®k(n¹ÍWCo<]»ýJ•˜*¹‹¼.ó–¶ÒÓ•îÎN®xÝøÔ`w‘‹uÓYÌžrm ïRã2²×ˆd¼(“ÑÅ¿uè¸Ì`iC·Œ—2'ùB®‘“ÁÏbyìb'›Ÿq—ÐŽº­v²üªûâ¶‹ÕÖl9³ÍI²þØmáJÜbþèÏÞ @ÇGÜ#T«x'€o ç ÉAL‡é(¤þP,k+‰g³¦±? ßÒÚºuÛ.| è"rêÔ©‹é.€¬2æ„€pYÌ/‹ñágë\ #¹J™tw$^$'c»íó2öã$nä6:pšL©[,˜9‰<âË u†\ºÄ—cãªË¬Æ €ÉX‡}Åä kÀ˜4ÆšèªßVþ£-pÅ?yú§úŽGyäIÞFBƒ…6a Úö›Z‰5ºxÃ…sÄVžÀ¦æCs [Ëí~ðƒTßúÖ³ÕØè¨mók³`rÉ"·³¡:˹êÌÃgì»Ý–ç,åë4,:ø/æ“äÚ®"àKDq–í dN D™×Ä3˜B>Ò ×íÐ8ø)%h@´ZºiVü&µ‰IW²%¥ÇÎX¡†œ’p!Ä£–`éµÄQÜ"’I…L&È¢ššõ–,¡K¿s«J†eg„x%§¶_¨ú'ªÍçqÓ4½ PW­ Ê^<Èãʼšº0UMN^°¾o×Î]X …E|%šJn¶v0þÕæ q’Ég„!ƅ͹];wÄvûÜç~ˆ~Þ^æ‚MÉœ®¨DSLk’QLC‰#½ÕVê t¤ðæ¡AŸ0Ë#\ƒ¾nùëÊ_q¾€/JñC?6!ø³?û³ÇŸ~úé·áÙüv³Ë}Í3sm7&Žà‚¡ÁÈ3èX-K&‘(ˆNc{_|Ǻ:söLµ9}6Ó¸*•iP'S»ñ®ÿƒ§«‹{¦°Ë„(·>øÓAu¾«‰ZŒŸ¯JkyЧs“°TÌbiE1]ë¯rŠIÊ Š*ó2ÑA‡eò "¤áM8i&|q©köS¦h®-8]‰% 3$±±ñãÂmfjšdלNˆ2…\ÍüÕíˆ×Â2ñ;SÍ”5;`Ùѹ[àì¦ÙjvóŒ= ¼à{ØçÉÓ5«Ú\• ž?ï–ŒŽáÕÀÕ< Ÿ¡á¯ÜF¼_•¶}³—´Œ›eÍŒ 0Y\ñ÷àqÃV<˜üâ¿xÜ~¶;¼rà§©¶ƒFD'¬ ¤…ÇXEˆ´ ¯Ä @éÀ6œCé<kðg\å$@ƒ¿]ñó“’h}o}ë[þöoÿöGpKê'‰nžÝfš°Úc{‹Î¯þ™¸€M®êãU”jë–­¶?ö×¾úU|6óË<BNPÚÄT¹†a~`®:õèÉjâÆqŸq³tÎ2þëéÇ»€–wóé:ZÔŒÄÊ ¡-¥K=É0^*”~yå& + k@gb„»Ø»&äë¤Îô‰–Ae«é”J¥Ïúmšµ†AYØbâQ®0AÔÆr‰rÒ4ÁâuI¿1¨Ig-Ä,/{ataÓ[§ñ)òÙjÇ)ìýõ¼Ês–—Qv½âvÜÖ;á•B¼}qúbuøÐ!~ÙÏö‚§s ºÈ]+»„ jþˆHi⪿ڴiÓŽ}ûöíúÑ~ôƒ—^ziý>ûäÅ&L@@ žhn¤"gžø$(£*qѯ[Ì¡d¥…ÒIÂëРO\0ã8ðsÐ'®Á?ú6ðƒÇýlž˜˜èݲyËæÏ|æ3?ï=÷¾ŸµìáªÎ!Ãæ‹Ô+ÿÑ3Qô­ªëúö‰˜@p•ˆ`Ñfœxàúîw¿W}ç»ØéýørÖ<'¡êtS}ž iЈŸ~èTuÏþùÊÙJö™Æë%,:ðwóG7:âäD¯/ò‘xÑrd¨”å`=çljÖ1BDé;ɳ9ðèè;2²LB²Ûm±X QF´K‰ƒ¾5õ€G3$·“ÇÅôXWIW7¹VK5±E†$³nÄ&‡-iµjÃÝ¡5; `‘SµLošÆ#…jûéPKpú0»˜á[úN±ÆÍöÏ/rrÀí‚7n´ öÛÂ4™ýe'ÕRO³¬šÜÀg•ðòÉzhòqðüA?ºAý9ïìîݳwöÿ錾x.}0¨œ0išÖ!œ1é‹feU„•6(qÂå¡Á_9 à@'„ó³Œï˜æý|ìcû6¬ØÂgDà™olÇßF;ðz匒¡‘ÁˆIl‰l 8¸ÙWýs§?¶#MT½I¹úÜå“ÑÛFª¡7`ÑßëxÅ:åéÑf(ý-.ém<ö¨ùœ"^‹I±›^i·Í¶d›-YïnX6åYZ2µh,½¥b¡ƒbǺÿÊn”­[e—';òuÚ­zd@Ó¶ZÏ%òo”ë"’me¥Â=¦×¢m]Å+éÀó ÖÂ[Õ$–C©½°uªê¿Ø_m>—¢gf¹íÏ\ (ù( Ùö(€ŸÞ»w¯_`%gPGÁÛF‹n¦Ù/ÑÀI8O{,àª?ë×wïÙ½‡ zöÙg‡!…§¿à€˜§žbÂ:# r‘Ÿ‰Pf¶Ä ñk‹r@\©¡›£DgÌC“Æ%L\“Æš XŒ[@øÐOÏûÞÿ¾›铟|ûÖm[w±ÎÑ"øÌâÞ8«L0oðc|4_QÔÀO“¶b¶òl~á¯ûŒŒØf?ÖÒ4A€a6TK‡ „ñÃ竳÷aÿú¢¥Ó×ᵂÒË ûUB¡ØMObKñ%Ç8É.Ì-T3Ã3‰Cbh#*ùD R¢¤ «zBFXH.íé÷v¾l…¥mÚ ¡º¨Kid¾ŠCUÁü &'Î"Gj ‘½Üô“ ¼Ž”xrj樤—›÷ŒµøàÕ»OTýÓýÕΓ»°F€Ã2|h=´éö ë—;ò­€Ÿ<÷\µmÛö ïîCÚûOÕ¯5ÔëFAM#÷¹I˜wOMÐÜ ;DSŒ»»?þŸ>þ®‘ᑉÿöwÿíyì ЯóÕ@ö¼ìl³%3)ÂÌ5⢴ĘÒ-5Ä Äľbâ•4ˆÎ+Džò[SJ1aÆqÀÜø!£ @?¿ô÷Á|ð¡{î¾ûQV™Õ"FlŽËºÇ¤š4$rc#ØÖTÔgãSš.`Åÿ·*<{ª×Ál#-yžm \¼:±g¢:v{n_t£4¤R:eMÿÊBfg5¨5Òß {½dPƒW›j@Ë’q»Qƒÿ¿¿çÛ‘´_¢þŸ÷Wƒx¾HQõ—k Àš=g±E*-ªJ^qP«ÓLL¤a춉ÀR6sþÝ–Ú·…J| Äì£XVøc ûœºý¤M¶pMµºÊüpúz`pÀ6Eãâa ü| Їݨz~í×~ýÞxà!›¢¾£ÒXo~èD5¾µ°T§ ¶™¥éIÅl˜:Ú—¯¸žðûßûAõʉWlàç‡,púšJÊt´èo‹þ†Ža›_ܰlP8ɹâÚþ•Ï¥\¬üäuãÛÀ ºT­&·vÓa²ÙM†vãQd€)nèë¦ÌÖò<€;vÆ¥ú¨Oo%uÍÖ¬Þ ZDU¿‘V¡ú$îy±ßRzñ6#…Vk[/dV*¿ÁMcG«“w¾ZÍöãt^í[%¸(gtÐ\ø‡~;öU?úñl@6K›µ…úéܵîÙ,˜,©Ö7ÃIô¥ûÓë'I™¢ºn¼Ùm «ûî»ïáßú­ßºë˜ ,Ѳ¯¯Å±Bã‡b/”!L½Ô™gœ´xµ@Z %y× Væ¯Y‚ËH¨tLNš*€•#œ4ª´X¡„û¹âqßÎ;7½ÿýï{+6ü¹«AqÃÖ yzÞz@Á”MÆæl5 1Jp ›ý^,#à$€«ýùÎ?·ÀÄg|ÑRÆçþŽn¨†UçnÄ6¿ Á¾Öö¯ŸÀEËÙÝxœ“¬¹‘²ñˆv‹/¼Œ »nB!çÉÆ†~ëá5yÀ¶Å®-°í®Õd «—UWŠk35”êª&Pâ[š`å´Ìf!Ñ,‰Ñ@M-§&®(õ]#Fp'àÕì;~ÓÄBá{{欿)…É]„ÁåñAìo­.¬_†•„×ý´<Ë& 9t¿Ü|äÞ?ú£?z`Ǭàsl}C4¦0f¯Ìƒ9W ÐpÒT¢“¿â3½ƒœUæ#ÒË¡rzÄIã+ª¬À>,î³Ê½ûyê©§ŸÂîP»øÎ?Ÿ±uðÏ›ç‰lMj1j$—S£²gÚÞÈørb¢zþùŸV?}î§f¦¯ÙAC¦}6úF¡@¸€¯k¼ÿUì­íÏýuW k>¸ßC1U³”Án<žÒAÚk2;:(€nö$fv…ÄØÛ†ÿ‚® M"\À¶^£x¢t ~¢Žcýâ¹nhCm£Éæ£Llq‚—k_v›¢5Ö–^ ­ã¨-¬ZH}ØÞ 8~ÏKÕäŽÉ|šðR¨>iÜ!ì_¹# ûÓ_ü™-œž˜àçÎÁ'ÓœÎý3ŽØï’æ¿õÜî3êX?ž¢„¢»ß€na>ôä“O<}Ûm·q‘n6Øø qc±Á¿m Šòô½€^8Q긽–¸Ê ±’Bé94ÆÌoÄYYqà¬Ê³2}XT2ÀçþØ jÛïþîï>±gÏîC4† /ÂÙ~ì°F–ž”Þ¨¼ån4É;Åu8¾»ü v÷ûVýÿ·²øŸ}1Ûösàû l¨¦7OW÷Ÿ­&öc³Šj© 3¸Æ=Ø,[p¶q೦ ¯-ˆv³E+‹Ú_vka§à$V%80д¶Ìõðš<`whAîW\XÍõdYeP ÖëÎëÏiů҂ ƒžÓ5{Én)O¼Í–Ò òê_iuƒ,#„øÜžsÕé#§ª‹›ññ tÙõyÏþѧª-~ob?ÿÂóÕñÇñh`£±ø˜@ƒ¾­&ȶõÙžž­âÏØšœäì[¼ÌÛ¹s׿û»¿÷ôM7Ý´ë׈ic-qÌQ-2fˆ¸hÎñß6Zä_SøzOº9#Ò Çƒy&ÎJPE¦C¿*ޏm´yÓf»½óÞ÷¾÷ögž~úýý}ÀÑ(ðÑ(oTDð“Ðak3F±¶“¹>?`óªm`!ðOŸ>]=‡WZì•?¬nµAvrÁ òèMÃÕ™»Nׯ²`MZkPkg×­ÜÝèt x±& ¦ü:­þTGneÕoõ«Ãb]Ê6%$•퉿XúYx¸,D?Çú°Ixd«Žç º­ùèÆ']GRÌi³ µ…Ålò­çF!³ÑWo?Q :ëYç o3èX’ÚI\?†¯òBjèìɲ½“t7.˜¬O¨˜µnu‚Òˆdy Üðô>lÿÔSOþüûßÿ;@æcbÌ6ò7cÊñDãŒbCÌ8ijŠA²PL”ÅéM©«„1Ã+%”Š$\:—ù]UFŒí¶?níôc0^¸ãŽ;v<ýôÓ೿;YùÜ!ІØ>ü75CaÞ™g8iÖ¢(„Z÷’àÕ>7ùùγÏV'^9ÛYØæw,«ÖØ1A Nê$z±"æü±jøVìIA†ÙvqŠ­Õà^.JgŽi¡µÒá¨Æ`]»Î\Ø!J;:Zت²T .–&nÐÕ•~ƒ/›Š“m¶…õðÚ<Ðm @‡Uù¾p9ë‰u®IÙuÝ%(êú–Žø†´ü€¯;Y=…ü%Ò[Ï“ÂäŠGYf9ðé›NW£{‡qõ†n¸—*Éï”z‰pc_ ì­Nc•Q“¯‹µFœ`5£´ XB³Œ§ ÜîÌGÁxðð]w½axxxÐwóÂ1Ž%‚9Ö˜0÷,,m2 ”Y½^!:!Â1?¤Gžpæ[i„cåpÖ¦ƒtÁýŸúÔ§¸÷Þ{²¦á-‚÷¢¬uXÃbƒI‡74âηfc°7Yk[øáoý÷ ±ÎÎÌÚë~Ï¿ðB…-†+Îb)Çaß Â À6ª…¯ü½ãLuažé#?&ˆ4×hpï…k+s­Pë@/G‡FŠ«¶3„”r7ºÙL2ëÑå{@=Ôb~.­²hñÂÏ~V½¶}:x`w^©ÎíÆÅ|ÓƒG¡*#ûI› &MQ‡Îž­^~ùx5>1Ž»­=öªu=øCÒúhSò €7ØE³.øc°!g‚é!ˆ±ðØ/ýÒ/½_(ä˜Ò_,Ô¸¢ñ&ŽA‚Yk„—H@þu Ì赋Z‹y-:˜4᪠ÍÌTI|öߊ¤lÿC>¸û~lù·qÓ¦mÜô Ì說­±Ù±Á€ØvH–1å¸2µ òøË/Û»«ÄíÊ<&ž{ÌtÙ¸§·LW§îålÅüKœô>lê0À™vüÛˆ-ß¶{ÏžÃàÑÈ`MÆãt¦Žœ©%¾@ájK¶¸Æ8CÇ,^ÄÕÿÐÙ3X]ÊWXÒŒ–-šÍØ.7é+‡G«sXüg˯wMxN®Ê/¼Úi—n)CM2Ö¹'K’S,ƤµÒ‘‡ÆáB9gm:²Ç¸[Èí…hMwS\§/Çé´©ë´QOÑß´¦ÕÇ"¾§~ÃFª±Dgz;,äzf:!È–âÀ2C ×¶Ðh‡I¡M?ÚZK0ûºä”,:pÖ>ÞƒGîÙ tÿÒìO}êÈÈpuâø‰êü¹ó¶Ó*'Ùþ!?š¤Q?âo=  !ÅŽŸÄ t„“B/nÂxñ vÜÄñ#m¤±&ú¢iüQLÓ,JÛA^ ¹È‘x­à•4ìÈY±ì¢1f^…ËÑŠóU?dT)Xʼn‡E˜=z×·~ä-ƒ›}Ó\‡sÐGu[ÃF&l¹}ÔfÓðÃdí ¯ô‘)ü<÷üóÕ«X­Š$¤(çteÙÓâÕUªNßvÒY&ÈŸµä‹\:Õ`&$€ôn!u˜îÕ$Ô&ßF£xG‡[mØV> 1‘sœí–Ú%ž5ÖËð«ÁªBõ¡¸a« hC”HÁg•µVâ&Qè™Ý6š1Òø¶rÛ ‚KÙI¢çT0±ê@”ù•#'ª³ÎXË÷ø$ úÃüï±ÆßÀàÆêÌ™3ÕKx5ìÕw›S?NØ}îÞ2?ìÒÉiüQ‡‹¸9Àm†Á͸Yüø±cÇvCOwû´E0Ç–8ÖhüQl9† qÁŒc=Ү̌^ËP:CiGºTÆrªœ­>ƹr°p¤t¯ýÝyçŽOýò§Þ¼cÇö=l¨hü¦äRƒÐâ;5$Ÿø‹¥ÖPRq¾7,³‚ͱ¥tÅwþÙ 9+íÃÝ"Î\Uýl lÄ6›238[ Ý4TMmÇf|îÏ"GÖÎ/}·¬ÐV~žøÖ¸ û5_v±Øfƒ¢ÖHǧq©½£^G.KG6-½˜(­ÔŠ6±$z­Ï0uÄÜQ3zYmÁ.lÕd"¦6ÔlÁÅúγt%íV% ¬EÇ4%’È xnÇÏ ƒÛì,“¶ìs+g`…©œì/l¬Nï« Ùâk¼ÎŸNiH³cf`Ì~Ûð´yPÂmWâ»<½iå:*â“ÄÛö~ꓟ|Ë]Gýµ@ '[tÄ1G°Æ%–Œ0ã¶d äµ…nô6Ù×L[IÝ“œ¥BE' f\¬ÒòàOxÓ¦M¶øïíoû­?öØÛp2CuË-cH‚,oÖ¼mdš±‰ÀüpáŸCýð‡?¬†‡†Ð8uÛ?É&›Þl P½õt5¾_ùCP#OÒk*²Ó)–¨¬]ñÚš;ht²Í]äò)&{1¶N„Šºãw`L$ÙSeµ¶tÈ´Á„q–L@"0‚Œßéq1Úî¿Ír=\®8Ø@Ÿ ˜› Ø'±ÅÑóªÙ¤Ë̈Ç8…l4'g $§JòXöD  ÓJÍîbÇljK)ðXâXê¦ÂêÀÔŽí«NÞxÂ&]¼£J:'`„½Üt;w«~úÓŸZÿÛ‹~x¯Ò»×ÍÓ­qc‹ zð+% b­-¥çÍoyËÛßþs?w`nÄñDƒ=OjŽ#{ǃ™%î™v¨Ñ„3&íºfòZ…²ÂÇ|ˆÆ˜Gt.aU„*€±&ONNR¯÷‘‡>º}ûöݬ_¾úg Ч!@: åOÖ€3Î0 ú|&…/JUg‡Îà™ÔñjfvÆ?ý+…¤g'(`A“Û'«¡ÏV3güê_¥Ì ¬ Àʼœ¢´•ß:Öä<Ø0¨Mn1û”Ï:´à?û]5Ö)$C `œù±<É 4Ý®u#´k©¸ž$‰õïåÝÃõpÙ3écÂ:‚ÁLʀׇ‹¨¾ˆ¶ú Ê%H!X½¶ÚQm£ÐÉêmtÐbŽLÖÚWÖr€º: V‰.û|+WŽ2ò.ÀÌàtuúЩjb/Žp¾¢²å'?ý ˆžŸ}6/ÆgÑßžÂÛV#Ã#¶7·Æ>lÜÐd\YH£Õfpš{ÏxFÀÔƒ‰ lݶmÏÃ<·Æú4ž.Çâ:Êq*Ö `Æ Ë%{Ubfx%„èæIŽy$\:XŽ×ào+ÿ!ÇO;öüÁüÁ£÷½á¾¼^QÑlWløñCfÙœÆfb5yƒ¾C'Ìý{fgf𡵟Tc££U?^¹¶§»æYï…ÜÇ€ä>Ÿ=/é0i‰D ^$Þs÷=ÿÉŸüÉ›Òk_8îhÌ!\ŽKªuÆj!ÒEc®y¸^9EnÃ5(K<:½¬ ULß›ßüæ÷îÛ{ÓüÜ,'†¨ ú˜Ïö½þù›·‹ ÍÊÔóP®2µF25(1==‹×QNV?ûÙ‹Õüܼ­Hµ·¤ŠQ’³[6¨ñ&ìêßöþgn®—ç•¿«»g ÃeÍ’ÝJ«ýž¡69é·ñò€M n…¶:îl—6h£ÍŽñ$-!ÙtÚÔÝII8'¥œˆý;×ïЗäÔ`@5¡ºUýR$ך*%ëe(do±Å }i×:™â*KÙʆyZhXÉí·.ð–´ZϽBm¥¢öúúIƧœ¬Æ·Ÿ·>³Î&-×o àq+¿.àCA'lQ ï˜7ÑQÛãt¤û¨ïäqÉçŸUhìäíoKΣcÇãŽ<úè£Ãx9ΔcñHÓ¸ÅÚ"Ì8Ö\„ÁjðÚpÒ®x¸ÃP·‚ËAâ3–Ûâ<ÐCÎ*‡ 5ðL¾;ð |úÓŸ¾çÀÁƒ7ûÕ¼j ÊêÕÌÊ&Â*÷º7ØÛB-c|0l† Ù~4ºÓÕqÜúç~ÕlX6¤Á#/g²[Çíê‹\,¨„Ž­‰_x«³mål¥¹.³•69¦°(L?ÌVÍve#ðHÊ„u™ h¿™Ì­“ϽÛÙ4×Ãe{À|š=ÜÝ ä4>¨jM+Ô‰+Ë€Ußuž’’±²Ò©IɦÙýškP‹Ö¶Ü–eê¶žƒE6V*ªµì#yÙúL.Ûâ£þ1ä>6ãò/ư‹è}öÓÖ·³ãÅûo­Êé'«%g{_/6h’§ îß·ÿ–_ûßí¾ÁÁÁŒ/=gôV€Æ ¶?ŽYªAÆ:ª-H®.ú‹¯Å ¸ÌxY`òIc¾¤'\¥³ÛŽVx|k£ÿCúÐÛvïÚu€5Ž…^N«uk¹ð¹‘Õ¸Q\À›‡·„¤bÏ—(dž5[OüD%Wÿ#M›­RݪÉ~1{eÝ?R½ñL]Ý.b¼µðãþ*JÒVÆVš<\»§ã´ iê¶ê“NÌ…ÇVÃm²jµA:C£óõͪÒN6ëSÇ :í!¬?p?¼æ_ÖM< ƒò= Ö ëƒ éâ©® jmIökŠCÉf$Û$€ô˜JJ´Ñ޲€g( B%çƒÛôi%#-4? ¢Å2ÁŠ«,ˆOÄ€¾Ç¿h~v§„ŒóBηr»uþ¯b=À îÊÝ$½7p¬ï޾¿á&÷\H„¯bÜX྇>ðÁ<£×ñª7'­cP¢k¬b©t&`¤]·àãµK¾[aååG8c93Æšqi†5y=ýS/R¦ïé§ßvãÍØòωz±éZº¶ýuä‚-ÁD–É>1E°ùÜ æñì鼂Âí~§g¦¡Å ì:ä4ˆb óø®sÕÈ‘l-)Ôø*‡è¯ŽÐá_H´Ò\[,n“ëH ¬£¤V¡T &MZ+úŽ_B´Kži7~H2.Æ­!Ùìݦº.ßm†ãÛI$bõ’Ó$ £ ¡Í²©v±]¶Óom*”¾!Ÿè Å|wÍCÝ`=ƒ­çeæ®P ¸ïÌž3ÕØö1tþ¸ `å&Ó€á?WüÙÙéê,¶ æ_Ň_p ù4Çà E¼ä›vœÅdïáÇýÜÏýÜÐ웚šÚ€Íß4àÄqGc3Ç*â:fØ Sãä1ˆîØUþeF¯WPAcLX‡œH\°œN\0cîù?€E!½÷Ý÷†ÿøÇÃí M¬k\•óÝ<Ÿí±°eXëà)¢¿Ä‡!k ¬{Š™"A¿ýÏ á<^ÿ{ïüsá 6r»Ê1c».EÎ:ƒý®ÇÖä;ÿôKGH>hЗAk±T›hÕ—FÍ4?âdµH&- €žT/Íf-Ý·þ`Y®î&ä ²ºp» ÔQ\i„[éØÍv¡«é‚Fzm¼eÓêvtÉö²Â*’_¸njtçî¼ZWxñîZ¬7Ð?`{°ðóë|^³ÝÙßÛŸ|þ¹vÀç&i}>§ 3œÄ‘Šm‡; ›>ö±=… ‚vsA Ç0ãÇ$W,Q “ƃA±c×á—™»š¡[Eg,Xù±œ×ëêß&|îù¾cÇîÞ{ÿ÷¿ˆ/ðö¿Ue^ÈçÉq ÷*F5ÖA%Þì«NùE*îCÍÙ&š²sðmз£¥²±Žî©F÷ÚB°Ö~p×6˹et4µÞAóS³f&6ÄÉ*­éC*_¡5¬MtÉ ¯(udû0=Ú®‰½›¯ö)V§µ&!õúÂZNNV=v°)©:388)›Î4J„ÐŦ’–¤i±m€Ð´À<‚Òf'´Ù)íýµèfëàâ¿¡]gíÀ»ÛÖ§òÜôž6õ¶æCÀè‡y£wdxØîLOÏàNÎ;vâV)ô/‡5®7?ù–i¾4œ1ìpf°€ þA|Fþñ»ï¾{Ìô¥ÇÍšè.@Û8Esª`±D8 èF2— _ËÞ)„uA\4ÁŒ£Kztx†qk¾o|||aÏÞ½›üñ»7mÜ´Í*Öß ± e‚Þ¼Ò 6!§Ö|@DÀÓ$³JÎ/8è?÷ÜsÕ¾OÍM€ÈÏ9µfi',>\øÔ‘“Õä¶ Ñ"—,¿ÊžŒÀš*â´B¿M·«¾VQÖŠ¬“o³±$­™‚¹Ehõmy{ÖÈC—£¥Ñ·vÀD®ŒèëdZÍ¢[JàÇú¥³’ôÜ"‘`¨@³é Bui»Iv‹¦Ód©δ ÕÈŸ ¤ßeØë8GV.¢¾r‹§ÆBël¹Ng¥2[gÛ)‡b°æù S±#ëKÕúé¬Ó³ÍèLs¨÷ç*5}cþið%ëR©ÿÇãc¿e0ˆ=öØc÷ìÛ·oóùóçøØ’y p9èkLcÖãÁ„R©<—$ ˆæØUü½š€n…bA£ÃD—Óäà6{zúQù|ï¿÷ûð/ÜùvnÈÎkï8H³Ê9°_“EÔ¢ ùãƒ??DÁ«îþÇ×P”Qð³°a$šG<Šï]íµªä-­kW¥ÊÍÕ‹£ÿ,•¶šn¥e[ kõK›n–• Kٯßf£cö¾Á,ËW²ÆN=âu"×ìÆŒ¨gÓÕ<Å:r³æô=½ÉØÛ]òwtw.5yIÁ& Ôj£$݆ 69hi#–ºÑ›¶ê´œNÛºÛÐÙþ˜·ÎütÊÁòb¶¤U/ Fh q ZǹÚ0°BÖEê3‡wWÃ7 YŸê›¡DVQ1ï¸RÇ€·ý‡q€fãÞ,¼Ë+~ûS'WÊ·´ ÿæœ9ÁÆ 3düªzàÁßô‹¿ø‹wñ1Ç`ñ$_lœ"_5؇:áŠÁêä]• L\ã‹…̘ùéæ,:˜¼óµ?òæ|lúsÛ¾}{ÌÛƒþºJ1¶ `0cÍ8è'¢ÄÏgL\iú ÏÛ¬’w—,ãøÑsJžüœ,Ìnœ®^¹åD5Ý­4£5d•ÿDß\RQBçG7[ —L6kšýÝÚh éK'[ƒN©f¼’(Eu!Ù¦ vë½ScɺëÀåx z–úÍú‘Ï3§N"×QMŠºQ3KdbœÕb+ë´…º‰\6 ¹¾LÝË>gÛÊthê3§pUÓ‹cÐùfý`z[NìßÓò¦ïKx={k´øX|¬sëê9H«Â­!Ñ?Ö&ì×!Nɶšç‡‚öîÙs Ü–9ØÞbà›"®œ‚m9¦ Ñ3wìþ^ÏÞ)^NP,G1¦C…ÇÙwe"Ž•þ³=þð‡o»óèÑ£©úl¡ëÓkŽ•î Àk:áLÍ‚7þjB` Jüã®SüðÄñ¯ ¦ðv*f¶Õ/Rõ¥:m|Çx5²oØéÌJšR[­}Ñ.±lÙB7½6ºWb#éËü™@H`À¬„ªVæ5ç7¦^˜pV’4àdtÃ`´¬ÃËñ€ÕEAóx׺~o¸©km(ˆÅdÈko艹£0áÂN–jÐÒ %C]h²cùekl)K™v™5â­ei\)4úƒ…F|v÷™ê¼mÌ€=0™Éaˆˆ›ŸÐXH=¯²ŽŽàî«9ïy'YÒ@༒ü™îò'¶{Ë'É !wÜ~DZ}ô£·ã.€O¸  GåxÇ.ò”‘n1D®m`¦®v`aU`¦%Üê,ðDW̼鈳*Â6ðÇ ÞñŽwÜ¿ÿÀþ[¬–h4õ©*AI•ï­"7'£q°§ˆKÐr{·ýÏœ>SÍãËÖü`[r&b=ÉBÕË×þ¶cÓŸO®¹…îGóHýkÕÕ^-Õ •z’h£Zöy IµÑÂD¤\G‡ +¤Ï£l—z ï¦“t¥œ…d¯¶$®Å)–3²6Ö‰ŽTBVƒûYþ'U9’"H—–)²-b–2SÄ‚ ¨ôJzÃN"NK^J}SAZÙŽl´éЧ¸%­ç°äWpÌ'Ç$à¾ÔåŸaG'_çÚœä8Ÿýã‚l9°·öþÝc*Ñö‡ù/£LuyR8.øŸÏ.ïÛ¿ï¶gÞþö‡Àæ8„]gò—5àÇñJc3 ¹Æ™¸x¤3,…»Ôküe®F`æÛB¤Ç ŽÎ¬XεÁŸWÿšÝzë­Ûî¸óŽ£7nÁîs|oÓ‡WªeÄ Ä:à¯&µÎž°^|yêøË/ÛêÒì3¤‰‚K²iÐ^’qt÷puæàiO¤6gYXS?—R6ÈÒCÚôÚh6ºûÖ}ì´lË JNHŠ;:Iƒf‘–[w›Çf í°rì5ì$´·ñæÁêÁ/Ü×H6h#? ,¤®Œ6-sBÖ@~ÔòËo~=°'+²(KL%ò„G>iu§WVjªC ›±Å,ŠT¤Ð" ɌѢ8˜6-Ï‚ Ø9íjù×ü„‚>ùL²´kzÔiÈ¡`[ƒI¦%ŸÕh;ç' ™])XJÉn‚Q$%)RGœdô6ZC`• (ûÉ}¯V;ÇvV;ÆwÀG~Öôœ]šYúÓ#Û­[¶T;vÜ…fÀ­ÜÑÓ›BÔ‚a« ÐðÏê©ç‰G‚ÉÀ4!à)ÀÞØrÛm·»ýöÛ·ã‹„ÃÓÓÓX‚ÐۇǛ¸Ý+ÏP~žPcc&̘ÖxW °N%À¤_Õp5&,˜B„Ec,:cr` ör pæ×hœuáµ¼ |ïýß¿k'výSEAÈ*!ä¿4Ǭ.lž¬FwáÖ“m¥tDZq|•ù¶2µÑX!æÇ¤Ø&ÓF£¸Ñé@NÓðÛ&ÛJ“ži™«M â H: Ö‘ wåv£¤ì玤oC5°o¤¡Õ¶´CšF6/˜ô´)çµ5L¬ƒP["Kìh½AKŒR¶!“툪Ør~@7{žë:õ r„G°£D¶šcÆeFü6ž£hT–#f@CH=W#‡”N¡°cÊmRÙn`\Â$ æÉÒ^fšÑ7lMoª+0NnVΆo®öŸ=Xm¾¸%•eñ¡Ã¯ô! >¢º0…OV.\°7¸Hwÿ¥ó#ÛN} ۘɰnfºÙ_ª2Æ8°;àÁO|âüÍßüÍׯ ơ޴€c Yq £¶žIN\9Q R#Dj'.Ç@IDAT:å¯h`&®tX*“,ã¥æOŽTl4Ýv¹é¦›¶á£?oÚ´i›ù-Â2€Ú΂ùâ'}]Jœ|Àçñ×ÓËÏýÎaA ö¥ž˜¬úûð˜2`€­%l||6õêáWq ìúÇt^Ïå—ÇÌçËõ…ù-kºV›/[i…ÞrÓ,åÚòeÀgJm©±Y,–²-åV;Dåí½-”ãD)-.k†‹X(K¦ · ” Òö/ç 6#í(UsêÊk¤%¤«´çbR-¾¼ÕnGJu%DÛ¥nœ€H®-­[;‘‰‡¼ám€ã_ÆïSþ’Ï7Cè:ÿdðäÔTõÊ«¯X?ÎÚ6bᬯ÷ú¥çã#Rí «“õw Œ`ãËB…»ÍÛ߈€g1îT½éq´Æ+UŠSÎ<»8uˆ3(v¬ów)~§Æ&~¥C·L’®ƒi –¿c'"ßÐw÷«eaÍ*ÛéÜõ{KŸÀצ&&'ð 'Ý,aã³{Cá«) ÓÓÕ𞡊³Ø0š“·š–ÕY´•5Ò"ÑFïÒ©Ùɸ¤.¢QhMG䘩‚d™ZL1 &;$¢¤øf¼iÁH,›•O¶ K“2æ‘åëCÚd,ù³}Z³TÜЦV3Ô–k:M1(-3m?µm¦cçB¢Ç|»ž[ö3Åí5~eÔü9J5Ñħ<‚[uX¿~NÖù=Ç9£…¶êÄê%K;€ô”“”tª—HMœ,l4j ?²¥»¤ŸùJÝåêI?ÅË:¯ 놢Ì=óØì§¦:³ëtu±~1ùªÐ2çSú¶û´Láêÿô©ÓØ*xÎr›×Ùǃoã d•zêÿm Ïp&‚Á7B÷ÎÇ̽=}7Ý|Óyä‘Ã`õa[x\36^ Ô¸Ç2"îÛcòÄØãu/E`f®dX*s‘/X!Î#âšAið·AÏ[zyûÿÎ;ïÜñøãÝg2›Y¼èow B½Nc¥B€t2¬8b»AQ96.4¹ˆÙãÐÐÙjb|â\tâÙU&‰²]pf9×7[ÂÕÿÔæ nÅEerUÆ€©²RÿÊÁT¼‰ñ6wýcÃy‹ÿ¨Ï½¼¡ÔÌæš©áÍ`fzâÆãÕ̾IͶÑv"çÄV1 ÚêVuÎæûº&º‰wÒ½îTƒl%–p[þ¦FS‹Õ¬Sèà‚ÀаAB²A%ƒ—e³eFÝ6éâå&çU\I 6_1’ o‰nWgŠ5µ”ždjd™ˆè9yÐÈ7z`²Ö倄áŒ%i´àAcÁâ)î g›Ü®+*bócY,ã$“¼%MIYµ%]%-^mSœ ÍtÛDƒTÊW¤(±îú2›%’Šèf´[úµAJY±ù«é@‹ôÔÎ:äV ÁúT–wZ_:èûX692 \vG ä›:®ísíüpÛÔÅ).×÷×Óe²ÿ@#l¿¤ó0MlBø˜H Uð±áÌÞð†7>ðÐøêlÅïÄ×™3 þÏ4Ž)¦EÖJQ3´’O½+˜‘ëT 3/Äã¡Á_4<½ÿßsô®»ìÙ»çfè`3 › úíV¥j7•λW±‘B%ó©?xç2^¨Æ±øoè,v ²]ÿ˜,‚å ?è]¸š€_û³]ÿvŽV“[&]„SRÊ­µpµËTv|méµÒBÊç”K²™Û¦›å³TRLÂ]tlpIºQÓ:²©U)?¦ìJŸ¤L 8¤%y’+¦týâÌ hv•dÒ”ôC­Ý›eJŠÉÁž¥Å|1-¥G<¥@TdÓ‘€b1£ àvYKHšÍ8ÚiÓ/øYYt‹…$nGŠžµ-Ÿîך–KÜM?ˆærI›H©ß*Skc»Z&C…pk=gá ßzÜ:¾i¼Þ1ŒvY¹c {cF”±ƒ`¼ÁÅ+ÃÑ‘‘jë¸8$¨…y¹A`¿o)þçíßXþ“d$KÓ®UõìÞ½û–£w=w¥ã”U¤S¾84ÖœOÏ4)×0¤‘튤X@¸bš¸`9‰8â¼Âƒ³©ÀÁ¨ûqû¿ïÉ'Ÿ<üþ|à‰}ûößÄóÅoÓÙVɬBü01U«Õ´““@¢ÏfÞêÇâ¿sçÏUǹ›Þ#¥¤KÁ0°ý©«jTüìÖª‹§®sU†ì½FþjÕ`$–´§ì²iJ/)4#Ou¶Ì ä$*«$–q·˜ì–Ô¤Gù&߸I$šñ+Ö¥F$ÏXpÍm’ÄO±Ðf.‚²@²ñÛ D¥œ"Ð6š„cLC7ñ]Ê3«ë­d7©k½¬% W-Yj1IÀ*£ËÌÒ¨Œ²yЬµiÏ0'eœŽ|Ôà'¤MÑÒÊf€œÌ4JXk…F:Y½¥ÜàuK¿¶–“«ÍÖPk!e^T›%}Eâ(ó;Ó7]í˜ØYmœÁ›YXömåð¹ý%ßÛc[ÈslèÇš€›«þ +\¦úÉ 4m&à¥v·ÕΓ-O6­u™ð.`‹øã?úÑÆ =;`/ðM ¥•çX4OÌe+¶„q±‚q©h9²]ªþråc† ë >ó@\Æ:8!Ð!¾Mž|âÉ;n>|ÓíÔmÀJoj;EšU˜†˜Žñ@óØ—xpµÿèè˜íùϯLYÅSjÌ%k< p0‚wÿÙMˆ%Yë¡Ké#V£¥J=vÚ¹ã¦0µë±R"™&q¥ghA«­ÓF”L‚9O䉈ìtÔÔd­ÃN²Ev‰Èªl[vG:É%å••#À®-(3¤ÅC)(¶(œB¤‹—Î)H«ÃÍ3‚nÉ8^kº|iÝp©_VS IÃlJ[1s€Ók¦A–Íï• ,xaÒ™—l‘ŸÛ‚Kç_ÈZRID+k®­DÌí"kÖ Ô‹ôÅT‚ƒN ÓZDFâ«:fùPP>8{ÃÙêÜ|f4Ö‚W@h?^Röß¼¨Á:€óçÎU}|œkvhȼ–¢´Ä/Õh IñǾ äw‹9Ñ`fH>tãá;ŸxòÉ£m t5VÅ8Ži„i€1aFH¸à«+W:¦-Dºœ@9ÂÂsM¸97ñz;vûŽvìÇÆ?œÏÁ}Þ¹d`ÝÚaˆ{—r:åUù¬ÁÔ,æ'ÇpËhâü®ü¹ŸgÙ~)K{@zðùñ-ãÕÐ¬™š{]žXc5µ á„lèµé´ÑšV+åJœRFóÚÌJÈÉ:HO}‚‹èW¹s6c”Nv%èu­ú–”¸öL’6²qR¬td³fkVI†TJCqVU:Ù¦4$AFI£AИÂIÓAùŒu'8Æ‹è@Ì• °H瘰Յ¥ Äì»AÁ. ªˆqÌw¤œò XùȦÎØòG= /‚,õ 4· Ëœs‘ŒR.ÉZþÎržÄZ£¤×àæä”–¥Ùh"vçnSrEc§o8Uß„Ý1ÞZqTnG,塀¨û¾àzþÜy[ è>Jmòxê,܃äQ?bO¸ “ÄÃc @;¶o?p+Ò!¢‹Õ8ViÓ˜ÆÜ‰F«Â 3Xîlüv£7„.aF®Dˆ™+aâ:˜–`ÆrDI‹}ÓN° äH ’3‘[£R¶MGvƒŒ¡kyôtëüMéòSk¶d¡¶“ÄL¦-_9Ú5ÝdSm$¡nÌ¥®‹:Wnb´Gé2’$`)g1‚q¢ „¥õ¨Û!Õ¦GÁBbÍv…V.|zשêô gÜo(‹÷ÏîEkþ©|¤³ßÇ]üR §i@ÿ¹ñë@]p(7ϼ„™’1«ýxýÎw¾óÆ)<Ð$ Žq„=ƒGÓ‹¼ˆfH%ê€p9?ÌÀ•¥ûJû*$é‚åö¤i¶¤¸-h"Ðûž÷¼ç¾íÛ·ï¶F«Úe §jR¥Ö£=$ßjW²óq wóøöýŸŸ¬úð̈ö5è[g— 8×´$ùÉß#þÞ¿-DY+¡­,% L£²K>}± šÙ€Ü¥ÙJ† û²ÕQ …\ƒŸxeú­* "‘ e¼€7IHÒ_LJ<Åmf2-wôŒeöb€ì+^LÖyeÄKZ;©´ÝH&x)L<%³¨\KVÌrA6"Ü™l¡˜}$ƒm™½L Ö3-è. vÑYTjégó")]GV*?<Ó7S oÃBm<~õž=3ø± ºÔhxЋáãÂ_ MÃ¥ÜcÔæBpcpL°qèé ÍÈT±#áHˆè¶íÛ÷¼ûÝï~W¨@9^q@Xcc&*œ<Ë*báŽ9O°b&uE½eP +'(–ƒS–;-ÙŒ wúî¾ûî{¶lݺ +z×Ñû8>¦ñŠc—_!f-ĨᔋášÔV™hÌÀåÀ*˜t#.X‹x騈óõŠ>ìÅOùÞ÷¼ûÝ7îÞ½‹¯]ð=ÌÝ+UêõˆþœxF"lL—±]ÿff«Üþçû¢6¤\²©uy£ó7Nï9UMlž`6V}H^«Ë‘Ê^Z F~›NI3¯tƵë•É5Ò¢>´4·;âl"ç!¦›`ðÜšKS4õF—FÝégcAJD£%æÏôߨMØ®P¥Ó´4MeÌÈF¤…pÔvš"ÔåD¬v®8Ê(ÿŠÝ†ÛràTfB‚ lü(áDjå!Ê`yõtj¿7³oP-µ³%¥!AIÊvL6ò‰)š%•°Ñ‚r ²Ig9é6u2F¹$KIÁÎ'…ÆóPȶE@måƒX,ìtô ᕇŒo¯NîzÅßÊBYX9  Uç†@cXä=77ëß{ñÁÁkîÕ…£êÛØ‰nâX©l ÁL;òZ´Ú¹kçá÷½ï}G Ñ;×È9nÆ¡ñŒqÄ™=3œ³hä1DY§¼Æ_fðµe¶´SÒ•yÑ£S“'˜Ž²ƒWÿ˜µõ`ßå-ϼý™{¸ó_ºF² aÇ‘’W™¯ŠN"©î\æÜÑ ‹C¦ª“§OYÃàÍ~Z&MHï‚?8·m¬Ûþ€Æ[QéDÊÀꊖuÂ7ò]FǽҨƒÈ \¦•ùÔ"“N/ê†2mzFSjD·ÉQŸ!ñ¤á´f¤Nz²-.PRæÆçª ÏáõPZ1©w/…8IÊÆH#Ý¿س™§‡åŒv¢V„Ûü•nßÖÚž$Ä#m‹_Ò¬\‰i_+¤ÉdD²ÒmÆI3&è^’µªg –põC.à5Íh¼-½dÞM¶_û¯“|b5ÊíU"c!¡Üéäì&»©®I–ZL ‰«LB[ AÏžœ#I!2@ aI-³E·0u]QdO}ïðÖ¡jtëHµ{b·-´Jä^=9Àƒp*ëbÍÖy W7ìÚY b÷ù9lÞfœ„SõzñÂzÆ¿ùE•Cœ0ÏgêèØüô3ϼá;ßùîÉ_ü?Ô‡»ǘ!Îcœh´ÄgÑäé°”€3q#.BÉøµN˜±¥‚dT0ÅÔÌXÎ`L§ ïÅm€ƒnÃíÿ{Q‘ƒô>üV |ÓážD‡!KÆøü©ý.qÞþ÷ÛC³øï UwÏ´À+>ÿŸÆû§§wŸª.\$yíÖD %xÙ“‹Èdqö¬©SÜtK½§rƒ&dÔ¡ì‡ÓÐ#º4·aVÛx–g—©»Ø¡~‘=þ?øÄjÕuè’=pìÿ¾³ÚöÈVÔ]r.£ärCU‰Ÿ«¢ K75ÅÜHŒtƒC:Öñ§N>ë `C¤DËéwÈJäw ­›`Ãb­Qš[ §ærdêV.Är L¡/~uç«Õ¶©íÕàÜ mÎÆ=aüK’ŽðN/?ðv~oà1>è㧬Õa³"­÷7?9Ý@ÚÃÁ;wÉ<1ì14^1Üx×Ñ»î;|øÐ×1厵W8P”cqšÐ0Ã%-dŒbòKÉt(‰ÀŒ\ÍÀÌ1(¬B2–3—‡Ížt+wv:tø.| ª7]ì;“ïoò\ëÁæo˜i`»H~íï,wýÃì$×ÎfžÐþ€@eþœÚ{ »=pÂ;õjøëãǯI¬¬¬Á%+¢¬£Œ¤6;m´ÔÖ³Å6«“,Si‡»ÚˆâêfÓÛ\£•CzÃ`›áhs^Êz£9éoÖ¹ñ(ë ÛÏf‚~°¨®’åe´”¸DÓNlÛÄkÛ5Tj°Í~ÍuK!Ï VD`ÇSJ©·Ù-i%í­P8u¸ý?‡ À+Õ…~‹Åûlµ ï¿…“€yÛàÂÅIìˆï Ð[|üH¾sRR$ïò³œ\†xÃGc¡Á¡ƒ¹ùÈ.¤d®ã(Ç5 2ŠiJ@ Á¼HW>f®TP†i¯­0‘Bºéæ8 þ½X¸1·gÏžMozÓ›îüÿÙ{“%K’ìJì…»Ç<æ:=•MP’B;‹B.=H^þ‡» î/ØüOýãDŸ¿(ÅÀ@#Ô"ìðÏß’ò²Ø§·>Ý<¸u_ò”Z*î5Ø©½;ÚA½0ð5|Ãl÷ÃOìé!@«~ÜW„Ä‚Cv[i7œ K)èÌ­öØJ$cACA@¿E'¬õ |(Ž.ŽÐ™óXdÿËúñV>ym}Âîj:K)ìÒ¦V¿ƒÜ>ÛÉ`%Æ’DšM§”²¨æ‡ò†)Òa¬¦qÿzu+¡-›l$ÊG™ke5¯(6úñÀŸÝøtóÛ›8‡£)<‹³7í²}èÌ'’öégx[à—à1¶ô€Zè¡ð踠•Ç4“ ÇœÁÅ@l€ê)4ääׯx+íÿÉÿüÝwß½þÉ'Ÿ<åüãyÀZ­Íy,'š9y&Ë‚u–)g%¾ÎTÃÏrjÃÝ(Oú’sÊñqÂ×7ÿößþWßûñ‚'ÿÑ !‘Œ®"…€GË*E<“tÙ¹¤™¸à üí?:ƒÊÚ¨0å¬Äg7?Ûüë›ýwÿ¯ú§Ænkrï@^iaÎÙKi«ò@7WÆÌù`yüä×AªøÐtùè˜Ø2“™êæ'GTx6˜ •(AÃ’ƒÊ'h3$ øÝõE:W.ñàCÉ Ð'Š}êØ/$SÛú¥÷WRHF Y‘Œ#"]Rê ´§—:b¬@:…Ô¥Xå±RÜM¾Rcƒdve<6_“å½ýÌv³Mቈ†=½œ¹ú#«öÏwÿeóéõߢ™ü¨†„]kRž@ø!2žó¿À@“?[ G:í3‡^­¯J=/d¸Ãkõ|ó£ßùÑÏþò/ÿòûpsÄ Œyn¦SN;oæ!j)ý6~8³f§Š­*n+Ôòš“6O׿7Ü<¤Í—ÿö³Ÿ¾ÿчr ÞBðóp¥»ì´ì0fÚŠ¼­Ú f‡"=üâáæþýû›'ø€Rž âXá'ìfd÷±à÷ÿiøWtí*•¯½SÄ#1Ó~ÆÏ< Ë ‡6 -óðBҚ݈h'óUhxɽ='RY·’–lnC]J]—“\¦öQJuÉAÏø]!ð"+ú2Ñm1w¤)`”ñ‡>ª_ ê‰Ó½`Ëð•>U9#À€l¸`… ÌråÅDj'Ë·M`°Ÿ0,d ݉3K˜nÔ ˜õà'ãûr§¬²º÷›Oob€¿¬9>ñkÒ†ÅôiÃ{½xå7¾¢~j-%ÑÝŠñ|B ’¦š‰F6yÿý÷úãŸüÄ/ò/æIßsç>Vpm‹¹_ôNϳgá_Gš†ÑÐ07Øp£«Ü cŽ~øƒ|tíÚÕÛñûÍ ?"®ïfØ‚¤ÉëØÈÞ†C˜DÇòR xùç3<êÁýºC”…).ÿgÚwÀâòÿ““\(ÐÿÜJY¿ü»ÅA}P;" %¬7ôt6ºÔmÒ\~®úP¥ ¬ˆjŸ¯©©w—NVö~‘E ‡Ìúñ˜ýá>‰"G.zŒ¾Ò_Úìª^x(6Ï~Ãe•ŠÎbºHgÙįڭ-ºÓujò»z…¤lO˜çæßÞÀ¯³0kð\®‹ýj/v\$ªY¤/éÞ¯‡¸ ðàÁ}=#@:Ìí¯Ì"Q‹!sIÌ)¤õï¢Á7ÏysámÌWßÜóWü-c‘ÜfÞrU z§™·ü\9 iWå¬c¾F³s|ð×w'Gøùߥ_þò—¼ûÞ{²8G#È}èg‡´ ~-ÃNÁõmÖ©ó ƒ/|ôÀƒ‡ôÝTò_kÉòøýÿ¯ÞøÕæ“;ŸÉ-1ÿ:å[ÚÆøœš®¹3š¯‰Wdµ²É¬`FÐmØ,£o«/Öfð•Î[ rÀÚ¬å(/>ñ±âC‡êWžãœSU$)½HnÙœ3ËOÅ¿¨€ ­•r£æœeZ6Óäµåw' ÒñŸÿÙŸÿøÍ7ß|·Å\¥¨‡Ôs± Ë*DÏ$}vÜ«ëâD ÜÃÏ¿ÐwAOŸ<Á ST 9Ïä>âÓÓoòíSɾ²YœÖJõ£ÙE°B&¦A‘Ø™—8â­ÂÃpZ²RBˆägél¿]AÐ<]i+ÛÞ¥Ó"E“óF¾8IËz9×~¹ õågËZá<Ñ\ÜÀ`ž/­.¢Ð?ê"D]}ýÕÆ¶uYrhÁ€èý”´•Ì-ñM=p6‘^vT›š&£V }™}ï¾i¾ ¡6.­ G² L—Ž|*›^Dº„0³MJg»©¾½.¤l3Vg•Ô%Ùl÷ª Ѿ£gGz?À¿Þú—Í£ãG?Îá\}غ7Ûx„ãóñãÇú Xwñ3F£*‚™Ïž%—¤“ÁóçwïÞûðÇ?þñï@£9­Ü Øæ8èXç½y^„H‰zU'sÓ¡}{ü¢Ó\iú¯']¼<°Á¸£÷ÞÿCö%dO5Œ5’¹còŠ<¡Þ)$Äð»!vø§Ÿâ¹Ðq£&zŽ#„¥2Ã&Ï'w~³ùâÚCºx¥“#uªFäÁ£˜®2^5M'0Û1oi°F¼…‰L–ܤinÖN²!kSrÇVj¨czgæ‚ú©R2ŠÇú³Îƒ$m)ër>¹ï"/C [ÿЧ㜹úÏ€qðä¿HÂBjxHFƒHŽGŽ'òÁ³f—ê(ì&‰ÜŽvFP:h¬XÔSk\iwÞMVÛVÔ{É)6cöZ«€Ï¯~Ž«ÿºyzé)ÂýÈP_ñ>€£Í“'6> y!:‡QýR¶]í‡,TÉ'sŒÂ=ņ(âÒÛï¼Ýî(ó+äIŸgˆ¨`Ÿ;ÊkšyêÖdÕf/ýu,jÅ\Aæ¦])ò€äOøèß'Ožá‰'ý×ýŸÝ¸qã` ^ѵ>âÄÏ/þ¢—²·À˜‡ž+<²\Kð@>_Ð@Þ9`.áýÒ,ñMSÿé­Ú|u9žü·zBq¹¯[>Ÿ<ýÒÈ™—ª çEêêPÍ<¥+²…¯ÌšaÍ~nÓ–òT9Û^¼°ÍSň*¬ÉµIÁº‹ü°Ì1:d¥/²«ÆÇì`ZJ'6𲤀i`ónI+ ÂxkF©µ;r. §ón¶ØÌeL|Lq‹ ¼4‚gxC+ÓW'_mþñîDìÁ£ Ï3Š%çKX]>Æ×¿<Àa>޽ŽgàáÎÿ Sní§á´“ È’ëV T~ÂÜܸ~ý¿ù›¿á“kOp¥oäZç9ÏsŸ sÇÈ;úΫ¾Òûô» Ï»¨¥óÊ›vβæ:€Íi|ïíßüÑýT¼¥O÷¸Ó2su;ËŸúWg¹#¨‹Ä¾š9ïüòä±~÷ÿø Vˆ\ª èj‚©¥ÀæñåÇ›½‹Õäq\|H§¯_VšáȘ¹¥$së¹0Ž; ³ÍÌË …´™Ê“.\ôOàª䱑öúýßûý7øÐ!¤:ñ³øµ9±Ê£Š´|ÁÉ¿`·SǶOünËuIó»ËŽÿèÿðý·ßyçMÞ1»ë÷ÿºC5^S|hüñ?6>HAùÛòؾÀMúއ!COñˆ`ŠÏqóÈÜ<ò]LCHv`>Á]ÐÒ޼¬Ûo“žð¾ °‘%MíºR­ í¡ÌòJ…‘ÐÀ€6Æ“æ ‹?2©ƒâèâ+€ÈY÷lºå˜XÙänï9¦‚¿Úõû¿øäÚ¯#rh¿J46.®<Ñâ¶åÀ±!©SâÁ6±Ÿ Æ’ÓØ³[·o¿ûþûï¿< ­[=7VýÕx{xžT+g?”1Ui7°æ¤ýˆh<=é…3—_|„ï°ã0e©|…L LüÀÑ/Ò¥æºË5Š)…UERøÀãÍo°Z|t??2îUÜ»G\÷Ï`#U•ÞârVØ,k°”ÐݺÔQ›ˆRJ ;жŒÜ¨e)Öt<0K!N.£ðèâ @ÙY¨1œòÐ{§+)[W4èøè²Î§}[TÈ}ìrâìXˆŸùb±@ñ½Êi:£ºÇ´«¶SkFó"`Åz{-VÀEÓ]¼d$Ïá|9п^ÿd<É#!Fm-Bó€dGxà3]x†œsŒÃ©ù=¨»úrîQ‡2p¹µÅ‚ø´Ðüƒ é7ß䀕Ð0ÕK¥;Š.¶»Í¬,¼l±+¢îØÔNåzlª|§ý€üvÆAÚàFÀÏ6_àÜÎþâÉq?CZÞ?Ä|Á›Å5Ù·þeSþ+ùEó/R÷˜Q'€áÇ<†·Þ¸q÷¯þê¯>‚Šóç"~ðe4=’fª¼e”“®Ç‘le’ø ËRâËØó¢¥Ö ›ºsm𮸗¬ú$=óÍpÒUyÒ/óyR¯F=yNÿÕu¾¯^Ó‰‘‘ÁòM€ì;¾+†ù£GXðHz=0Á¹õ£å€“Å€(ðø–›´<€â¼†·~?'~>PóTîó5i¦ÚC!‰ý6yÅl¥ÏrvÚV`•“^ÛXå5×ÄOÓ÷¿ÿýðö¿Ûà‘8%;¨ %i…4vÁ¦Ô¢Ò zv.¿Bøò«/ñÚGž Æ&Çå7úÉÈ'7ƒ—ÿà­PL¬éEêP<ô ÁÏ5'p„ $:U”&RÇçÅðF@ýº˜ÈAèþ£}ÀqcNJ¿´Ú¸rõêðóvÎsû±Àu.d'™¯gš…Tš|MÔ*³á~Óµj…L×Ü^]–u­¡ùt¤#|?é½÷Þÿàúu,´‚Rc#¬ZM±3Kª!שþmæR@Ÿß×ÃÜWX<þ4;ŸUŠjĉ".=ÁÄÏ'Gñ}ÒL½cž»hIV•M¬iæ«îZav÷9‚‡&f_ùÃI>ÀÙ£½ÙÇ`˜xUT=,û… Ç …§ jÙV”ü„­ã¼˜DJY$/®ôèž…ŠpºOúÙz€F«O³”28Ré‡'ûõ>¥M”$ë6+Dy}Ñ'møiod* å»^ÎeÒv¥¼f[PÑP ƒT6_ëD˜LõZ‡NÒRk†ò-|ùs÷Ïí¿Á€ë‡»Ò&‘Øiq€ù‚ÏàB€)`éI 蘟²Ûkó ¾‡ÉóÍ5,Þy÷Ý8Ï¥ߥû6‚gò\IZØÌåŠB$ÒÞ$È]ÅTù*] ZLB7m7¶îÊ1¯ $m^4‚©üÊ•+'¸7J\ºô„×íÙ>–Ø6vBFU+€äu¥FšèáÇðÆ?­è`Ã÷è¹Ð<Èy¹‡E°tO±:äOõÝ}¼‚)¢púŠ«¹ˆÅ¶ðóÁ¦1ÔŽº% <‰j'âPÏÕY|*\8’&[ð{ë°‚YLôp»­”|ñÀÔ·§e{o‰š|t7Ä}`²°î£bRDÁÈjæÓUÉö#,÷YmbñY™lJAòMÜLùNýµM{!÷Æy*ûefÑ }½{í_±ÀÿØ@¾Puæâ”þAá„AõÓgOµéÓ¿?tjQ‡3,ýiã.®æ!²1+Å>8½Îþ)|_zãÞ½8Ïväy4«¢90ió̽ܚˆqʙݳ"gMsÅ\‰9gÃŒ­:5˜—C¾øâ VúÒŸþ韾{õÊ•[BÈ2ˆÙ$raiTqŠ8XÕ#¡9'{t$&¾äAÎÎ…,Ü‘àÙìxüLðò—ùúHV˜Ø@Êß+°S|NSÏlßj+˜š¾[4jÐððÆ‘<[.FJîRš`K”Ùbò—“iWñ©cÀÐÇ]›¦a†ÖŒò>6 ªY”²›ÿåÍÍïþ¯?Më KÁò4§œÖòjÞé¬çÑu6'´!>ŵK¿QÏÑk”± kºÆËŠp“bH›¹Ê\Ê\ŠK­å­Õt\ðšU} #ÑÖ»£•BG°sÏÈ/@¼Aw¨£ ”PF0CKqàw\P<Ö£¾¥’Q¶E#{J«&›Ût¡$‚)Kdcj›ÓA«¹x{µmx(^†r—¨Ž•îÜ’A»¥>¦23~ÐÉfRÌ(÷qUß2xÄXy¾yxù¡n|ó±e~ û–‹è’æ|ÁgÆ\¹|bŒið1 Oò¸S ¬ã®u(;ªeG1¯^¹zë/þâ/ÞûÛ¿ýÛÿ€wÒ<ãü‡{ 8†ãt“<åvRâ²dŠÎ—γpÉ®xå-sNi6È% 7B#ЗpƒÄå_üâßÃ#W+D,zŒPÝЙW TK"èÄò†>ß™wtreW¿ÿбKqSþøÑo¯ÿ¶;ùŽP+!ÝÒòŽTì°ë’5“ˆ0Q¤x$^Ò¤›•„öF†tð–Öó¨ea/c8¦4éɶIó  ;« ¥¨k‚Ç2šEx†»Ž2ÕÊ¢ üŸÜ9ÞÜú£›r “$+ÚF¥þ “xŽT&îu‚JŠ2^¢9¹15?ϘÆgù<ì˜x¬è̺a¤‰]Ф։4@Kçàâ$[ÌcDhX6}„µ¬Ð>ý"§™¢½4 H•ŠîþGi4cV ¡K§Ã@`…(Âjúœ1©oÑ/„¹\çaJ.ý† Ê)õrW4;›Ð΂ïå·&Ës@màÂÖóÓ`×=¼¼ÒO®ýfóÞWïm.?¿‚JòxGk‡°pž ÆÜÁ Ñ'—â°Ð0ÈØëx!V]NBû$þUG¢æšã£+¿ÿû¿øøïþîïþ½Œ¯¿ë ÎsY@Ò5äYú¶´O¯Éx›±ås¡–ÏyÅ‘®›'}ÚPÞŠ·ÿI÷Ö[o]Å;ßÃz@‹’è ÙzŒg;#Õ I@  ìð‡ø‰ßõÚ$6¶¦A“˜»Y_mWè•oA…óýEÈìÙ¨U[SErdäè€Í\WóÝ%¶‡]Œ.Žxù’º9!Š ¶”Y%{Ø™—”ÃqFpüEÉ¢‚[«½¸ÀÓOÔh¬MèsÊO/™%eõ@ÈwÚBá6ƒKy”F¹t%·3-¦e;@\*íYDR³.Û¬ö ÞahN¹h“­Üö8˲­Ù¿,2ý“¤F,v½M”ä–Êð@‹V¼ig":Zõ!›Rò)JÈî± ²š§´ WØG'‡“ô’Œ²,²Õ$üp_ ¯§¥WÜD™.¹8œE+¶ýR’Ÿ\ýD¯VÕ¹sœSšãŽ_ó¹|Œ;µ;èù±<>šçx”2ÐÁ*Ã1£pãÿ otë­·¯2@yÿuž)6ϼnÔ1QöB =oÊfÊM­X­¸iMö  @IDAT@º±¼BúøÎ;×Þyç÷ Ë:Ø09Ö™ˆ °&vБ«Lèàž`bŒƒ€!Æ_աÀ٥͗xòß}¾5*Sv«Ù×+_L‡4/ƒhípYvU8’£Ô1­€‘£Ø8Cf>ºi(b®QŠÄÎ^ȇŒ’Æ¥Ð(à¡¢Ö!QNX„´O,½®– ¶i(Úb4 *öò˜²(Õ% õ“ÉP'VˆŸ%lÚ­Kk9ä\‚°èæÒígÌ]Žóî; ²¿˜°¥…N­ë·Í©Ò—­š,z`¢.Œá`Kƒ,p±_qQ,‰º£èW´¥¤,}¨ñÚ€ïu¢“æ ̸Ï66 ˆFwT§JLd¾_k0;lQÓÉw/4©Á~hÙz¥ þe=gÖz}vå·øàaL­_bIÎy!º/‚Æ€~ –ó çÿÕ¸Q§gÓ0\¼l:ÐãçæWÞ}÷÷ïÞ½s hNõ!xŒ¦çHÒu; êÖÒ6ùV…­*¶«óZ¹mrº™qµÀ¥Û·o_{ëÍ7±À—.ˆO$ü{†?Q^¼˜‰QÎ8ƒdP±,˜|#…(7ö=}¯xdçò¦€,ß©†_]þ W> »ïÐ>¢95¸ö&Uí¤1+&»[ti¦²fßk3ˆ‹«í$ÇF ¶Yÿæ}xr¶o©[›C:c( Y”kÛí8ŽRYE…ØlÌ¡ë^KeAtü Æ@/à€znj°‚åö²G}Ô¿Ô-Ζ­RÖU°‹&œ0‡®©…'oèƒ@GÝ©ªØfSúªËä(üЗ’óäʲ`•"*a· 4³YáE^~f¾:ŸéÛCl·Ùí·Åÿ >ì-¦?fØâ"¾ÆÜñ 7Rì'|j¾áG1ñG[Øqì¤Êœ°Ðh4œ\~ã7?à^øYY²Ššiº·Ì4s§ª«2Ó;óÓÜÀ‚v¥Zcgùºà%ñׯ_¿|çîÝ÷Bþ“/çóGDÛ‰²`MNƹÔäùf§GŸÏ»ÿåÏXµŒŽ¦Kzö?ÙÊ´¯]-~‰tŒÛiÓé-²„ƒbCãÛk•’.X£f<øÁ®è»óÒ+¶ñ%ò@Œõ!Šq³Kbd§“OL³Í6lÓ‘§8zð¤¢W|E½Tì]/ÉãO~Ó9 ÔFÊ2k¬'ªËHIÓÊ ½}U«Åá0~ÿn4ó°Ÿ–£.púÐ`ï&@¤ëmê‡Îs[dgñ]½cCl-C¨ˆQ¾Y¨râ˜j#C•IaÖÕUe%Ìš±Ío”Ï}F·µ-ÚŸ^X•t¬þ¤‹4R2f 7ŠÁµ.4Û¬Èvíy5ì½—.±JÙæ/O¾ÔWí}ЩÆìæ9î6›'XðkäTAGQŒqJÓ1HRÆ¥ÇBâi‡ß£cqq„»ø©û Ü]¸¹ä¯ÀÓt˜Sf—57 ÈùÒiµ¤C*@Œq¦Ù@&æÜt ó>øàÞÕ«Woð ÂüÿœOkò}ƹ‡8N¬àùߢmœ,pÇ“Ílüä†\øâURŸ_ùn¾þ7âQöc Š"‚ìPK1cã¨H›%~†÷N¼ÊÞ’…z+‹(ÊOžê>m̈ðÑN¾‰§4F‹ñ1R{uÏ”ö‰ÞåBØâ‘-õ•k'¹Ë‹õLš ˜‹£o9žÓŸuÕÀ â›Ê”(&QC¢é)¬íÃUøÖŽâÅ‹”§u©×l*3èi®¡]P„8dj)´ —µO)‹6Œ2VGís‡;ؾTŒŠšÀ|7®᤹ªØ mU9d”&˾I»BýÒåZúdKÃáR·¬ë –Î ¤ ðîšÝåfy³}Š_Öìþe¾cc#ޤh¥÷q|Åþé“gº÷¦Es..Of Ƣà96¥“cœ!0±±Ì+W/ßüàƒ÷ß  ÜhÈÔó!Ë M‹¨ÎHS¿-¹ømúU¹Û°ª<…pWe©«r™|'òÑýû÷Ÿ^¹|…¯~Gå1bŒ½Bû0‚W¤†I®AK š?|†Ë8úôOçìpnôÀ¹W>ÓODy-XSå+MÌÌW;éïH¢vâ'%ØA20ôÙ}«„…~B´3ìH‰¤© šþ½PË º àdàF§ª$wÕýjÀ6­Œ0ÎzâIƒ6žn»¦ÏÆÃIm–…¥­©çù…ÇB?JKY–Ê‹rä dÔÒ>ƒskGL_ªi\äôï:’N%¸¤Ò'-cÁ72ÔáGØÒž¬;±ádá½ûˆ«;]Î1Îñg×®}…GÇ&9ù¦Ö‰ò¦«BÓÌ'@e£í»üQZ]¨¾>y©•ð6ŒD¡ Oâí›õ™ü¼jRya÷à]ýµ¼úZÓ¿ä²ÏO>ßÜ¿ü)&¥˜–7‰y„^”¾Ð#i oÑV_Ï܃Á,sú”AìDC’£x³ùþÇßÏ8þì³ÏžrÌÍó¤yG4*œ«lݶ|'Îo3Þ'_sîJ2§7b–s Ì~çGwÞ~ç·âMK*2†¨"®cÌŸîGàS¬@+ØàNŒÖ)è~ÀË7>˜dâZ±—³F®|®ËBQ²ö¯Ô.bUf›jšùŒÅiÓtNÚbî·®Û‚Oq³[CØ]ÀÉZô-«ábÆ Åÿ(ƒí,³q¨€ç$´,£J>a5µ¤Á®š`ö¢’§YÓœ5b‰I8_j›áDŒÈX”Ì×ÃVéÁ¾YCÝèDŒ_ÝX8£ÐÜ5×k²¡d0O^ROï­„ÁG2ÊÅìµÛ«ÍKló_,‰:¨KwÅË)ÉÕ·ùX«õ:v8­C¾=iÆï!p€KIÇ^9¹Œ 3N˼ P¿@­Ù6GBÏ—Àó ¥} \AÇŠCN#ûö[o¿ý“Ÿü„ï¼áOáYüÚ\ž§?Ö"kx¯S-Y«æ-‘É:Fˆ‘ಬî+‹Uì\³p¶Š”Œ\ Pô¥šõ¦•uॠåÍì¡™Žç€Æ±Ìºµ6ÖgÞý 4Ê˦‰}ıÕJnÉeR3Ö6VÙ¬kÔ+¶‰²Ö궦 Ï.l%W}ª¼Õ´ ƒ^` ™ëfù2ï®éRÿ*JøÀgXÄ¡Aâ^­ÌãÕ:-°h}»]Œõ~£léçýŒ"ÒþÖÍ[¸ð6žzË7·×³ ó<©jeõLƒ}1‰…½ˆÄŠÍ•«2ë¼PŽIžò£{wïÝBº§ƒ$ÄC3êE^‘Á“‚¬åï–˜üŸñ!eŸ Œ:³9»„ïã €¸Ó3d_Å£¼­î:a• Îø™/~š!<3Ö'ô¾Úº“t‚ú0£ÿàTŽia'ß14Æ Œ­Ò©Ô]G¶HпÄ<>=^j †ZR²›ëD)dö˜Î‰§-0¤‰ Yz”¿ ›°CÌa!kȉô¾Â«hp*Kr©VTu”go¬%æé7è*áA¤b,TYÑ"[«•“¨yêm“±(Õ,íöPlj™&]BaçUWJRIŠ©køà:pŒ/äégr× †šå~ HH 'GÑn1Y'#¨· U!¥mk{Ô ˆ¡£Žmn ±lë„oþÂëæÈW¯,y„¿ð\ÿàä¾É&û˜RG¸e gLøT«úzÙ‘`ܰµðÅè¶©>¼¢‚ ÑÍ›7Þ¸w÷.ßzË*póÜH𩿦C3êfÙ6Þò!?t°­t6ë,³|nXãñ݇hü,âÆë7Þˆ¢ K…¸ï2€c )Ô"@Qhð¼ôÿ”+7U0OV®,„~À3¼'ú ü4D•^!¿–;…¬¶Ì½5ȵ¦ª°Nwd§º¶SÔrëµ0µng|÷0P“}ÙŸpƒ~žîÂS… ¶¬'þ9òÖSNJÈæ°[öOíMÕ“­e1õ*Q²dIaÏú±ü^q é¨ÓÄÄÉ©LP²žc{ fõ0„m¯OZ^ˆ ÏEÚÝ“ZQôÚÜ(³ag¥Ù³ÄÙ¥l’éö   ¤®[á…µÁŒ ¼}H;à¡)1îèN…Gûïò½Ôl²ßëðÛ|qüú1 ?cÌÕFæ¿®?×/+ÀôX¥¡±¾;‰é{)7¢Û¦¤°KMf+äš ¦¨8þ·•õéà†k„uÝw¨ÀL‰:mÁ¼[E-87³]“…r4h8—ÐZE£­@“ ãF+n£ªørÃ]A¤ÌžÈÚh‰jª…‡ÄfèÅÖÎ)‹´”XSóŽJjuõT-NGþÝäÓ¹x)ÑŽm>Å/báã ã†6j 'Ï«œoøurIòœbœ8%õ Z²¸͹ ÷Æìuéèøäæ­Û\ð«pFÖÑ5]s:s2ÎÜ¢…UTŸª+x:(lEõúmTð} 3Æy^|®¹ü¶d±ø €{OîiŽ`W¨¾=H½þh7ŸM§9 ÒH 4&‹>Í©‹LÀ$#xÎiœyð¹“óÞ%¾t( èàµÍ˜tLöüé, €Z¹¢¬‘e•vÜsÄgôã÷ÿ·oÞ¼y‹áQlh¥É?Vjò‡Œb H#$w"hÎ[\Фï&ô<î|› ß Í»A9^Åä“Ù™êž1\µeZ"p*i¶ðaØ H‡Â!x—Fé8S@íˆOO£˜Â¬‡ýͶ¾_¼áØíþ9DmˆH/ ;21!éåPÜÛáz<{ü|óè?¾Ø±õèÿ‹Åj‹Áw€øƒÿã÷7GX¤8Þ¾Y¯öQL¦½§zXª.|:‡½ÇÃ&?DdaöØÊn£j£çIC?žl/h„Ôˆo*W°£¹X š©Ú†„û,Ùà0 óì{¶m<ˆF‡¿WqWîŸðÝ/ˆ :ë9¾ VŠ•šÊÆ"!£ ïpÌ=ba´¢õý†‹n7zÅpoÿèG?¼õ÷ÿðZ¡iQçH:H/C^e¤YÄ™ ;MrÁ´©…Vš˜mí¨c¹Ú~øÃÞ¹výÚæÎ=‘NðÎZ!ä‹d ;"dTéîM<¹1åºTUP§ó À¼ ø‰Ô¯ûŽÑáÖ†Rmp†H" âŒSÆ´BwÑ*£úXe6#+-ƒÂRÂ*fò …mBJ\Å&*”moiëyÕGžàÏ>ä§:kª^˜%ªyŽ­ðgL8ò°kU¹ ÎE·õȪÀIßú2kÈ‘îÛxAs)—c­ÙA×=A*†Zn¡ ½q=êÞvY—4´}G.%ÔÑšÉyV,„ó~rÁ1gJ×aÌö­œ,m?|ÅÃË'z|é1®|¶yvÄOãî«hÜpšáʯ\@VáµÊϵFmÕ•»f·"󤧾ÞùÝ/]­ÒºYsÝm:Õ”;‰p¼pñ1ÈÚê>A¿,)$´Ó˧v–{¡<][ž±Í}b.mx%š²¡Á/û,ðÖpa';”1¤UÃ9C,·ïîoDú¸Y껇®s{ "PÛ±ÕvYVÕî¢'ÿ®€MfÞò—(÷Ísþƒ“|$pÖqiWŒIã$›Å¯“5*8¹¤ŒA'[0¦9·FDg¼À£ïo}ðÁ‡|@M,®nÔ‘ŸS•OL+m6XãÏòÀì§nÚ9±¤™,sLɽõÖ[w®a%qEÐDP…vð¿4§ÒVä…›l6ì4º}×&¾sô3X|º‘l<"Êb%_ÙĶÕ4óеæWÜ}Љ¯ŽžG®âH—•*xK™w{K „”Š…=e5 A÷b­½1`$,6>€Ó“|d\º=¥±ßÅžª9Ž”]¤ÓG ;.N©%úì#.ò‹GBcú.Bɸƒ6q²™º«ÛR1)é.ëA2’¼€´Â|h-MpÉ—Ed&}h°W]‹ÉDnŠu©Û¬n×ß¹5£lÂÓÔE8ó£ñËÁ¡¾\@ÞÇOõ¨jŽ Íþ¬gü9$ 4'qòa;ñµq‡{–`e1×8£A$߃¢¯¯ æ ˜ÿx€ÜêÜV2çÆ0¯²d÷f´ëJøi¯Ì¥¸2Õ±+Jì^úÞ½»wðL䬫~È`Çb ºŽBí5 þ±}Ïõû<þ7ù¨ªÿQ#´šåðòÏçx“ž ÍÚ¾")Ú{šÊžÞ‚Þ Ií£¨“ì¶S-õ¶6\¦âd‹­Öîà)’X¾»¿~´ÛkñBÒ&Sø“'I¤Ñ£ct Ÿ÷èrßñ<⋾SÈýÖ—qBÍE碿¢ot^‡Š'tËj({/[Š’PHÈiÁ¿H¬ý•–ø.hàY_Û˲1òFÒÛC´å¥¤áé,7ª‘l\-Ç’CòZÀ!ø0sÅ0ùF!hr¼€ãs@¿9œqt¯#G‡8ìlV| µÕ¡>ÍgopÜh윀”´&B•÷“>ß\¹rùƽ{÷곈s‘5¯rûb)LÄ9m;EÖ©+;WÄr—CžtÛnáÇÄu i.·[,Pº/šÄwoÖˆŸl›MQMâàöO>>Ç% §—¸Xø.¤̇µ4c0Cܹ‰™N€êŽÞ=½[\hsäŽë½)v bI\‹ÐrßO– ¬'b•ìŠð$;žhiÚðµ1²øouñWFöþŒÏò—­µâ"?oôi*œD¿³zª´¥”EïR='ªŒ釔çXÆÑ/À¾ºÓ,¡ Tô/š"‘ÞXæASÞ9ã ÝË J&ü¯iRÅe=ŒÛkeà™òˆÜ™L¿£'—žl>?Æ[`5ÑsJŠóDtMŒ5áÁ?çNC‘‚ÐOÔ)/‘ê…hîšüùæòÉeÜxÃöœè‚™{‹"û~›¼#¤Xè‹H®tõU+iz»Ÿœœ\¾Ž»,qr"­UVѯ0ôv‹žª`¨Ž(jøa‘Êx€GBºã÷.üµÊl¡CɸÏí/ºYE^êz¬€ «dQf+(4ì¯5Œµªus؈fC·Ð€Ò ÛJGÿƳÔÊ.Œå‡z ˜;¥Oø°ä"?GâS–Çcþ{ôH—€ãþ¨)ƒÐ¾Éj¥º0F@x_GPJ½7ò¹dÀb¥”N…R÷N¶Ú…~ûÞ–ÌM¯ gUYèÌh–iÏ 3ý匃¾@ãõó<ŽýöJÎùJ$ÄÄÜ‚˜@»ÿz$[ŸJ„ãß ´#V—/_¾~`–äÌâP^éŠ3íܶæ÷æ§=Cm+@ÍFiµ¢¦m³Æ_ºqýú-LÊ—ðûÿ\81H±IBŽãò)ª#˜ÞÁ?é°Žx†‡1é :ÏÒ£cØŸOðÉŸÏÿo%‹ïÀν°³©ŽfthDØb ßt¿V‡Éöê¬iFÏäF kf)[Sõ–ŒžF®£LõE§%™«´Þ]àdxÁn@댘\«S¿e÷nV;tSÐj`º›BÙz?ÃÏ'þûÓ”æµd”¾¥DYŠÝNÎà™ßnùZi8™Ž=¹”?ÕEøÜ+К?Ðb†‡Ýªygš›âg8_b‚â£8wé96}¬µ,4Âp¾ã¼‡'Ö¯Ü*ÒEgŽLÉc­?U~Ú@u^ > m¬rÜ y3Ë+,HÑ.·N² ¬´`‹)›¶(€‚4;1:j.Pš:6?ÅÞ`å¹\–ñJì…!9ò)\è+¸*;MjòRöj;8=u×q0vÀ’RÁ4míÂyÔŠ%t)XKˆ!mžˆH³¤"ú üㆡ‹tþðX~Èx‚ѱœ‘î¿(ñ¦‘’ûÊ}DgÖ­Sž+†žsëÖ)a©å\˜M±6í¨ÌD|µ ñ$)õ¶Ý2·o´TCÒªPK¬ðJ¯zØ!<í·/RåÞá¸áWOò>€ˆ b"Ærͼ2€þÑÜÓkÂD„ç‘èAõXvÛByÌSü:!>ò^½rU†W_7Te¤™, nmè„Æxãù! €ÙÉ\8VÌšÞë”_¿~ý 6c`Ø í2HŒbl 4µ©RC»° »dãf ÚªVØ)G¦ÎD“‘ó·ÿ|/ôwå €£©íÝqø÷Sìnx—ý 7K—¶D8…Mç’Ò‰nöDá•#S“Ô£t½ { Å”ï°|¸ÛÑ;7ªÊ‰‰bl|´|ÌA¤sƒÍIÂÑ£y™Å –~—2±øêáø˜7ð×x*9çdGÔ9DMOšiMWeÚ³?d°ÇES³ðZóÎ 4­ü§?ýé]½ 1‚GY‹ ‰ˆ=h"t}/*ØŽ_ 1á•ÒÀ¿(WØq¯lRäJíWøYTÐI$cñØj'…c§eмy’nÎ…×.Dy 5I#šEz3?ÖÉ%[ÛòIA·K×KI³O¢ÛÑa,ºl´ï5£qáôˆïtx‘#ˆérð0ÖÜ÷ž‘ w¡íºà;¢k(£–ÛRJ “í ©÷²®ÚØÎÈ1ïZÙ`ÇÜöí˜Ñ¶kw#F«¥ÿQ¿ke,îm/§Üçzæõ@|eÜÚçèsŒa‹?·…<ÆBCLLc‹EA·!5Ø`ÐÒ ?S?ùÙÏ^ h#wÓ8Ö†‡-\‘Îï¥Î²šŽæ ºPË·5dƒ‡Ý>Ö»é2ƒHk’¼ºâ’ºZʺâÖzÄ€otñ1u$h`¹ñ•ñF(¿F©7;%ÞCþvîˆGõÝ`È< Œ¹Né·»ïÔ޲܅“¥iûäu^Ô<]4yâ;.¿,a”h’EÜé#À®(æ+ÃÂ#{E•g&É3"ööá×Ùd Ú$~†ØÒ±Ì‘3æ4Ñ=¸û=Ðna»ß¬œQ¿Ž/ú‡Åœð9_´aùú“£ÇùÆ M¢ñ1Åø¤0ÄS¦?0xXØÉ‡˜í%ó(,Gdۆǚܶ“Ëuö4Zs<ËÌ3gZË›ìí·ß¾·|ó+~Aõ´%.DkÞXÑ¿aP;"ZÉ›-èØ—i´rÈœü‹…~þÇ@«asöŠl¤#¬¦ÔVÚ6G¼¸|Î~)ãÌšß^°=PÒ¥Åf]ôIý`0hc}N1Cº˜2ñ´F(éœàɳÇ:ú„§~ýi½öDžÖèýâÀœIë5âÄÂÄöR©9ƒí-ø‘3Æy)#E»ñ¶[æ¶k[—ØóJ\–ޝ8öç-ûk±G¬=Ä×ÁO³O!Ð?[¯‘ã"À©}ª—ˆ_ ,r‰@» #“ \JÀèCáOª=Æ<è§RššÕ¼êé`æ)sHïM§½P»¢–™¯•ÚFë{<áf¾Xíc*]*ëAU )c@•Gû;ï&¤ZQæäŸ5S7‰¹Wð€’ÀA¾ò{÷ÂÔ-â E¶Oþ䶆F™ˆL‡‚ó ±ý¶<Ëq=¶cDq4ƒ1ÀüyP¨¦‚£†^š8]™ï%tª•ÖâA]è/]ÜØÂs.‚5’cGŸý²³­ÏP a厴&ù@Ê‘ƒ¬X¾ë®2¥•#Maig!‘xëîL1V¬²ä9éÊ¥«ƒÉð|º:åÙøà2¾`†ˆŸâyH¬g<#Ó“š8 ,ÃW™ƒŽ-”OÐÂЖð´KŽDtQøÆ=Ç÷îÞ½µçb*B "iË,wN·Læƒ;ÅÞ…žÂd/Ô•2|m@£ñ:Ä›˜üš/5B¿²tsr0Óƒn±·Rüd€ '}àƒ¨eÜžàÆgù¨XÈëk»káÚÕB0¦8ïŸZÇq§D¸_Ö­JŒßW˜};_ÁËUõmLÔb´ .Ð!2.u Þ¹Ì÷`F‚µ =¶8¨iá1‹xgțɩºF²Šòy$´ÔtíÈÑ4‹‘—¾ëRgºb<õ D-oÄÐ塵4.jÛë<4j®cÂ"ë‘9¨r/ðñÌÁñ@Lžb²Í¼& Q¢y)ER›4îØ‡ ,Àe¡ŒKÇ7oÝÚöK×À–/<ð/bP+IÚüZ>èù$ ‚ KEŒ%í`Zžb`B]‚LR|òçÄßOØÑ‰à[oFÇ>:~±¯i|3ûˆÀ (Ë=$WÄÖ}úó”gßöü,•v(s`Â8EͶ}Ê¡ÄRçaÒ5<êV|¬í+ž>ïf[ÆüK’8¦‹ÿ × ¸ NìN÷KD˜û âÊaÆ=±½[Q²P6IÓä"Øö½¬nO™½6;Œœ}lÏ߯ðˆ í~ŸD¬£Ö¥.%â¶³U•±¶£W<ÿêè+¤£KüäGùÐBÉr$pŽb\;%êd ‰®Goa{‚WA>.WâT/ϯÝ."’ÍZí7ÙÊ>ñYbÄ®œX•5 <=Qeõ¨é˜J³j{ nö7h†xò_úÆ!Úð£×ÎÅHÓ¾aùŸŽˆZ]ìψ££É…ŸénÊ(Å6/2d›:zê“ûº/RÉ!ëv½öѱ] ª[‘êÜ3z¢–Ø,ìÄ_z«^–ZyYìh³[+‘„h§Õ¢œ—QÐGÐfóã+aNä<Öµ±Ò¢%&d Øú>5ÑS<8¢‚=”ú”œ2ÝänIôèQñ-&]kª{$®`±ÉÒÏPØÖ˜ð éä§OÚFζÅS9¡ÊMòÕe™­]O_äì“èezM.¨SE` %íš‚óšÞ¡5}Üè¥z4ýè¾·Ô¹}6=ú^ŒÕTb)wÉô`/TX.vÔ.¥£„cç5J5î«Ëîêjô»f°^Ÿ5ä«#û*çƒ+ϯ †9ºÌè ¹ÀòØJ,8¨Òªôzô‡°µ?è† 2ý: ?¸råêµ{#)$]ùxæ,WjsX¶ùµ V™Ñr¼ á‡:ðS»¡Œ ·Ú¬d4»“Öéu‰«½3§ýF±”=ÆMìôW>9’µ!5nnzÕ狼ñN ”.öl<¶œ¢ºjÝ©¨;E1Ã@öX†Ÿžã*!prÂ@ÑsGˆò9Y&·E9óEµNže`O®„sÊ+=ãÌ;¿tùää*,t¡4•‚”wFK™Þª@=ÍË%âqr×÷Ár1Ñ£¨¾”@b`ËŸ>A‡¿ €µîz k0FqLFͧɵä"Þ–·@´qmÿÌÙ¿‘†ê6›‘ Æ8çD„*1ÒÞí'Ê[oYÜFÚoÔÒ\/?p½L´>M£Lý6Ø ¾d.²³G ò1D:™È°·rk)ó˜Øk°ÅÓäGn&YŽùYJ‡£läÖ DÅUzÅbzÅâpQ Y¹jv¸ƒ—ù/zºéæÙ"&}68-;Ó2çþ1Þ¹‘Ši_ ’ÈTØ??>Áa)–»,|PT™içðæ< €ê߈6GÔ,#Ž´7óxòÉœ~bÞV8@ZN^α“¼yçvÞB@™<äÉW…j‡qP ÷Éï|^‹ÔžUÕ“j'«Af‘èyô>r ƒ&0náv!h&«Ä·Wça˜ŠõvLD×tªZ;äåëÀ¼Ò@;^ÛºH/&ꇡ3úÂM±ž‹á‡ÉÒh²íð® 6îÿ®#žRkÖxÊVÂPÛê%•%[×Î¥Ôª¶c•wUgtp n.bÕô Ъå7.|Ä{Âð¡0æŽ+V[kƒé&ˆ±‘®Å"7«¥Žh_©¦éˆó`(ž…x“`âêÔΠ³äk÷ôVžÅãX±5_Mv̾‰áËû&Ü6lZ"ÌDF{Õl“n™Á#èP5¼ÜÂUeñä'^òyåS‹d¶„dÊÇb{›iHTÿL»;jªo_e#Ú'îaÞõèVÔØo—ÆXÞ6ö-éX·É8jì±ÛXRQëã/Ì‚÷ d­~é€Z’Ž}¯Çu¦`qß{Û'èuDÛý;zîpÿ†"âjáÚíƒëv_£\Ÿ^žÆG½$WýƒsÅGMéÕÖQ‚ë8×ÁÚÈ퉜ìUN±)¤fÒË"€©K‚ßµ·Mbj»Ì^B¯èYuÅŽö°I½•ñ’Õ×Ô„F:TŸ5Û×´éVj®Øˆ-÷¡ ¿À³€® W‹Kàët—žƒZ[ê.Ú2¢Ùª©õÀzåÇGÇq× v¦\Áív”1èqÂ%ד:,»ƒ´yÖ‚ö¢Â•bgñ!@¯Í€ŠÝ‘¯¸B3¢Œz¤à(Ù•Œ ±”„{ &Ô×$¼„¢ ²a”û°¨4ѶØZÓüä(÷é9<-÷öݱ“w+–¦’3D ‡3&üǽ#º{Ëb¬’“„ä ŸÀ—6”D€í)=tø4þB»:ùCØí‹©Èl ñ$ºqtŽvä?»/¬ÔÜUìèŦëRkwåö¿ ójè´À&E,ƒ¢‰Z³;(ÊÖ‚å®àyt|È aLô:9väDÅH–øFÀô¾­”0Šý®ZTÜAôy/RRaVdÇ!0´q öúò¸a)çak½óŽ J8)3ðåÀs'°} ÐÓŸ²Ãg_¯4Í/MXŠn&]_Vc2ÖPÙÂù„Kìv-ñÒ¿”¥P$=”n%'y÷k>ŃÖeG=bLPfùhC¹Ê£ujcH°°©{[‡G¢Ó?p¹Hç€ñÝÓ×¹ï[ïÁĺèðöÑËé•'ð†6ÅØqà¥AÍèÏåtyý¹b—JÕˆºXj/UêóVŒ×¬“ 2·Ý(^—VL ¸ß­v{éìnoyçð¦p<=­®nG0'lmÝyÙDæ–Tm/hö™ú-ç%©Ó$ú3õX1 qP«R‡À¬ÛÅWÝ^ú¼ €lÒ¢œm•¯@¼áøñɳ-3y›ðƹT+âÄöƒ«gÙi­£Ô‡Qëø&@?þ±Vè‚# Ð¢­œ±ËŽ·fÝt«vvÄ~,.‚^·®8š˜'Ú[qµ“´m€Fn)[¯ÏÎ.”û#Àc¨zƒeEoŽQ¹Ž«Å„% ‹µn6¸ä »6ÒêN–¸â¤’ÓB£{0è@O2\ZÛËE>F€s_¯É>Umþ(ÐX„€s'ªø‹ó 5!'Áü©ËbŠÓÓÒ®  j\ ßu%ÞÉœ=XR<ó3¥]ŸÖa­”m]ióÎ…ÅCyN0_ƒæÄ}‡JljQ íäIŸÁUBF»f¡hæH HY}þsƒ¼DoðZÓ¢.Ûó¥3c—šÝv»µÑcë˜õ¦Yêú˜~®µ „qEc“"ŠÑWÇϲN”Ø”¥ñT/W¸¯Ì# {KüëXo±žýÕ~šc_{,ì*šÎiÑe¶~5ç ¯±·nΫ­íB¶ÛŽ~ǽ-gïÁ×2:â¬vÝÙ©ýEŸÙõ‹6äWœ˜XmÎ1@ ìäµQ(e<ø‡ zùZ½IÛ–pV ¯‰BŸ%4ÖàIç 2þ  Y&1x›•/‚‘ €CêõFáˆc^ÿ a‹éÆ{Â8x@ÂD È …}s mÐ[IX„ìl½Š€nF7é 8²ŽsLz–®»Xjú®1Eé Œí¾—’®mÂSŽ ˜uÏ´ËZú°ÞåÕ ¡{&ed§Ç’ªï úðD<3– rô…óÞÛûâ]ûŽåÓCœO¢Œ-§w,,\BÔ ¶ÁËúBÐ’žÛš6Å®”Ó±¤ˆ™mÌw¤=…Æ\×o§–¾¶c]›ÓÙìò÷­ê&|þÇ_¾8¦£¨šÈVƕ㱖š¯ô•eŒ¡fB½¥ðcâåIBAöH¼0½«¸¤¿‘ì›^ bÛ!À" !¶v0:x>8ÀGè2€-KNPxˆ;o£ã¸Z‹@Ã7 :$âgå PC…^;†ã©ÇhwóŒs/ÚÒ¥-}3mG„þнýÌuŽiÕ†GKªÿ5YŽÔ3q rÄךô±Öí\‹Š³ì"?SpÐ{|ÌQ ûÁšÞ[.+4ÜG/uo}èìŶ={x.ež"úˆr©µ¬îu¦\Šý÷úX2[Œ¼íGévî´øíž^1MlŸÐ‹ÙŸêÓ27ŠÈÅõýÅØ£è­ð¦y(eôº¯v  ¡]ÝC°«˜¯ë3 +¶·røÉ‹äšé æS}[lzv£åÍ¡#Oe^öW_ÄN®¸ ŠÎC%T‹¨JÐ|<F¯ëŽ:mê§ÒC,çC€Óº³Èú`9M»Uqµ Ý¢=–3êF. Ýzy<‘MmóΆù#oø¤ñräÔØý…zW / -9$úvÕ`Y¿6üj»§í–½.g±Þî÷ÛÕD[4ŠÚY61Fö­Éœ“À£KøÔpRßä›ÒIºašçC@5þuÁ û4ð›Ë÷]zøÚkS{„h¹g„XìôÀ2¤SîÙÝe¼Lß—@JXl õÇØ±ˆL²'Öì@þþZñ.ó÷6DsApœ €Á‡}½f9#ðò¤íµ‰¾pŸ:ßUóí¾hUµ½âºê]â¾±±t^(oþa¯ù™"ÁÃ'ê¥?‚{ï6˃8×PºŽë½¼VmF»(»—rZjô¶n½Óklëe-­Yæ»}/ñ¯¾Ä#åøyÖ?[ÅyBiÎ’%4ž4o•y¨=ù‘ó‘þ𼂉ῠ‘Ó—å žéÃ-dÛS:”;}] €µŠ-*üìé³ò”-ó$ņgÐø§àg´…Ô O%D%R‹ÆeÆKtxK‹š5Í+NDÃý‰è65æûË´kyXé£ }f7–>rÕÃxÂßÁQNû&á æ`»HçŽÀ3¾ýýÞé½Ã[¨ýŽ’×ÂIï¿2žÖ– £–¹”ôšE–ˆ* ºJ–-Ù­Í&³CñÄíÆ§§@V«—Œ.Áâ\€§Ò—0˜2,º¼¯köä1­±†>¤âXx6•xmЩ,! ’‘dÖüÈ‹%³³$y>‹¡m¾Õ{ÛÏñ@@A5bp@@à =£—õõIÀlåa;ö™’:*½¢#â>‹(èøùñæäùÉæÞu‘ˆG=chÎÚ1_"ˆo±ÀUšý1è £ƒÌ%Û.úØ\A'IK¯ã!ÔÕNž%°Ôe^­µ”|G2>½G‹’ i•èÙ‘^䧋ߴк 4#lY佇¥¬JŒ¥ŽÉ}(yV¿FFnP¯KÈ]ji¬Æô:õú˜ "{­1ô7jz Ë6w,QÕG·¢·®¹Ž©ÃP£ÍËÌlð‡9ˆÞÅ5OGJ3J¿fõ¹kŒ,Þ v¬\öÐ7€&Áx a’Ké’¯‰*ƒ¿¦F·lXkVO±B¤ TÆ¥’K‹8é6) BÂ@Ù‹­w$h¶R2d`0ýkÐz‰.¾ó©ÈÞA[câ^È`gë­&Ík9˜Òn´µO–åŒzK]‡±D[Ï6DEÉÔØwGÙ.¼z¬u}/…®.%e+KÛAnÁE~¶è Lz÷'p'Sч±·Œ˜°±¥­'N7gy\Yï<I®Å!ØŠ9Ôµyii4æòóËœŽ[ý|Š<Õè˜kqIz§D8’Cê&ò!w°dJsÓƒé׿ÈÀZÅ·5HØgOŸâK€l>$ ÜâáœË1Y1bÌxQÑüÎ_}éUôîèâXñ}«?T£sï2çö³â ¢µ¢ hG¹;TÅÙòÔÈò†Ëï&Z÷K©5ÎiÞ>‰Å×ß ¡e–Ë ¦O2ËúV›N÷iÑ¹Ž¸ Î×ÑËÜ3º½'Ãkð\ˆY;#ZéÃ8è£(ð „°öÔk0¢¨§.ôͰ‚š*q'¨—LªsÕÁvz¿.]zÉú,«’¡ÚF¬ ­|¹s^ >ÁÇBF½GŒ´ÿ¢þœVêi‰ãÌ‘ н …ñÑV†œßûœÕÅ{à8ÿ=ÃD`WDqܯɺöPç]0^=fQ!VúгáOx K¡‹ŠC˜ ]Å?3ÐèaAE •@•ø¯ÕÃFŒÈçêì½A&¯öNmßÝ„ ƒ†‰i»«ûuMõTéjiºèÇ£Í垨ah–¢AÂñ³^Ëö"˜o¨˜QÕ—ÙG|@òä?×t²ÇO•[–cZl‘Íã¡«†ÂªÇAÑNÛ#~æÒY-ºÒ³Åá<®×kéwxY¯&’s7κ?y^Òå|2™X0nœª´Óœe2"š³–úÒóW ãù'á7r |¼#ráE°Dl¬Še4‹ª‘:C:ïÇ`Wj_Ñ«8|ürQì-*MSœè¹½œÄ)À†@6®Ù„žÕ/ðV1âOpɇ—}^ÿí=¼KŽjZ2ŒVQTiò©W´±ÓJ™r%IAÑÈthf7!]ß KsÝÎTõtBΤµÙBÛkÈq¤Ävw‘÷¶™·ã…¡ù)"À“q‘Œé<®#Î^"sOŒ=š® ¯îkËçªQÞK ôè·ZXÃÏ5¦³>Q¼GV5JÚ%uUxHÃ.^Pa¹÷@Xµ­‘;jeSç½N– ß_Å~¹HÌXà+ÎøÓ¨È9£Õ”'5$ݱÏùhx5>X ß•ˆ9tã_ÆGþMÓ «\"ç}'Mg* 뢙ó,""c!û*ã†)òäé“Ë—q ¯CðD+_£ÛÅ^ê¶ȱM&DDÇ‘O2 Z‡-HžLxÉç;±˜C¡nÛ)XPrð*¤Z:X5@j3Ü€mª’ój®†ÈØ ñ¦ ˆy@2Ê¥¶\¶[|Kg˹ƽÐv‚G]ˆß¡×)àÀ:vÔj|4‚ý¨‹†ˆ¹{òV\C{ÈÒ£¦ fi„;LÖ¬û·v´³”6Ò`²F@QQá½KLÑÚt`Öö: »fÿ²É®<¿‚%>ºI{B9CÂÆ‡™>Ŭ±Æq¥ ?#GÒsâEŸ: lKœ¾xÆ+4ô––«Y0ªšçQ¼Ÿ[[°€3;„m­`¥]›&ÃWEˆf¤T'„µhè0sÁ(TÌ!¢4.¯ ã{v$»›þÚ%`¥çÆhíoµ]làÊù¥Ô==ú|óàÿüº”¹0¢ñü†ã›þVj©?ºÝI9°Ú 'Çx³Ô: ;Ö§º¦û¸;—Š˜°‰¸tíÂÀ™·ü%̵ØrEØ¿Ssy79¾®÷£>N§@IDAT£q‰`hþa»2øæ#D DÆ•’)b†íÙ“'OüEsª²Jϸ3ñåÔv&{¹bÙR5Ñ2bH{3¿AÃá’ n;.¥Ê£ gLe訦µOáÍQ‰‰)‚Vpè¨òÉùK€×rà`¸À3 LwÁíßÛn?rD¨¿Jùc PL¶Ï>ºù÷ÿýÿ=^pØúŸþyóÙÝoÚ£›Ç›ãë^(bÝx‹?.3ÝÀC]®ðÉn‘Žno.ážã«ÁSËq~Dl.,Å_Æ J&íá›÷ˆ‰‡î¨\øÑÞåæÂ ˆóOÚ†>›šNL´‹T­»ÔZÛÌZ!óDmÜYrø­59‹‹—Ǧ ³þÜ¥j%˜ÖVí·ê¶®08Ù±·pcMéª0ìŠð8~€?ôÉÀø à+9‰àÐÄ[HF¾eí9òó,Ô¶¬¬«@ÙœÖdÄ<ôè1ˆ¯õq$3™x¯º"ŒÄ˜˜xw í¡*ZJpoGœ ½‚ÕÞk»`ç´"šd±ç `¯ÈåtOë&wÿüÎægÿóO6ÿ×ÿðÿ¬.¤(xð¿}¾áöª¦“·N†ÊeòXt8¼Ùù›¿wcóÁûžO:*‹|÷ÑåS¨ÅÑö|Ü×áó|5z1ÖWŸ_ÕÀ‹:-ÑØ²h'E>³‡7¡sêÉ3 'rT…±W¯æ•€¨tTºG©ÓÄ ÊÀKˆž=~ü˜ €µ$ËIQe¦OÐýìYó80¿V‰*#=l¸ðeü.%Õ•ÿ`ÀR1­¾T"•ÀÓ”&Øñ;mbÑ}бkâæ BÂ!¦ÿ ;ý»ªƒJ´×´ – ój먗¢³ zã—w7¿û¿ütóïÿ»‹+5Zôë'¿ïñzôë »÷_ßݼ÷ß¼ç²! wxt,é·|¢›¦&8H‰B±h*„52êYÐX•9À¡:÷þì=Ý‹¿µ½XÃÉÛì[Ø þ¿ñL7_Dåá­?ª†XœuN:$Ž«Lï-dj…ivVjùk˜›ej³ñrSv1Ìí„[ÃÏCPcÅð*gDÆ”Õe\þ/?aC9Ç(¶/NLsK) !ƒ‹–ÄÄðwõê^hE,ÖòŒ·üµtZùrcÜæÖΧ0a¦,ÛòLï.]ºx@,ËG¾¶Â:„Ïä¥Zvv_Õ ]Lˆ+ Z°ÓÊŸêIpØÚµzgoâ–†èÌu&iô²Ðì•M.j—‡\SξŸ¿§{î¿úâÀÆ6²íÛÁmðw›Ç=RÇ9ÖSÐZK¦ 7'Q§š4y\›³µó;;Æø•ÿ‘Ƀô4ÛÌÜ®êÛu1§1&1°ó ¶ãSOCÌ‚Xv¥´äç â[8Ë—.]¾€ÄbQ)™‰p½´¡Vƒ]Ë3‰AE\aÊ´4ÏôÕ‹/^€ó–é`9‘+¬2K#·†äzËáhàE :›½ÈT²UJØãöºm—OòŽ~M>êYÉá=q ´Ù¦üT¦µïÿçîÓ£?úµ¦”³MÞöÀ­å¾‚ê§ÃÓ¡QëcÇGʤ„½" ìz RzÌrÓó²0Õ[›!7bœ'a›9ñy0Ý«šQÛ|™d>ÈóÄkÜf^Æü¸xN‡(Á•‚‚–Œ@6ò9=ãÎþ…ˆãtp棈`0È8X Ñ¢·Bd-øu¥k¹°VÑò¡”+>È/ aå„+)lV@¡.FµH(^÷*« ’YÁ¥nêLRàpùÜA<¬é-þ* S»È("›6ë[±ž*ö~]t+cµô˜XÈ —â|\ž\[$É”ûþ…ûºgþÚLÞη=pËz _Ò÷±“óIôÇÍO,kOp§|ôMa7ä(}FùÍUòÍBQ}Íù°Ï€'Â4+ÑáÞ'ª?ã‡bÂ9âKþ{Å$Dw.àøà Ö J*õ´zr‹r"–)çÀF¡cÛ ¤šúŠUÒõ]ä °¢L¦wÈ›ë‚ÿy¤•ø9 :Dúa{_o*ði)àÔi#UÕ ìˆ€¹ˆŠ9]®ÜHçÇïZÙD· °Û|?Åv·½Sñ|©lŠ6´¬à|\òéþî ÿé“.e;ßöÀ-çAð/­ãÑ #‡ÇðÈÈÇK•’–1Jp3.&wÍ­¶zY[Ѭ&Z`X·–»…ðÒÝ«{ 5/<·ÕþUÎ3÷ò¼:ã“BN1 Ø$Aí²Ë)ñ)d±øÐú2Âà9ŠU©„á"'ÅèbÓç*†ô÷YD‹†´4ã®àX^i§OŸ>ÏߨJ°+­’„’P’ñBbVƒ~á§…èw¦]E‹ø¼ÛîU,’颾µ³¶=ehɳ™œ•'ŒL$=E¼ÂáSR–5üпöP÷ô_Ý^ØGÛù­ã±àÏÖñˆðQÁœÇ‚Ž+ŒëL>ÎÂL-`M«C½$Þšhñ$º9ÀÒÔsO·[?gø½ßu¹>ùSƒgÿŠ5Vwä'.˜ ÆJX纅ͫE¶ÈákøË§Ïœæ€©7`CÃ<ó[yÀ®ÔÎèì|# [j P›ÌL¹é®´ñ*rüø±³¸Ï hÛŸøGÔ @º©—¡¹rù5«Ž¬«¼Àޤ_œ!çop0¼>@[·@j=]lþu¥ ˜8I•L ë÷ð¿þ`÷ôü„Ñí|Û[Þ þ¿ÉWýÖžvG ´ŸÔ†tâX[ÃS²00Cd@µ°¶ÄºÌŒÚþ,‰öçþ¸À_dœ@¿0/ Håí Äêø1 øŠúוjù²8  ¤—[rwáEž¤Jeuéø±ãýg.!Q„za+/ïÃX¦„ì}(U°µGâ¨Ú(1W˜ÆOÐ>r?„„¯ÃÇ*‰*qå?º¨úðmRŒ¨Ct‹@‚¾¢!ç²KgªKÕÁødhyñ?þ°geOßÙ¥ÓÃÐÛ—æÏªµý,™àåîj¤×QNÕ•3¦…”KÏËÌÿòP÷Ô_Þ^TŸn[ÖüŸA0qú#’PÞ,ÇG/§Kúy!] |Ywfê„åd¦’å!”ë°†Îff3ì]Ý‹«Áü]˜ü:Y¬‹€èŸ@ò@žaÒw1_.¾t;ŃsyLò¥B‘ºE9®*,>|ø „CUzÚS“ôžÒCëïÌ¢S‡cocZO²Œ+Þ2½æ¼ÿþéåÕ•%6]~ªÅaÅQ­MV´<½ˆ¿òQ!­äªZéàr¨qÀΗLköÅ«+f¶/}Y—RŒ;Šz«ú£@9° ÏE*8M0ü…‡ºÇÿÒc£V¶‰ÛØ `ðî7ü1Âc€£ÚeÔ§±Î¶øXp@ J_™›eÍ%íšëÐÔ£µÓ—ÑC­ÌVÁ=ç3ß»zwý,|´‚MÄ"@ g¤­âá?\àCæ¹ cëûZнœØ&c“þìˆÊ·Þ{ï½SaEZ>¦¡¢IPp‹‹‘x™oÞÌ|= €iX˜ ÜlÙÕ‹—.-á3W«ÊM œ{9]|ZR;é¿k¥Fò¸Ã&¸àìøø5@.|EÀƒ‚"[!m¼¾E£wm4s*^©ì»Y¾‰cÜl¯gkQp[|/gh®{üß´{üW5a;ßöÀ–ñ€Ïüý‘ŸõV¼Ü-†¸äJ‰êãÇR½ýIJÏ hêh£VhqÓ×È7>G­að³Y¿»ñÇ“A¹–.Ö *î¦ã ~·âò>hÕ'°ñŸ1][À ø®s#À‘ð*Þà·¢‹DÍ%5…nYËLëlf¹‰|£ €¶`tr†­3†¯^ºx ¯@ Xóê<œ×_¡ìý® Ç?S³ºŽÒq €¯ѽ›èRâìH’¹Û‰ŸÛ»r7@QH½}’{bÍ7‚ :vÖÐró”ZC³dÃÄãñ±î±¿øÈ{Ûäml>8øÇ=dÎ3ãsM{4ôR-gØFËÍ–*:ëÚÏX¯ÞC™¿ažüiP~*²D Åû–}¦NVo¢ýå>ƒÿƒÊœH‘1½D¬BŸÆùÀÀK—.ñ@+1gʸáàØx?˜Z¾åÖ•ot0ͨ+žù¹b†'äð)€ó¸gÏ<]Ü+iîâÁ ¡pZ8älª±h‘ÿèuúcDb+µ" ×èw.æ»{Vï‘L®ôV‹§®­ºýèž¡ŸœÄZ²iAÔ¶5¾“ÊLŽÉЖĬÿį>Ö=òïàÇR¶Ó¶6¹†Á¿­ìä¨÷p'g˜È)›… …rU>ñH—Š€á.—\u‡"ׇ%£×5G]_-fj3.01ß·ºÏDLÐÕ`²/ÀgHbÜÀÆi?ÚÓ­à‰5%Fw>sF„;>Ä. ð%Ql!­àˆ?sø€U| ×o K(Ï4ÃÎi©MÅzKžŽO[ŒŸn¥6kBÄâ} &â„ëÆ À±éê~õ1þSÚ©z¢¤KÖÞ% . pÿŸ &þÞ²:PN8:•m†ÀÝÝâª?†Èžß:‰õßXÊÎ\K“²´¿±2²4- úE–+:Ù¶äÈ*ò¶Wvˆ uz×=õkOtÿ…AßNÛØœÐ=ÿ¿Yîù×*b×ÂczxW6uŠH¯î##(ë»ÒVµ'€¡µ ö¡Ê×cxB$¬wIÃÖ™ºyòE¼xO·b€ÇUã2ß±­„Ýæˆ!¸°Œž¼: Ûkaà:!­Œ´0W8š–®ò@ºPãa²(É„¬É¼J¸V`Ú`½öX&7Ÿp®Üš0®œ½rùÊE©Yy¬¦hƉ“Ç$ “ CcUUà2âcÀö•¬E4@œGð¿W˜ðî ݺ©­ûÞ{a}¤ÖÈ4Íd»¨’¨IqHÍX†“Bci®{ú¯<Õ=ô+Û‹€¡¶±ÍàñàŸk6{”¯÷ˆ›”›n7—Î铺“úYjù‘2²¡ OšØÔQs=jsÎþðWg*4-dÄPÔ²ªI: ¿^¯7ôgüÆ ?÷ôˆ_‹ƒø  çÎ_à+€A`ÓBi(C¹R‹ w%ukºÞ ¹¢ni™ÇÕŽù+øÐÙ+p„Z‰&µ«'z’Œå2ž¬ Z-×*M¡=u§ÝEñÛ¾•}X`À ZR'!•vëíÔBûÆÍ›À?´|ëÕ< @•˜)¥ªÁå¾ÊAѺ’¢~#àõþdoÜöÀçë]ö÷™?ˆ×¬”Ç?Uú#bºZ=V,²²¬2aÃŒ6Ø ‡g«ºqÆþ Ü<æü}:ûG-á Þ¦O|¶¯ºc¾ÑB3O,—ôE£ð 6P¢W·ì!ùˆp P†V!YpĽógϜίæØ(aîŠFÍ3MëØ©ôVn£ µ F\!Û3ži–Ï4éž8qḁ̂˗Î+ÌKJÅ‹ï+Ëerð °"zØÓ«¤©CV;|aI×G7J­ù0Ž6t,»{~nAÏììø¨T•o•] Îk¬-}•S‹gÞFàdG v4½™$ÔG FÓG:šêÿó§ºûÿ<~Dh;m{àsö@>óŽZb™RÆ~s)=‚Ãú‘-†V:¦Ûš“én+›ä28ÐÉŒ- sÎß·zo·€ 0ÆùEú&9|>×í—®òA}Ë8N36¥`$¦¨òì»Ä4ÍY"HS®|ŸGü;] ¢Š9´Bð½#Cbêª.r¡Ó Ê•oaV4zPÛÑ£GO_¾|ù|4,9¬t)ìTƒ¨‰¬8ܱ(r„2Å-è(·{ ¬¾d`t-"ü ð¹#ˆ¦Õ~U8iLÌó&b³‹ÊÑzÈ8êø8Χæ\1J¶p¦¹qÎ)Kxeÿþý§/_ $þŒbN¢£d˜ž+°eåÜÉ E¹àËìÝètûˆyl XÜ·rŸ~ Š,!c·ènãm ¯ÚkqËhYî$ ‰õÛž”Ô"à¯=ÝÝûË|˜g;m{à³õ€ƒ¼ê7œctsÀçAT×’jÃæMËõLTaZwšl¦Û~̰ƲÄíïÒà~D€¸À^àˆ^ÑŠ0}ËɃxÿ_<Ðô)ÇDbe˜@Â}’XC»Œ+ßû÷µqQâEÛZ9ƒûÂ6]ËÀæ] ãÌI3Ýp‹×ÆîرcþÀxâ¹"Ìk»–»òd&Å‚`üÁ§\ñ:;H¯l •]˜òÁ‚>ÎEçß‹+ü=è^¢€[1Ën«?Î(b? DH¼®” .uÉÔu›_³CK\<û׿Øíû¥x sÈÝÆ¶=psÙ'H¶E&üZm ŽØ’²ÕžC*ÏÀžÿg:Þ‹ÝNÛ¸Ù`ðÎü¡°:2Û±Üâ8•Í\À>¹REŠ^ J£BzËE©êÙoMÔã¶el=üŽÕ]ÝýÝð'|Ï„1æÓKá)¹ø ®&3žÄIfÉÈdJ1(bùcâŠð·Èa'ž;wž® ²@‘KN8o@k²\%0ÍyæÍ„×»h gܰsHØx»ª©8VV”Y9wîÜ\²çS{¼Þ¢ïÈ€CÈ}Ù%è¤Å@_$Ã=;‚WøMûèÐX ‡Ñg¥Ù`ñg÷à/,ÐúO1v×l„ZÚ6wcÕdËÏ¥ xDRóÒ`2A §öÀBâå"[X‹LÊ÷üüÝ-kßöÀ óÀ¾_Ä·ýKð÷×J×g|8ÎGuŸõèKPhj˜”ò±’È­Xa½íáö´l—ì™¶‹þ&̤9çsîŸc»àH=ì'óÙ0Ð'ðÇgø Àå+X0Éelßíýòö"ÀþÜίß}ðçiÎËèó¬ãÏE‘PÇ'ŒYÊZÎãÛ¸Õ¦æÌefÊT­ZÞt‰I­z“I‘-K¹ßÿ»á¹h¥öA¨íbÜà/êy2ä>ѤûŒI}WwÄ Ycq]º?ÁDð?yæÌÙü;6ׯÉb0ŠR¡7p7k°V1mň·[¶1!…¿Ù»zâäIÜ8œ>¶Í—×D‡ùpdus”H#½rŒ\ÜfÇ-ãýMvúC°gì—€nDß‹õ ²&×´¶0°V[Zþ,¼ðšã¤wN»¸€ü@vÂö€[í´bb´¶«ô$@«s\ü÷Ïw{~l÷¤À6eÛô@ü­^Çjëýˆ6‡¹áêåâÎ9LI#ÂY Œ„˜l·²6eµŠ#ÛýLöL&«™*Úþ¼ðTgþç|žü1ðþ¿âwñ¯6Ï{ÿÜô09¨Œ<úƒ½ˆIl(@Ü},ŠÏ„œ0Ë‘Õîü¹óÇNœ8Î/02ð»–ÎA­72‰[†ù†Óõ,XX®¨ Ï•[ÍïÆ >røð™Ó§N áÈðaºÑ©À"p›O<q¬\]BçáëMì¼Ò·2D8  ˜_„º? ¬ÔøØ¸±úOÐÐè §¢“meØöFh×RZ6Sá:É‘‚­0ª}à†Äž­yé|¾»ëÕ;]»í|Ûö@þU¹ŽÅ: +k-@c´ŒÝÍ£¶høE*Ó2<£@[v.Ñ1ÝL+p&Í(bÀÌEÎ&@JƒîÆÞ ÇÄ+œú-ó:‰¼Êûÿµ¿¡Å€S‚nè£a²d’°J.¢|Ú¯¥+>AϽ=Œ¸W”i ÇDâÞV˜´6ÑÖ-s½ äJäJg^na&Þû<¾p_D:Q¦ƒE%¸ÓŠ*H…·åR'qÀóëM|¶P·Ð¹êT*ƒÉ=øÇ•Ú}x”ƒâ–IòÙZ­ÙÈ¡JoN1Ú’y)‘a8C…½FfÍÖ|ßÇÅ@NÁ™q›Û1×½ü¿¾ØÝõòö"` —o³G< àÿ[Ï” Ažztöã: ð"B/gÓ£Ÿ¦¯ kõvX•¿ j3QòÔC{ÝE¤êæÓµuëÆ‚¼À78÷GLˆ¹þÁ…ã'8•õWà&(™ÌèwsR½…xc¤ –q²±Ÿß¹Ð½òw_êîø‚?ú”¹Û𶆞ù÷ã JÉ¡Fª8%#U¨òb¤wlÄZ9Ø“ÉPÍ ëÉ€Tç‡v±°–™ Èknwr-6Ì6ÒØvãoï\ºÚKzœöƒO$O¹ð»‚Ëÿ—ñ 0` Æ?4±'wR]- pq¹q ·Î9ŸFüc #MlLôb‚Y•r½»,\¸Ët¥Ñ­cæy€·!q“Â*ëÊÙsgËU€¹yÅwJ×ödà :RÙ2+p/–qöÏEßP‡²Sëû;~€÷€ø^èC«ýOÌRî–NòmÖžÚÞ: ô>é¡©ZbÈßkÙ—dJ Y<ÃRYÚ“Ñ O«“%î˜ï^û?±xz{œ¸ 6 žYíø˃ËóEÅ{£z×¼G7å23Ü¡¦:ôW'±Wž MX31F›iõ³gæ¹ü¡îaÌôñ¼—þ+ÕÑ<Å3Oo óÄ¿ØW$A„r}º(X¢Q½úLZÜà¶(<…ä¹³ç^ÁàPñ¿2&Æs0ß0s§ÌË4Ã3ó.ÆŒåqÂÊ8¹b9wðg® Áš €e¼péÔ©Sñêž>ºÌ_`Š¿ÒúbEY)17¥(ãbì--¸ô À•«ô5üÏÖR·vr?4öâ)€Vñe¨[%Ñ9µxæ^‹/ùu Ërt›lÔLFîÃ^Ï\åuAÒó[È_-J¾øµ ‹€¿EÀ“þ¨Vs¿=0ücŸë=c¼Ž¯žU r°a¬ZÆù@´iQVl"™äùª_ÔZ¼×AjMÚJ\oYë®Ô|°{áÿîô0«Àßx7]wùÒ%4ÇVNy ¶ƒIÕ'SøR©SL'­zÕ­ž.Òœ•’Î|øw>£7JÙ~ÑKò¢Ü%fé…;±ø?^ê÷-˜½ßÆ`ðþ·ù´?œPƒ½GL8†cJãŠyª„ðŠA"àÅÊa®7VGy+Ыôù¤l­\)œjê1\¬Œ˜¨²Éò#²ÅвTJ&o85êQÌñ»æïè–qáY¿÷Nàå¹!É—x@Wè§öG|*ÍS„}ò²±+¼¸°²¼²täÈá¸Ú} Ü¥VŒZ4’ã#ÐAŠB¤\«!£`¥’£<¯gÐVÈ…µ¹—å «±øøà9:{éÝwß=\)~óɾZYU;L¹ ’²q"”§]ú—·[ÔÙ±+=¬>Ìo±Óù  ¿ÈK>kºWuÙ\»Á˜4VÍt€OˆNèd}¢È˜X•0¹b®é ­‘GŠ©Î{‰Â’½$%°j'Ø™·x÷b÷Æ?}­[¼_¢¶&¶ñÛÄ}ð÷ÈàH‰M”2î [¢‡{hp4h°%¼'[ÏYd‚Y„)ïmbü'óÓAk3_#e‘¦.šYv‚¹¹šËQ%¾þÇËÿêE87¥OõÁÂ$去pñ"®àäψá³õÑ÷ѹ ¾"®%ìŠg¼t©T]^^¹üÎ;ïÀ§ï—æã—IvŒX»»¥·<â×6ºŽÐÉâYi'ÂÞÜ@7Ê8âÿÊò®]z=kå;ßùÎa<´w.F>½JS¾ŒRTáFå-ƒTŽçÅó’Ì惀\ÉQG—rÔ“à±ÏÙ = d®<¶ú¸^ $cLY•-Â;«(}3šFôX9hZ~‹ÃZ¡Mè¸u0 ,´: .´¡qÜØòž…îòÊö"`àÔÛ‰àÏËþ“S¡ÇÈtoX¢`¦O׌¹m:ŸÁZ|ÀœD6(35ÌlXo²èMEñ‘ÎÛ¿OtOv»æâU`âL !è®xãg8ââ… zˆ<^Å"€ç„Œ1ÅIŠ;´Á¿Bç³kI`,°‡åUÞŽ>‡8wB+ˆ{~JŒ‰Ž‹ÆmŒù¬”ùë|½©ÉQßóÆ \ù®$qÃ9·Çd^ßXøt=‰[‡ð¼Äêü^¤[ØCÍŠrv5ª?eJ"©üñÑ%þ˜®ÐWwì_.xHÍ­>ÖíÀB€ÉƒGÈÙm¸Î%N5tzN —NÂ%ÖâI7׺m·…( ¤­Øc¦PáPH{I„€¨Qb¡J¿ ×EÀjÇEÀëÿøånaïöí€êŸÛØóåÝÝóúÈ3¤:r!ú)¦d8ªJ*:õtCd[D4!™šŠIg”÷fš(¥^•V ¨²ÀkªÐ @›ÁŸÁÊÇõ ±Ï…åºñïcø»õämî}à7cø¼~›FÁ_ñ¼ÎGl" ~PÈ]È8Ãär„H†rĉýçp±{ßÿ?ˆ“\>ùßÇ¿!L%ÇJÂ, Ë’ÆDº7Ê.*”)3à.hjZ™îе¹’óܧ׷V?vðò¥Ëgµ Â’+ÎÿiÞDÁ?Z$ç“•’øŽ,ÈÙ¸ÊÐá’‹~ØWÔ/Øõ‹€pÃ. ŽÇ±N܉%ÀVNÃÁØ´¤ñWpéyezï6fŒ&ÍÞ”™9zŒ}XèÔuWUÑvb«Œ¤W ØN9Òh­H—¶T<±’=²CrÏìèÞøê+Û‹€ìŸ[æ™ÿKÿÓóø\tš5cDhÚÆ™Q&ˆC„uʘK\¢ØMœÍü  öÄ,o=—P£0d s+‚,WT1Q[‘eGl鎉m&OÎ=Wx–¯‡=ÂKެ/N<'.œ¿—ýqÂXc |VŸú‡ìB^úçú_ÃÈLØŒ«ÏþÏœbHz¤ÉŠf|nâ³ËK‚¤l.§Â*wÀ ɪ€QçTë'Ó îà"àÿzß X—èönë{`üÙçÜÐ×eP¸×ýñžŠ‡ÔÐE'” R«Ö£áï¡¡±‚U›-ÞÛÕ+Ä|lU¹ÖfeŒ3°©hšÃQ£çìîž¿ýþ×çáÆÙ?óè#~þ—?#÷ôùÀ¤/àtþy ÈKŠKÑ\Y%1Ìtø! xíÌ;ï¾ó1â™ |Å s™-㦜*@,’K4î<ë™6‘_x³qlzÎݰœÓKx‡r+ßüæ7àéËÓÅÙZ9±v[1â+êã~ ‰äè2 A”³ƒéçóX°C¹PgÖ;Öq„Ÿÿ}ª{sL\îƒ?Ú­ùubŽÄgÄxGߥ)·Œa'’œŽä:%zñª±¨äqã_ V9Áy•·¡O}ãëß8Î â¿“¯ä˜X «HÂc)ËŒñ×EkŽ„uéÌÊ•šç†æ0­ò§O>J à #§ocç*HX ƒT±¸¸t ¯uà­€¸ÿ:ƒ>³²Q’ƒƒoð7£·rš80å§Ô¢NïÓ ÅIlÝàÀÞˆVˆ†‚ĸå>m×)êÕtq_@1'©é"yÊÄêÕ‹uU"ûlçÃ;ñLÀö" ÷ÔÖ†üÿ[¾çÏ)c›g`1>ê”#)kN8R5&È–ôAÑÈ›~‰1ÆìëÐ5Tj7Rl–ȱÇô5ó¶*õ8-šüÞb>^zêæƒ8—?>÷áNn,;¶!8×ÇØàkã|úŸWøÕ›pý+ƒ®Ü"²ƒ>À‹?zBrѵݙӧãžc_ Se­ "7&]ë ´K•È•%!óÚZ’ç·àjH+¢ãÇ>þüQtŒêÇÕ};p‹,ûˆˆ"åÊŠWÄ¥>x_ŒÛÐ媯.PP.žÀ`¹§ÛWJpÅè-Å!½®7…»§ºÇP ãµ¼1ª_˜ÉÆD™U–­BBò„£” 5eÉv½®<‚+\à'…·ÓÖõ€.û+ø³sÇ޾þïÇ?©’Œ‰âƒl‚$Œ±°Rø$U°XJã¶²š§*ÙR…,9@LŒBú‚zº Xiì7#hÛŠ‘-Dr¯Ü3·>‹ùîßèaƒÎËÿ|e±‡— gÃàߋՉ‹4ð (úç‰?Ÿ#˜›ÇC…GðÀ‡ Vãà6oMgÊy+Ü_ë`­brE)›ƒ¾qÊdº´rðС£gÏž=â¢}Ã!Þ èê(ìjÁ„…›‚÷:ˇð¤'W}¥ïQ vu,߇_‹æßVOÑ­è]BÂË$0ÊkmY ɧkmˆnI"ñÜAË\‰ç:©w{i˲ïkÒ¤gN¡N™³5¤%ÙÞàìì^ûG/wx>t;mAô÷ü=&J^ÆULßhpK°™ds|dš°:–†™£"Ó@O–‚(&õ°U;"ª[ [En ²c{×Õ6ÆdÔ¨ÌÔŒ(¸—˜¯9·ôV>wèÜÿ¿þ>Íùò“‚;<•\ÌX±:¿{Ž $§ \A„†jbÜécOÁO"XT>„'Á“6ò6ögbž4+œilvkññ@7²Øh–gž·¶¡äá ý ßÔêèÝwÞùôä©“ÀÃÑŲrß…¿Ž4ÿᇞæë€ k¡ ¦\-!*t|ò¾$£ÃôG(ñÇ.]Š©c4`¬ä¼  ü¡¹‡ôáˆèÀRäVϪïf5d ¡–­ž¡=ôèÉYæÅëPÝÝ@j5—môâAm']R‹Œm/ !0EÏæ³F~ ܆w}q®`Pšv·÷›Õ“Áßã³ïeÕ½Aéf©Y—9¶¢;>æŠg$³Æ ¡©°(%ÎcƒÔâd6´‰þÀ^BB Úë”Oª[ä½ÿ‡ç±@méט㉈B `<ü§·Åp•ø"žc’KOø ¹ÄÒËCé”!¹lu ÑAbœêº£GøàƒNÝy'~„¤j(öåXØÒÇH—5—tãzk£ zm,¹âæϲns6Úñz_÷çE‡ÃŽá=üd¢œÍG&Õ|­¾ 0Q}'A€ ´¨ ø‰G¬ðØÁ|ˆ÷ûÕÿ! °à ?ÌŸš{¬p(æ¦UÒ­ ´â„£ÛfÛ/½à`#¹g a°¤]dl‰ú†ÛÒ*ÞÔ31”i,ª¼‘2uuÈÆpßùÅ;ðB/š²oRôÁŸ}Ï/[ Ƥc™'ÁØ GLÈY¦j¤ñ×Ú¨x•1…¶&¬êÐs yëKTÌU®Í7b4Ëƶ!ýdl³Ï…¹~ad¾üÊ+Ïøk£Ñ~ÂL x…˜[Ž#쮾Ë"˜óc@r!#;cRŸLƒŸaƒ,>üwáì(d–K|s¼£°ã sâÞR¦"ë–Ýè Î0kBÜ´œ›Îܤ¼açâãéK9çøñã§ñóÀïãIÌôžÕPˆ­Z Žbú/õ ïñð! ΫÃuéŸRè|ý)G/¡H>8ÂoGoõ”µ¥ñ]í±ëih=BèñðzëûyÊKÇRÌã 2…òNG!8Ñ×®;1ɦ¶ tÅ%¥uzÉ9˜ªg®Ù]/ÝÕ½²½°w6]> þ¨ž;µt½Ç€©d÷#!7ËŠ‰‹±K,oZ¦…^ìÙTA)빬êU™¢[ñ¢4’Qwêñj13௅´e¶øZú›ˆÿÔÜ0—ß‹3Ìø¨®ð¢ç0Õút¾úwñâ%| p!|øl¤íì3÷››:\€Vªb1±rêÔÉ÷ðàI +%¾‰Eœ"%GVaó›Çœ)ÓƒÒïÉ[wÚè`b¬5%åÂs% 3g£¹1fÎÀÏg”9rää±cÇ>Â@‘÷s@ÿš‰±D¥Y$ |‘ 䜆I£"Õ‰_äguö¯!@{ Þbñp÷Þˆ·ôÎg)†Ö¶RRû6Ráf‚£Ï©Å+“ŒÂD¶Ö` YKõFõhµ>\r[p«Ê²‘JíÍåJŒ»*`¥K-êºûåÝÝ+wûJ@öäf€ûà?6­ÅQÀ½ç†¶Î1w$*i1^<^k SôEëeóX«%@†UjÂNåT×§rZü{­ ¨êOp¶>ÁÁøÞîÞîÁùØãr²ú^0ïûF”o…16œÃÇx ~4Žýñ$|Îxcß(HO ’ÆØüî?eÿWp2û!žÿ;íß§†³o¤gØ8óœ(sMiìH™eh½¹Ò³rÅf–·á‡šAãý’>úè8/d {µX³“YQ’b‡<êï‹§/ûó9~剅ƒ¾õ98ðÈ7÷àýÑ—º—5TB<@ÂVMrZª|‹'Ö g–LṈ²š´z-.SîËV³­;hô†v¿~—¾%?[o›ûYy þîW”\»kŒ5«"(5®ÔºE`˜¥2RK[[ybJbpL§¥­… NA`Ã-’Dmçù-FV°G™¿<ÿJw÷¾þ‡sNÓ:pä€âŸôÁ/gΞÁ7âêpÌ ¢x¢Y/¤àS’;ùÌçtzÊ…ÀÊ'Ÿ|ò£÷Þ{OW|‚ ¡AÜ ¥l”]Å x"6;òstofŒÁ]ÐF[`k·å»–3ž@šp|ˆaJÐ}Ë?üá?ºÂŸfÒrŒþR ö7‰tyà”ŠŸ$®p%dø£ Wu€f¹( ´`t_ÉîÅ ÏáÏ¿ àÁ%á-¶›¨{ò¡šÒâkµ¯•¿–‰D:­¡aÁgkƒrJg5&H-]Ú«zEG2T¤güWÄ‹½$)FÿL@ìý‰½Ý‹ÿÃóEi;û¼<0üÑ?¥『ši.H–Ù_éû›P– }+‘Ë416I´aN"BŠt¥S¨O"WeËViE¶Ás[jëïíojÊéÛ–&æ‘ ps\?ÎÙ/Ì¿ÐÝ7w Œ>PàgÀçüóËÒÝY<ù™¿ iÆ…où"" pºøÐ­,|Ø?âLih˜+T¾;sî‡oýðCÆ3>Èø)n2…¼ƒÅˆè*Æ„¢“PѸžÚêôœèZ#fDj 6žs6–xΠ묿Ð냸l‚'>ÀË)¸,3Åý\“:*º"¼)&wEŠ„Pîx9ïzâU~íéÌé3ºO£Ÿ.éIöÚžÝXB•ñaˆgæžéoØn‘¿å²â6µK¾“GxœT—În³$«È ³¦ÙФÕëqR%¦n³¥\/Ù±¼À’§P³EÅNe‘èÍ ð‰Ê‚WØÎe6\:íÜý•=Ýs¿ñEÉnï>{ìùqþ¤ï³ZÈ×ÒÕÏÆ¢¯gkË2çXRObK¿³ïI¡=æ…²$H­õµY‘ 1fÇbÈ«‹5hsní§.ÉbéÕ­šEˆîº}¸ïÿìÂó˜Ë»¥Õ%½ã¯É°§ÙÆyþ8îן<}’ë!>ÄýÿÚ¿ÙGpZ, ä=¹Èåå>cœÂ-…yÈ®ž={æ“ÃGŸ†ð2l;ÎÑ€á I7Nû.È9iL-Ôétó'òë]°"¹27ìœ ›¶QÆY-«¤•¥å¥¥ƒþ¿ŸÌ3éüâïb’éè-D‘‚Œ½dbÔ ”÷{®^¹Ü>uJIœÉ+²>ŒA‚ÕÀøŽô3¸ ÀŸ“d)yú—âÚMÔ]ŽZ«!”šªÑê ï%ÊT  tÙÒdKÞb¯& p!Û1ÃUSz£%R}e[¯Zª_H’vï/ïë^øÛÏÖ"¶ÏÆ<óé~?éëžAîÎÊGj+–Ríª\#¤©Ó›ú×±ô鋉\Œµz6›ÊîeT¸%†y–GsÙPbkt,`r®©y½ï ÅÂ@9Úë±Q6ÀúqŽÞ‰Ÿu~þùÝóÑX=ߥv”hlOùµ><¤§+ºÿ¯v÷ŠØQb yAPYÀd'\%&p=ÀŸÿ=‡àÿâ¿qßÿuüc¬£°ƒ½é9cÙü†úõ§ë]L«Aø#*L7&Ë›ÆÜŽ cð[ ËW‘/Ÿ=söâ»H¸4sNÇ¥úâ@„CHïk§¡±(Íæ)D"_&Ðgõ{§ÏœÁŠïj kú¸`CÁèøÜ5bâ ñչ׺=ø^èBn·œÛ4º¥ ïA­LcBýµm|ÒíË‘z-›ôàyœ ªPåÚB{fC~‚SlØî=?ÇšÙ^Øe7;ï/û§’Üy@5AXÒcý9èáÔÛíâ¡–1V.iI7‰Tú4!3c¶ªà€õØI2ªá˜_r‘I>“73Ìwü™öÎíé^_xwýù]^Ñ],ˆp¾çƒàÌùyøSxHŸ_ä !÷žá§40ð‘ýr7ºt"IÁˆƒŠÛ×gô£÷ÞÆUl~X§³KW˜Ç<ç ×Ô–J©Ü!@ÙkN7c0Vù\IÂn4aŸù›Æ•/—¬ò•‰¯~õ«o]ºx‰—PØ ® Ï‰ÕŽ¢€M–î*2¡Åa<ŠÁt‡Ž‰ßàeмJŒ®Ù‰KH¼ ÀçnËT& úG©&ŒäÌÄ”Øð{»dĦ}‘+Ù”{®Ù:àPTªÃšäú‚ÝžQ§r—К»æâ²ã/ÜÓ=ûß|aXÎ6vÃ=ÐöYé·ÚÉ<dor¶èû¦Vƒ©µÇ ³ÈV+£ºE¿¨Ä2RvÌC½íIs¬G-­ZlJôR=ÔʵxÕ+c²2­ú–ÁyßÿÙùg»EÌÝôy<Õ_ZŒ#DtÜâå}ÿ'OééÔˆ8Qeª1¤‹ú_tŒ) ððà%%J¿þ÷{¿÷{?`[\\ô•m 1ÖqóbÀ´0R†Çði4Ò×®uÐVnVYÖAž4;!Ãu1€•éÂOž‘äÒ˜÷V2µ—7$9‹Xà°ž ´n–íí„f?:«tس“D&ÀV¾Á'æŒ ›ƒÀ¹™Ÿþ}—ÿwÏïÑ û¬^ÝåùÍÕâ¹@IDATc9ïó˰xMOñ€gÿ¼J,¿Âºãœ|ú¥Ð©¯¾‘¯H¤<7Æ—ù¼ý·zúô©O ¥V♃þXì£%nä9™F<Ãæ_w~­ €i³’N®p›³¦µ°ƒ«0xr#œ·tàÓïã̓À9òUF¬¿€Õ²z€‰ô¹J++6²¹À“Ÿ\\—¹Bô!E^0”UšëžÅàQ|ø¶Hvß ±\—T·´ŠÓ³á]KIeBG<*ÖÞ\MY·% ðRhq°÷ÖÚòUŠ&úGµr¹.°UD ÝŠÒí(ÔñþñÞî‹ÿåÓ®ív~ƒ<Ààÿâï<§w¶k÷ÀvŒLô“ú²Šî#ˆ‚–r!(s›f<ÆŽ¨Ü6›£qÐÐT¾V C}Ê;Eùa×´©y*ÏURb$0ׯÚ$[Ôû⣿"[x|þ‰î9<ü§c_¿´ Mß@ FÀçOÄŸ8q\âÁ½êB»•Q#æ ShrBƒ–÷`Ra?þsàÐÁCïç_ÖoÝ8Þ9§ê4˜¼¼UŠâŒõù4z/1ݨÀ¬ÂÍc>ÓZ%¥œL ]¼7ß|ó=ü:àazÈe'ŒÙ$¤•ˆ³"\p’¸qpÏ>ðãK|À?F „xˆ÷ŽðšV…/οÔ}aî °ú¶èŽí$ºj­TüVÅÖ¥Ó”S•ÐÚÎ]ÚwQ²ñL- å5’¢j(]—W˜F‹ ·Ddì¼H¦zðÀ¿|_÷ôödËÞÆ¯Ñ þ§ÿd#ŽeìÑ'êðØW„k*¼Šhuåµ ÷u? yTTíP¹¥2o?jµå¦ôzѶZH’©´€ú]|#31WŒØß $×ó ó_ì^^xƒ¡£Üÿ‡¿éùQpÌë| q¿þwNñ€RŒ Úè1ÈJ0ÝÂ}¸§ÇL zaCéüùs‡Þ|ó­w ¦8–ãY¡9æ•^1A´Øés›]–oXëGoÔ -±­qota9f ½®˜vìØÑ}ç;ß9ˆÛŸJ gîaX™ÍÕ dº»vƒoD§Æ ð 䇸KP±3ÂYUÑaû‰¹'ñ|é.šÚòÉLmHò“h-N"hq@T­!0¦3”X†FìpnËnñÐËÊ€Zå½€UÓª1‚”ì$#ôú7èžú«Û‹9ï:v5ø/´ý<;e %†}T«0Úד²¶ªõ킳ôÉz[Ÿ¦M£¥µ:cB°“J“¸¥h¼üÿÔÂSh4Z…k:€¨hwÈIâ+ÞxǼïÎâJ_ýƒ|ñ»ônŽT°ûzÏ[içäÉS¾õ­o~Êø…Äxæ€?÷"¤M–è˜×Ò2ñzáëYL«é9'lœu6ngØÆé¬Á†oìÇkgÕ‘8Šx ŸVâ¡ šd*E( 8:DYXWNQ > À¹ ܱ¸ƒCƒã%ýáö)”tþ1¼ð*J Û–—Ò­¸‹f6- 5ÄÕ!;¼¥@>UÆfP†&8÷G¡=ež¨ÍØÄ8V^C“êV}[†`­‹ÇH¨’L4vj  Ã¿‡åÁî©ÿä “·ó z`/¾³ðâßÁ%ÝÁŸ=Ånp±ˆvœÅ裠¥jR¼tf•гþF$-Û0«Ý*UlŽáÖE]Æô’ŠjKñµÒ2nõÀÌ:ÙMЏÇ8'¿¶ðz÷Øüãª)áñð§îâ¯ø9íØ¹?Ñ{º;qìÞ<]5f`cœàŽNìиB@^á+¿þ3Ç· ÎâSö¾ÿŸã˜ãµ Û|o©X/¥ Si9'|CÒõ,ÖS6*'7<7ÖŽð É9é^9-8pààñcÇ~ZŒeöLõ ‹“ðbZô¦%(JÕ8³çm~Š—ƒ"e— (th<4¸¢ÁÅ[N|Æ·Z>Qºp­v€:!\>ÔˆÞéi’±`ÏLÇbtL¯ÑCTSê³YvåDÒ¤J©^¯â!=¦Ÿ¤!$‰ÞtSŽeEÆŽx©tü›uOüG·Éó#½Ç®Úû|i‘÷üûgtágüy‚.%d¿‹T î%RA»*öú‘BN›I’F’@ƒ–Ë)t(ÊÐ!7YL`µVhÔ‘^•©Õ¹ÊO å=Ó„ ½–EÝø[Cãsg³–N/-¼Ü=ŽCv¼â‡vˆ]|Ùr:Çwôuòwþ>1C!µ=¼‡GuGÆý´¿ËU8á i¤9\­~çÓŸòçìý,Û¬GEo^ SʪáLlàõÈ4*æh7*°b®„duã–±CÜàœ›§…€ï|ÿûßßÿéÁO±€ þëÌ ÖN°vìOÈSÿ«)t’ò3ç°8} oB˜OÿsôJblBÐsðcswO/Ú=òï>T4¶³ÖüŸSð¯ 0;°]‡9èô­·*6ÑwÅèî›^¶B(Y‹•ŠgÂ6ÊÏ"š¨-6rµ@e×ÊŒ”gsYmM¸µK…1Úš†>÷âÞ¹»»/-¾ÑíÁ»ÿLú r~Ê®“8t ¿ùgÊêÃߌŒ#Õ ú³ûpy’€œúYP#QœÁ~—ÿßbœ‚ø´ËÿŽsa²÷~Æ 39l|¿™qMP¯w@ÃÓ*ÐÒ‰{£ž‘sò3èkÃýy}Áÿ<~Z_VZºLSð;œ ˆ[>¤Aˆ¿àÕY󗜀*Q–ß8Å/Bá³Í¼ àë dü#m™ë Þàë&¤oõÙlKMì…µR‘©“ܘÎ-Ùe™<˜ªÄ›£e§.)¢V kC¢Nà”.©ÈZŸTqe¨·4îef $Ô¢ÞD86)Jm‚ÌŸúµ'º‡ÿííE\1H þ/áž?5~Ì‹þbʹ|ˆ]óÁž)ýþÖf-Ò OÆ#]²­ª &zS¢,ÙÒx2ùX冤*ʺU¤Mµžëz­I(‰©Å#õœPJ:äMzgBcÓ8÷²¾Ï,FD.‡0²ø+¥Pš©tŽû”¹¯pPðò»ºtÏ×§!5€¨ªA$+ÚéG%`&ÿ™ù?…+O‰îÁØKÞý—›2FË|­L‹K†,Cœ©•kqÉX/159Nö€'eÙö®H]h5—‚6iÞŠ¾K ¹©?…ò1Ê¢f!ë1hͧÿÊ“zMÐÕ»]ó»ù´??ò“^õë}‘RüÊ<|gé Î´¢Íþ¶®E‘‹„Õ²–DÈœ–&ÆO©[‘/¥F¢ÑXªKk·”e=5C2®FÊlÍO¶fñ¼¢cÚäQdÎæË9õ3=…ÿ~|Ç—áåð"çhúœsw¾ —¿‹øòN$õÖ—î†ÓØiºê$0­÷)dˆƒçIX=„{ÅðA¡ý_ÿÚ×?„Ðò®]»æpEÀ1,ç´ìøçRL«&ó*áf7càÑ;­¾l˜g˜áFG‘–&:.û‹†ËƒËŸ|òñ›/]8Áö¸ À.ŒS¯ñç`"þ&ÂM3ˆ¤£Ó)_{zU? qìØ±î<.-ð÷¨¢Š#j·øhÀâüŽî‹¸ pgw*Î*nýădè¶6ÑŠLÕneZœò#4W¹¼™¡^)1É%0[êá*PॉÖíh$$7¤Y²7¯ (fOFêü+÷g·Ì3ÿJðŸð&ü”\5â—5¤3f·5ÖöKË Ä¯ƒ:be´: Õ‚Ç”À#ZmgËÕ\ ŒÙie61¾‚qþžY|¶Û¹˜ó´æ}8A'n¥îš¿/,.b^¿Ðáþ¼>ÿË3ÿ˜ï¤¥0‘.OFx€£Jç„[‰‡Œøqÿîâ¥KÇì?ð=Ä'Å2< ÈÛÖŽiÎéuÇ6Òˆsc2XO7μvq&^|£nëÒÂn¤ég£ÝpÓ›G‡ñ§u9…«ªo~óO¾‹{8ÇÂ}ýë½;‹)õ*ÔÆ idÄê..=zDŸ^\Ü©'$0®z¯檑¬ñÞé+ó¯ÐÊ-“Öµh[«##Æfì[àrvC¯´ð)¹Z£e±*“ˆ;±?{§ · [6Qh£‘ÓFôµõIW{Hh“…&êRÍO ø¦ðéï/üúSÝþöûµIŸùëiÿìßê» &Pª zÖRÅËÍ>'ÜŽ kWõZ¤ayÓǶSE óLÑ3ÓÕ2Î<Ñ(‡#ñ©)éU™B«zM]«\FìL÷Y~“¯-¾Þ½‚Ïþ:± œ¯êB4^þ?‹¯¾:tHó½¾û¯C¸Žƒ6C8Ç{æ HEÖ®ã8"©œv—.\<ú­o}ëÛ嬿ƫ¢žcMxsŒ#Îdz‹7øc°iÎoÔ -Ø ˜E·S(“A8¯’´@¾Š¥¿—¾ûÝï8qêÞ³T1¥Ë%•®ëÄËÆN‹€È$+¼ púÌitn¼÷/:FUüaP.1ñJÀ£óê»Ó¶xÛäÕ‡©Å¢…µŸ*“t*Há^¡ï£"гªFLÊÖ+å%äM¤z¯ ‰:±‡nð zȻNjX¬"yLYk Š`ˆE³DÊ„'‹%}ñןîîÿs·Ï" ›ÌÙ÷ô‰Sß Ågd*ÈeR¸ú”|â#)ÑÃZ±žèî—1#¥#uDY7úSA(õi«1(+˜¶Ë<ô©-AyDoj;¥Pv®N¦50ë½Ó+‹¯v.<Ö-ñwãЄ89‹–8ø{A€_çÃçyOêò¿x`ðö¯Ý*7 )ó@Dnï „¨,M øa¹O¾ýíoïg| jþ”}Çóïh€V‰;KÓèc²ë¦Ý¬@[\yÂÞ(—’Fºñœ^:°ÿÎ;÷‚ð: ûC;Y¶ñ¾{Y ùÌÙ°·$餮ò3Žxà$^9 Ëü}º:ñ/8W6üòÔ ó/àŽT¸r«Ll¿ÓDÂ]fG>•æÃe(>ÓdnnèŽZ-Ï’©H«U­.«äôB¨¸Ï BžÜ¼` ´µ¶Ä]?"Ф²ÌW t±ç8{æ¿xº»÷Ïî“æ­¼‹þ&Õm¶?ûö'¿Éw”P¤)2ÙÛ¶CIO E’Y¤Ì¥Xp_ºË3ŸJk * M¥‰æÚ2o„f”™µÂ¾, œºkÌOëS?†ëʹöÅ…—º'Ÿ,&ŽÎËúSÔïð¸ZÀÇøÉ÷ã'Ža~¿±Í¡+¼©u@êa_š+6D ›UŒóøŽÌA|¤Ž?ü£«Ô\[Î1‹¸7ÓY4iL®s§ ›vÃó¹˜VaÓ™ç±S²ŒivTÎ k{ó­·Þ9~œŸ†jȈß6$•Ž÷à:Ãâ5ŸÓ¥".Ž>ªI9†TYb0Ñ7*üÕW~ ‰¾¤ü­&ÚAµiŒ&§Á1¾}mM,Pú4ݬGx ;dÚ„‹¤Íq»Sú¬ø=ßPjµPñ\‘Ê*@Ñv…D­õúÒsý‹Ý¾_º§U¼ep¾¤=pÃX ×€Rãg{³CÚH)ú„†Ž‘RôÊ™Rp×2`g‚6Y^­G©Úõ©O/¼A¨±5qŒoÐÜg-î“,ε?»óŸí[xSð\·ˆ¿ðZ\¥;Ù{|ýïðáCÝq|÷ŸOþK°æLÛ:y”8ôÀB þ¤!=ºÝË'͘(‹× ß}ë­·ÞZã`„»×Ü ÌMè ÔÜ4æm²–¾aüF.6R¸s:ƒxÞ²õU@ðWî¼óι7ðƒÃ~øÑ[2€aÇ[‘H3ÆÙIñýpAÿ%1躣ɘǗOž:ÕÆÃ"K|îv½œj@øê"^?yná¹îṇERnùÝÄA‡¶©¥•É/¼á–oý–.½!QÛò·"A­.K-%ɭˆ…J;µ- KèX9µ>Èð@V«n´xþo<Ûíû…[o àÿ·'Òwà{»wÉÝ;-ùoØCĤ>fcÐÖs>¦PŠËRÎû¾­”T§TíÆ¬¤A’%[ÉÄšà ]}×ÔÛB>Ézdþ‘îùÅç5÷ræ¥ÎÍê MÒÑ(^¼åvõê•îÓOv'N¤_þƒˆ=NiÂ=ˆñAŸ$ ¸øÈñUxÅxü6 >Uð®»îbÓÝóówGa·ÀÞÁßü•–V/W?Úƒ6ÛÛd"¹_štòbÐ-—úOòyœ‘0Á'-¶=ÐâÅ…)!HmSC“B ›Å²Mµúc8d{]@Óêu›zdÖV€9§rnÝ3··û¹ºÛ»ÀcÇ7ça¸@¹p‘Õ¤œùã¡<=ùöìh3JÐ RÙù•>>¨Ã\šîvßþƒ0å‹@|šˆ;°û!âÐpýÅÚ6^QSq ¹ã˜sÂ2”TZƒ'ôÆ7zÀŠÏJ™o˜Îpƒ™gÜδ™/ù K|ðáá>úèOÐMz3Ö6K«=¬Î‰©§f,)¾Õ±?yáâ…îã?Æ`?ÌWËmYˆÞ9}×ܮÒÝ?¯sÝJ €Æ£ÑCç­I ¯BlÝzU#lcÏ >‘fÒ&mäòÉ•lLHr2­¶'¸Á¿áÑéÞ ÈzSRCuÖË0%¡\ôù”ò óÙnï—ãó¦änÕÄþ^™?ƒÿÐãl/hÉg¸¥–·_(#’*N^äïsÈB0ú…¶\í’D›…Õ+” )"ý”2Â\CŸ¥ª¬*Wê41.Š@S„¨ Éò§ÈHvÆn⸞!»XžSÀûÓwü´æ\ž¼a‹Â3Îùï+Ë+Ý{ï½wô/twÜq¿«æH²ø¯wa,z¢@†}Î…B|ùÇôü'òõ?øà$—ñ ƒøBm ö9v•’±4Æ:&ó ‹øYìnô`VÜHæY.ãþÌí@Â×%¼Ö±‚ §Þþáß‚à}XÏæ¡P¿˜…„LAœQB$âïH_¾t¹;ŠoàAC\ âU€ø  ÎþiŸ–ž~ù÷$ãt³œ·±+e­«QÒ™ŠåJêpoÙ!õ±›±HGÆh­ÜætPnt ?dåX8ziaa¼¥÷ÞÿÓcGá÷V–¤ÀC²Qvft(KÃÅ©¤žG¸J:|È/svŸìßß]Àê‘Ƶ&MÌgCËÜ‹99NÊJΓ2üíØ‰÷þqòöáñúð«¸H¬wr‘J ,Šl’”`òìV’åñÅÊ…å•å¥cÇ÷½÷ÞÓåÿÅÅEÜ-Ö—ÿrð7ۨÉÍ1P%s7#¹*3D6ƺ €\É çš™Îœ›`ØŽ#›qÝû'?»ÈO`ž{÷Gï~W_ÎGß±—ûnã£;9¥Dˆ]íVôª¢ÑmàR< È"p‹AWvàKR|@kIÁ2¼ÇÏ—Rÿ݃ uÏ/x_ Îçúî¿ú­X,eG3ü0N[¸ü Bäô¸,¼rõÌûï½ÿ |9öÈùÃ?ŒUÞ¿r s!QdßãQ ¦LßðššÄnÆ€¥äJçR3°qÃvqÃ΋~+¿Üw¹ò‡øGß=wþ<ïà y¸ÀW²ê"‚ËîÌRæ*—<Ô°†` çÂà~*ø(„ýŽ•H¼°{ĸ…û¶x?õÇ< ÃÞåUÂ؎ѶØnW¸Äœ‚&ºì1°[=jL¥5F!×P¦è–²v©9Fg²A zÖ¡œ Ä:qÙÜÖ”æ"Cm󭣜DmÀR€˜ß1ß½ò¿¼´% þ¾ìq£Øì>il-ã#ûÁB¤U…H÷ˆ^X…<ðWÐ’”}Z…{‰Üoc’x€eŸ(©_&DAí±AÐãø5™JâA€@Vv*ú¤¤4f/•kÉÑã×ÌM˜çú~yçOt//¾T|Á«®<ûŠJS.{W‡qÒv®;‚KÿçÏžEÐFèàÂ@Ž/ …tâGTþ#!@‘0(È«¨(£ èç/œÿôþè¿ÍøÃ8TNH}‚êXåØÅœ¦Œg³fÄ?³t³¹c $Í}æ[ιÅ<¯¨/¡–pÙE0¾ï|úð¡Coãuóü5õ'\ ïBò,L+Q8€È&ÆÎ§~ùgþxͯÆÃwP$W€<ø8 ùS”O.<Õ}eñ§ñ›T¬Æ­—†‡ÚHûÂ}“ 9Ù]^ØÙñÖX v lj{Øy62’W»V*·%€ö”€«ÝªÓØ¥—¶ÉëWG”ûv‹­ÝHýK1”ö¦Éž>tæwÎaðbwç ›÷J@üªŸøcCSR;àåc°ÜNæ5ÿ¸c´zS.¾‰q%i‘8}7°^‹±ýžõ!½¯#u“>MŽ¥†.†¦úLЊ±1:h©ä“«OC[ó¸mä7ʹôgvþl÷ôâÓ˜aýsíឨÉG ðDø°¾ÌÓ}øá‡ø¥W\>#ó:]Ç}u!Ó«Ÿ M†(YõDYåmfü¢à¹£G޾yèðáÓ 2áêÿŠ~²x¯ˆGÒPñ®‚sOØ8À5qÊ\wú,Ó*éÆŽåvšiÜ++~ÿ> XÆ{˜ß9uêôt¯Ò › •²™“›È"wí(n@¡è~IŠ?täpù.¤x÷‰=¦ÁWGt$ßaðd÷êÂk²Ì¶òÁ§F4»knOuv2¸nZrtRŸ ŽÙ³ü”@P¾åRÞ‚D$èÀÓh‚Q©ì7×[î°•¬š n9ùÚüŽ…îU,v=£Uãóâ²?Òµ¶”£åÈÝ–¶ª5Û_¶Y­b3RZI.d„MêW䫪¼¤†,·MB@óǘlK#^ËLLÐúÒJ]¦•ŸÔ6özÏ·‚›g=;¿¾ãu|õïi8~Àæy—2ºÿÏ(&VðøË®x5O'm<1ãØÃe€`¥]¿þÇ1dô25dÓE!ƒsB†¡Ž_|ñæ[‡pò¯þɱ©WÆÝ3ÓršÿLÓçµ ²Œ9Š<Òåì’ G@ת‹~ík_{ÿÐÁƒoǤ)v1ŽrþEIP2gÎN×à N™« ð?øèöدKý ¼ü„”>᜷ƒ_¤Bºgîîîvþbw÷\|Ì… †[:Ù¡¹‘c4òA‡«†iLv”6¡9´clL×¼)9-k!PømI™701Z‰f 7È2Ê–mTv27`ׇùü Ýkï•6Ï" ù»þ®µñ)ùÐEBöÙ¤µIÊPy’/Ê ?‡¾õ’àzƒ/ $µµê3* }× êÙ\Ar+&¿öwæÐ_Úõg»}xòŸs»¾úç•ÅQÎóºE‹6>·Å_ýãmYžôyµž(NCF¨`’3,kå€7 (þƒí·þøÿø=?ˆ^þ‹QT° æ9¾µ<ËAì³K732MkPÛp¶–Žiév–ÿ¾Œ L¾î½|üÉ'ðÃ@èhGûè3Xö8`aLd¸C+…B% Ü Ø¹c'>÷x¬{ÿ½÷ð™'þ°J@!R„cã''ùµ*þTð‹¸_õâ‹’áOWnÕÑþX3ï]׋ŽÑè/L¦þ“¥ÑçN„i¶‚³Ð,YsNäÞ¤Q9r §½œÈkiâË^–4œ¤Uî˜Hž%ÈUr@³<[ 9Qna×|÷ÆÿŽEÀ“Ÿÿ"€Áÿ¥¿åËþ}ëܶ¶îj'‰¥}l²ic:Ò§7¢&Mò¡íÍìœWýž¨rE/º•E"Ò4{…B±×ø(ôhGìÅ¥6Ø4Mò¬W«|´‰clÄÌf$±ÞüÂó—v¼Ü½¼óåøê>ÁÇ N:O¾ìcAð/ÄÍøî·ßÁþéîÀ÷\øà!º«º - DèOøB¦ô•¥_ssˆ/à–ð{0ǘÃ7Ï—£˜Å)Ò\üš Fç«ãbgs}OXŽ¢ÄNgRçGÆø«W—°8¡•%>D€×JðVþû#€*oppîÀ'‚õ½êùÇľծøðsë•W'&*iSèvsÁSä’µ«\·iåŒÒi©ÚiJ³~5¯Qê`16Ù[ÞúÊ£æôeëO‹U¿B™“dm‹€×ÿÞËÝŽ‡ø}óÏ'åàϺ־(Õ1M‡UÓË:ï[o@–›úˆ¹’­ILcVz1Ù’ØI¥êY—&äîË ÷+TÕªÒÜ—¤Î°S処e+“ÆäÖAkÇS6¹ÙaŸýó[ÿ?·ëç»sßpŒ~‰µ8Hó.a$Ž-~ô¯‚ëÒ?OÖðm¨ÑU1öbŽwà½0™Qw "“á¢$œÊQ\]øã¯ýë?äÙ?ã¶‹ÀvŒr°Q1…ÞÒÈcr^Z3  ¹™»›½pãÚ6˜ÎܰeLcN§MÛªã¹صkW‡ÕÙ…·ß~ç­ËW®œe¯cð6@µoÃ*¨ U Ö  –2¡“µšÄ'»wß}·»xá.7áµ(r jLÆß¢/;~eÇ«xrõ=˜{×Ålõ<¦ºØ×¶L8³pÆè˜dé³ä##Õ`Ð mXÊ|>f—öfMôdCª¶e¸;hjdw@I­”Mv.HRµ5Õ7Ö`=T—BX¸s¡{ã¼Úíxà³_äàÏê×&•æd\Í*»Ú·ÅÌjƒ€S–.î3çmrŸ É_‰'ëØÝe9‡`“Z€c¼HÙX¿±4ïÅ9¦¬1ý@1>F+w Ñøàßk˜?_Û‰g©8É¢30·²ÓtëU¯ÜÀÃ~ç1/¿õÃvW®\íî@<à‰šûB.*ÇØÐ]ÀD*÷œý#WÿáÙB}fˆ‹Š3?úÑ~€8sžñ†qâ<)­±ðX¼ sQˆÔ…šá*`=Ë\3|³³*æ†åœ°7;’¸a_ nX€Î¹Ú¾Êtôã?ùÿð`ñ9oÛÊ´ „À ÍtäýqGYüßà—ñõAý¦4W˜\pDÖÉacš>_Þñ“ø± çQYV3ènå°ÞgÓOT™©6©ÎÒZKÌg{CN™úNO*ñÁ‡txã@¯J£†ã4)?{о²òÌÂ3Ý;¾äbÂNÅna ;×Í£‘‡³?ð,<’OÕŸ"KylÕvÁIMƒI×½ }È×1&«º¤l·R€v½U5£Œ \¶J‘YÂ9e 7ÔÊ~qÏB÷¥øZ·xßÍ_èÌÿ¿{‡jÕT¦´¢Ö›ÕËu&_€A]oœHISr¶>!(‚¸S€§püĉ·——W—ñÄ>gDuiÄ&A;‘úUaé|–ª~²°r`nnA?-yèÐA]àÅ!­F5ûhXjJ m—±xEàU¼Æò¸à´•R·¡ÍGÛ$?6’c4Š`Ò+.Œ³*ÊÉ΢§¢h«ªW LµÍÁÊQ7Ô±Ô³5);·!•4i[ÉÔ¢S¶h+–ì²õž–·¸w¡{ýï¿Ú- ¿YIgþþ˜‘k¬–k\‰R½]³"ïíN$ûÈúAß[¦¨„ ¯Læ4<`G9m’H#®v‚ö¬W„Ùde¢Xñ(ßê´8×I=UÐæßåºÿÔ_éÞØùÆT™CEêØ‡ëÃûsš‡|z Û϶ój7þ~‹Ç›û>äáLèÒpkvnÐ{o­â½ÿ9¼ä¿¼|òäÉ·Þ|óÍOÀcœaüð Ç"Ç)ç.†9iÆû"\Lù áÏbÐ6'{œ<ãvNÆíȱ܎wGøW—xùÿÛßúÖÿ}áÂùƒŒö|TÕ°ñ®åA;(^”8€¸pÀg õK‡ÖG'øô)' N€ЧRѼÇï~içŸÁYìV±1ˆ]ƒ['mWëp6—´Qz¢ŒÉÙÆ@6‡ú¹>1 xÒHгì‹g›ÎYŽæAÈ•u ße¦Ò†` rÐ4<(ÆÊT,Ê‚® ‹0GÚ¹o±ûÒ?~í¦,ê™?‚¿Úª=`×]uq[JÕ*ûЊJ“D¼T\IWøŒÔè_mË–eQ<Ö3þBÂRÅn3^ji4à­©)m·dZË–UœCµÚ¬´¦~¤Ê+‚#4¶r+'×7æÈ?sç/wïxOýÇE4 h"[É Ot çÝ;wÝ¡²?ø÷è¯ ðsÞÆ_ÉC2öšÑ“ïz“ú0ô‚Î'ü(pñâ…ßýîwþiyߟqÆ—ÿ{‹Æb”i.ÕÅeœE;™> 7ý†åŸÇ€•_«¡äÓyÎíHæüv:sv??Ìï2_ýƒ?øƒœ:uê#Ä“tˆZr\ø‰‰Äø‡Œ.–W¨ƒ ‚þN½ €g ¤®ÏRbTêYê—‘Ê7â@|hñI<ðe=èM[·Zò©„aÝÔ›¥‚⸾Mž´2(ëEÝA?[³¬xµ ÛÒ,¯lVrnã%gÙ²¤jÒs¤“i…5Å–¸àU›E¼]¼ZÎìš«0zÌ E65Æ Ï9‘þýä?¥/þÑAË|íOó*§ÔX×\ñ4nÉòLŸ_ü;xðP|©Õ~×ñfc@ˆTvrŸ"Kºî˹ nq¼Nm:îÌé3þþïÿþ÷WPüÙ_Ç¥6&™®’`Â¹Š§M¤ ¯—ÒÞݸâújFgx³³hÑ4ÃÙ±„íøÿŸ½÷ü¯ë¸î½½vФª%Ù²\®í8®r‹Ç%îéí~ž?)Ïó.Ž“'.q"Y’eÙ²©N5Šê)Š”Ø{ ’h¼¿ïšYûÌÙØ©B<ì=kÖZ³fδ53{ ÜÖHöôo¼ñìØÉ±·TPâï¤,xN#£Â• ì/æ½ò_RÝKä‰t­1|رc‡}w²]&J~èÐs•Ý¡›\ðÑÙÚ¥-¾Ö¶­%D•,+[/¶×Ûþm^yó„ñ ÊqÀðe¾’»2õç’e´¹Ç¥2¬R|*³ÒÁ%a;\öñÃíáƒúÀVÁ·k åŸMû×d’&åtq7vU<…sTÅS Á ñ—¹Ü]ˆ*€Üo YrœðÎ z=‹ÿ…3Ê®gÌ‘6ËǬ|žGF&ñm×µLÖ¥ý70Húbï—­­$ ;uì5¥$˜=z¥¶º­½M#¾)íÊÚ®©ÿ=Z1ÝIüøÐnëËmQnÀbÜŽ®½ËXl†¡Uw ¨±óñÖé¹]÷`»^B4°ÃršÉñŽ›U,œðnÚo¿uXxìn2hZ#wË(jÜ{ï½OéÛŽl” –Fõ*ØlÜÀ‘!Rc7‚JG͹‘¥™¡N}:ªÓ_zé%­:°]Э§*)V˜Tô˜ $Ý i÷|¬ó:!pP¥gqä•ZI'¶»ÝŽÉ뮚]à³zQàjl5Ä[«!·Âëy.½ÉB õ,zFfJ²Ýé1 8ù3[– Él…»Ò@ðXD¦Îå]ZøþТ;.Ö ~L‡ü0òŸ¥üý¹ärÄ’«x²ßáÞØžFn;[ù—˜Ûå;Ó,[ –we߉ÿL’Ïè„ F~á‘/Zƒ/Dù—dϪc„q…~má`ë`øD×'úŽu“Vg”ñ6³j)F;ª´ÓËÚUúáÑB¼ ©y;ªëí»¿~?Ív4´í)»ŠÄ‘ãpgdd½åEoÝ)°wó=÷ܳ }"§ÝAƒ­'×A9ì:Ê;ˆÁ`ûcˆäžvú»b¿—€ù~@9qòt8O\‡I|`Ïëtvv"oJ7l={öìaW¢ 3#G­ÄD<™o<5F"7ИT Ê=,>a[`Z¢"Ki·=,d©%ó§º>nh×"*™oˆEö²t(ÿ¦, RŽÄ³Æ1Nø‘”f*yE™ o4^…”š,¡-›"¦?·Ì\V–›à’l„â, 7ñ9™§Ò]ø!œü;I3O+:ísÀÅtLùg'üÕÇÅÃáWÈÔÅ#‹S¤.褲ÂÇ£‡TÀBXý„ 'År½ÉãTO‰®ä?'åq°¸TIÇ_•©Â{-‹­•ï’ ‚on|eÝ*y¹œÞÞÔñ¾ðÙÞÏ¥(ÇÅQÛ+•d±é&ßÏÛ'XÚÛ75õÏ‘¿šš·ö˜S[1žäIX,?˜¢ÐD ŠÀÎh¶6 ˜ØÏHoètÙW…žÒCˆuEïºÆuë"·áÆvX ǹÜ%1ïÞráÆ?'«uµËê^É›ãæ³Ñ°ð`¬ŒÕ@ç[ÇÏœ9»dÉ’ÖeËFoV¶Ú¹–ÁÒÝ’>V'A%ļÌ8¯:zZWŽŽ†¡ÁA+Œ±çšf”ò…†.} ª{¦w…±ócˆZÔ¦aCEÂU™Yx!Rï R¹ÎQ6‹–ž¸¹ÓòùŽ(‰+ëð0»ÙÖñÔ£%ñf•pöèUE/‰/9 ß.%´÷·‡¥_ ~z0•÷’— §)ÿÿ÷&ü½jÍ–[óv‘±TäR]^9]LzÊ Ém÷c¶)Ô*‰W’“aj`ŠO ßå$f9®æ¥€[á.g„“Œ…*ÿ ¹µR[Úé@ ¯j[¾Üû'áºÎëcs¬´©ÿöï)ÛQneÝ¿xòɧ‚zÛÑcJ‚Ôæi/ ¥ºb­§žÇ, ³QcÐ"BVþ£;f¶oýWO>õÔÓ§õ@ë'„cñ‡ÿðØ@3³½sà€¼@.ú#ÐLEÎΉOÞÞYërêø/ó¼ÂƸ¸Ü¹ÂÏi†Sfµôöövi!à„ô‰[n¹åC:-ªß4º$*õ­——ô2þ…Œyä3ö¤€FZn §ö›jå¡Ý=ÝÓÓÔÑPï´v6»Gœ€u(uzVªÀ#÷¥ÉMèbªÐ1•êß Ÿ'tÎ^…szF+Àp¦d7Â{e76˜"£³“EwGWzW"s14àq´ÛÎW¸Qf 8–qyÀíCía”NÀÏæïÔ”ÿET  &%ÅÛFÕ pgÆ–£s?Û< ùš‡§9ìÜ®qÏÆ4tÉsŸÑÖ{þ\D_úTçñòwøïÀþFÿ7Ãgz>«öT[ø´è¯Pþ¢ÑvR‚ìOp›Ž`?uj,h«wغu«hú¼ªà­é=Ö_ÙJcKïˆ09QVLÚvÒ7òX„Á ri9{îìûï¿ÿ?tîÿ¡¾¾¾óç8](*}WþØ®ôÝvå›\öG`aªpñ½P–—Òeã T¶áóv»*á7E¯M~¦Ô[<¦OkÚhœÌ-NL e¬CŒ‡,s“w W¸ç<¢«´XG`«ŽÞ«“¨Ôé(üQàâ ð´:ŽÜðáÎè|€Ú•Á>#±øÞ¤Ó,“’¼_…3¿TØ(Ã%™]É/‰ðfrð]Ïd¡êäÃïöz/N©Ù)Ž5)5’Ë5 áÌ!˃¢Ÿ#ðœü!¯N¦ nüê^×­ „Tææèö¿ÓÊßbÃo ®)Þim¿)G<ϯ-ÒÝ¥VØ.³L*ág¥i#Ùø+ù-‹v·•äTñ/WY‡<€+Ȧ­£MÅÜÖy[øH×GmÔ$k%:»x„ Ý„›6º·¯7ìÙ³'¼úÊ«©m%·DUáò¿˜Èr‰ß㨹å”TëšlÚô‚Æt-Zì?®ÏºHo™ÝeeeßHÿ8ÞÅZ´SCË øKfæh ÞÕ8‘knr8ÇwšÛÐ˳·ÍïÁ6eœÎ„hkÓb‘äpøÆo|ŸÎp^ª|žQ™²Î5JI‚w,Gô2·lŸ³„Ò›)lѤR| w:<¼$pN4#~N!¦ [”, ¥·„^ýõ·ö‡ç'ŸS±Ÿ¾V)b ‹ïݰ³4*ýÞ*œ±ˆÕ]5 ýˆ¥’†,2bd˜ÅK-„T‰t²ñ ìn§×ÛΎȹsØ}9.· 楟ƒE°f»GÙ:'`éW†õ9àP†à|Ê?——ËÄ7ÉXE÷ªâüîöÀݹM†s:68÷™è¦”Ýw•ŸÌ "*Œe½ðõR’«N~ɳG¥„.¢˜É,XL^áªU²*p ëNMÒy[ǹ(?üK›ú§À² °6¯_lY¡—ÒƒQ>mê­¹zþ¹çÃ[»Þ ]Ü“›”JæQ8#%98ìw‰OëdhÚ_vëéÓ§¶ÿú×÷ýÿ‡:!õ¡ _Sç$)ï`û'pºòÇ L.–s² '¶Â”ù »\~[}.ÎþµNÇžëñ lëha^»:\4±~ýú¡%K†×µ··uK¹[[[Ë‘˜öž…²·¸(Ȭ(Æ‚W«ýõ©È® ^½vµÝ¥äQWäUøXõÊçƒÁ¶¡0¡û$öLïúóŠ1ûç/Lªz³'ÓlJ}épzÆŸÕ¼~Ux˜C$oSjþJPÿÍœ&% JpQ”ŸÔ‰‚œŠTg‘ÓŒ^‹Rjžr?œ0üù%áà/j”ÿ-ÿÂ7ÿƒÜî·Qxàs?rŽ39^i þ(=þr`ʾ¨l‘/ÊÍéS÷ΨÃçñä RVÏ \4’ÉC.lqàPJdÈå:\!ßË„³\É6m{þQþ_ìýRøtϧ5#Wð³²?fJÌ_ÿÝŒìÛuºç¹=ùä“aë6¦þ£‰'±ÇtŽõT0ÿ&FRÌikMÙ'µŠ=£oÿ­­š%Óq¿}ôÑÍb™ÐbrΕ¡€Ò/w\ù»Ò÷N@Þ GýXg*r»Žþ®;.U€s$þÄÎ4x§UÁN+~â/ÜšÖ1ÊÛ6ÒO®]»fUÿj¥<‰ŸÉe!3UT Õ;E»|ayÔ€ÕS´[S–.]R' 6 à?À ¶ú[×¾.ìÞkÏÕЈ©æÙ†+3 ÐÆ1‹&D‹@É™ ÎÀ‚)à D=Æã»`(ȳðÎÒà É6¾XŠJ9!ú“¢S‡QáÙD$¼¬:ã4ÁÝÎÔ¥Û?>Ýy$ôßÖnùÿn ­í6þr–,Ê—•ˇÛi0zÝÎñÀÑcá€K’‹JWÅg̵—G¦†) |7š¡(˜Šðê°EtJØšSa—cèʫƔAâZŸSN&òJ½ûp÷‡Ã÷¿úZû,íPÊ›’_S”2Ô¡›þè°ÛJW¾‡cGÙ,+3´–·QÓ[d É¨IšCx0,ž€âÐrôèÑ-=üð='Oœ8)ùé3²øÝvÅÏ,Ê>ï‹þ4ãnìFf.Z#?o);D>Ï•2ÜÈíxlé'¸Ò­|µ>œY±bE—ô†ööŽÞ8 `™å³ü³*»&ù2Cþ ,Ü´tT¦¨8&X HÂ5k¯Ñz€>íUÕ½Ô,n¡ñ/›ÇN”ÍA½ª{§v‡c牥>D‚ZŒ¦áïœëçÏ¢Y‚ɹ`)€‚\æ¢9—ñ䌵“•sšùèîyN>ˆy›Pb–3bjø*îÕùkvת®°ä³Caõ?¬ ­T‘ 3ÈÎåã»ì3Wæ$æ%®Fʯó˜\’ÁyÂ4ò{±á%Ùy˜³~Kf£¸Tà¥\.îJ‚ù=´{Ú7‰éë@IDAT7„¯÷C{þ×ǶP½3¦ø¡Ç?¥ µ“Üè7X`=66yäÛög—­¥‡ßM„õ*b "iÌ‘ÇUÉŒK4-ô;²ýõí¿Ý´i«³9õ}ÿåÑ>õïÊß;ä¢ç¤ÃîöhºãsØéï‰}9uøÁ1jvŽsž\ñCswnK÷J§" ]'W®\54¼dÉ xšž™.ö}Yž{Ì<ì–Ù*Äš`Oêøé3A+GÀ¶vêØ`•b…NáK?O=a\S:·hUû*} 8^œ|¯S¤Gù§d*£ÍÝ€FƒA6@®c©s”$ÎEƒµ ;Pб£Kb ç|ô"¶0¦§ÒÏldÄ8;Æ-Çx<"5át§u.ë´iÿT”k?Ù*lªFYle1Š8Þ95‡SÈu 8§WDÀQiw'ÛCóoü%ræ§…;Gx Â0!-JpyY9˜ñçèºv'<åêø‰ãÏúÿ<|ª÷Ó¶ Ú¾íÛø,–`+3ªËnÚuàš¶r‡W_}5¼ð 6ÀÒW]5ÍJÌÄGÝ7ãÞO™gV–¢¸Ý‚äÀèªßÜøàš)>-§ó÷Q¿+~l>6rÖíÚhåœ/»á»$æRwøÑd‘›2œ»áq·ÛŽÃ튜)~ÙŽÇ }Û¨Oaíšµ·è8É2^YD†ÔdR¸â¿Ú‡Z^6CV˘·„Œ“Q;354Û`ÛG—ê˜Ê©XàÁa¼8ââÜ€á¶aAçÃö©í º9ñË*¼ýÚZò×ýÜè,· v¯Ó fy›…(¼U0g4$e!Q8›‡ŸK¨ƒ£÷:T½£&t‚‹ðë9«]1Eëi.³ë¿{¶¯ÜOGî"nG³ËòÝ7‚óÄ+ü;}»Îo×B(ÑòPkœ‚ÞŽÒGÂÉep]'&…XŠSÂF«¼XL&ÿ=_íûZø|ßšõìµ¶öЭµr8NÓïvèÏol=ö˜V§í3·¡b,’Éý‚ˆèHs|r4x²VýÓûhá ·#Ï=÷Ü]/¿òòN¶ýéS.ÛþPþyåïšÀarÓs4ï mÆiîv»Þéïª}¹uø±ž«nç ®ü¸Ç{GwÛÁ@*M'Öžýó#KGnÔ=ÓíšfšQÉ/SùÊ,K‚)ãcŸ!cŒL¶‡ðøñAç„¥£KÃ@ÿ@ôA‹"¬`Y,Èg€%­Ã¶ ðÈ »O2>s-þW‘.åŸê™0/Æô¶ÉUç ä\¦’ž$eŠ8bˆq¢•dƦ„¬rV†Wfô0Jvß:GYÀ¸Éñ°ç•ù-”l†³´šË‰Vª^9ÕcãvÑ9SÌ•<\|Ê]Ä)—”Ás„Wp‰'JöPæ;—¼ šK,ÂZ¿)¶•!ÜÜysøÆà·ÃªŽx ûþ-·”„…¢¦â(3ùœÊɪú&6oÞ^ýu]öÓiWýúèßò!jû"OƒÌ 6H£ãV¼¬Ý×m±ç$ÿNMý?¥ScÇÅÉ·ÿ\ù{'eï3¹âÏ~Wär®]Rœ.¡u9tøùž=U°Óªlps=ÞÀnU¦¶è žN­ÖŸÒU¾o½õÖÛäfè­RaSÈFÞN°WÀ—"+Ul,Dc­åfÖÁJá=qòDèÒ©Uë®Y'¾T.\¸¼{…FeÒ®€em˶ÉmáôùSº £]ü]=Æ«ì¬_“{º®ääÔŒП‚%£¸¨¤ ž<´…œ6DîÓ`°c‹`ã·‹pO9Ky0¿îÑmØ€ýqorç,ŽžÓ®ðPÔ…²Gäë±2^¦-ÐÝ8I Y?,•Å'Æ–w†œ/>kˆNK"£|ˆ‚œæ¼¹}´†å>—yÂÞ†qøÙ_ ýM¸±ëF[E»¦ÁWª4¿¤@J]`u@±è©`ŒFqÆëIý1ÿŠßø$)‘Y”É„¤ì$^IâÔŸÐ2~z|çwÝõoêlœìîêšÉFÿù´¿Ã®ø±]Ùc;\ÎyÜe\Œ[c¼Óßuûrïxäy ìt§å¶Ó±½`°¾ÓÛ¶À3gÎL/]Ö:<2¼¾½­½W³d’i{$—÷\ó|² ¢¡Y–ÚKÞ¢Qz§Bzöì}³š =:ˆSµýÃ&ba§èuýÓÛR‡“NÀ²öeáÌÌ™°cê«fk baÆ2¦‚µÓ¶‚»†ªôW#%+CÅ­_GcS°-«ëdÌáps°Ô“Ê,Äz–‹ˆ3ö*ž’s&Ù®@ëüºŒÜ®’ñÎà<¤Y4dÇZåçôˆIo{2säB2´%š‡iu®²ÏÆî’L£BjìåJ¥°Î‰-ÏÝ-Ýá+ý_ Ÿíûœµ}“Z÷ÔÙÂ~LRÐ@Tµ|÷g”ÏIÏ>ûl8¢K×øî_31íM{›?§¤”LW6U>£à­¨Œ:ôG½ ]ävD£ÿ{Î+¢Nêà˜ImÏþ¹âwÛ?6¹™+~Üþ¬ƒq_værìHåÚeY—áŽM‚çJ\#w+g¨ ÙïÖ¶’7\ÚþuºJ¿™ÄøJŠ^e¬C@hf"ãèÆèÄh±ÀeLÇVžÖyÕk׬ÑÖ•žx8mwI…U’?v P(øë:® ¦„Ý:àj4)eªºç|u Dzd‘;&J1ŸiÈ“I³†%º=þ¸¼¸8§E`¾ðªèxGL.ÈcÕÈÎtžw‘°'ÊxÏGýþ ÿ·>|è©»~õ«ÿÖ€s #Wþ(|ŸúÎGþäd>êÏ;"™©Èí¢9pžKn_.²(K‘²;#ˆ’Ǹ ŒŸFw 8õ-Êôó:µorttÙê®îîOõ®[YY› €«Ì „ñɨä¾²++ÇÇÇm‡ÀŠå+u”eŸœèÐ+•.‡ÂL¨VüÕ;î íK±™ãáÀô~šŠ¸°èm~¹>‡jéÑïò2:`á,€š¨YЂyju¿ü;<ÌÜöH8nV¸ó!¯ùd¼ÓôZ,X²ÿ~û9%ÿñ'6*¢ŠŽàJ2+93ñòcLfùÍüÕÑáÅ4gù®re9òßu{ׇŸ|þûÓÆ‘(x3²ÀÑôQ8è§O³¥\ðóÔSOé¼ÿWƒNá±…€ºçÝÚXüY[™ìèWžEF Ââ°!L¾ÅI4 ôl×£ÿS§Ç¶¿øÂ ÷lß¾}—8'¤ØögÇÇË+ÿr·+}lŒ»ù‰ùcÄ„sžKn.§‰aY–R%‡ó„Êñ9ì ÞqØ<ÞA(ÜQLJV}ÿïØ¹sçáuëÖé6¿[ „òS¯ìq•òÊFúpc(Ô‰.«à_øvÅ´ÿ‘ÃÇÂòå˨ Røé‡*0+°QN,Ø1úKÛ—ê´¬Þ°crG8¥õ‰Ãì«é+sL“Y¿»ºà«¤ Y‰/|ÍO‡uN »(øJ&ÑÝ™a¡ºüjN.Ò˜ð ðKôñã6^«~ø 0³¢!™Ž‹âk9ñõÔšÂwü/$¾%žBj¡øÈ/ù«ãª ù¯Â^Œ†ßåí!Û›¿5øðnÝ?A›G[§ÏÖÈêÇ[D§ÑtV›J­áõm¯Æc[ªYKEû| Ã꓊#éÝ5:AE¼e£yAøtâßouäï¤f˜öW»ì ÿÜfÔ²÷)?ä²+…©ÈýÑ ¦÷¸œ;ü~rÏMÌÉèr|n;=·ËÊ¿›ó¸øÌÈÈÈ@_ßrM»À×L¦ô-gõ2^{—YÃü²ŽÀô´ÎPÏ–Z{`Ê;’ ÁùQÁ+ÚVX'`ëÄk¶ ¥Zä?Ôð·×'ý섨¤[b¼X †* ÒS#[imwQr€íqdrWйd&wNï^ŒsþžÓs=1÷–ÃÆ¥pʸ˜ß¸à(»Í÷ܯÜK#ÎO—Bk4ðXò×€«ݰ×q]¹ÿ}­ƒá»:é⦅PCÿŦ}³ôõO¸RáVû?þØãöÝŸŽ³1™åÏX±‹RB%.s&‡%cô—šö‡Þ´ùÙg8tðà¡&¥|äþó©ÿ\ù-Wü^°ý±ÐÓËé9î²€/·‰bY˜¥Ž»Ývwç6pþä¼t 63¡·iGÀ˜ ÚØÚk®ùg( ©ú|*ªµ9B¸ÛìH͘b¿—ѽysfù²"¯rÇ–,ø[::ºº»¬7 ›U Êe,›Va&5ÕÑÒÖê¨à“3'®©·Ô Räÿ¥A–€/KÇ ¼¡ÈÙ*ÓïÅÄÒÉ‘qû,DE óòÀž:Þè¨C%ΊP¢„ÄœûÉá*— G¼ü!ÇêQD)§G¤çEΗ܆ší£–¹÷ŸÃ‰'—læb•ÿázó ›ímS—ýÝÑGøúÀ×C»Ú°I ¬;´èÏt¯ê›µw”†”Ø´‰àŽiËß“6õÿ²ÍÄ-Ò‘jÊq­V]I8CóŠ©*ÈŒ¹p$D#°Ö®sâŸîø÷-[¶lSû¯Áÿ4Jß;(ÿ|äïÓýàò@Yù‹l%´\æsãï’™+©'’ç78‡s» ã®zô}©«Óû>|rÍÚµý:!ðz—6Ú•á, Œ½ÎƒT½éys“¿Iùg¼É—•`üÍÌ耠£Ç™aëÖ­³…0œ`뢇âÝÊÍXn=×u\öè¨`î ¸Ú U™¿JÓ]ðÎC·ª`.óø5î…ðXÜaLÌÖ‘¬¹"Âyj¾R\9ÇíDº`ËýÏk§:Ÿj&ÇU¹É¸äÃ-ÎSþÊ’œÖÈ&Ø*Z]Y.Å­F$×°.o¶ä’ S¾àj>J5ç\ªqÕ Äï’£Þ(“¹Âœ/¬ô(Õe×¢²Ø ~a›Y2m‡ý|oÉíxs’”mÓ>Âçw[ŠëE;Ç¢¿®.u”~¯½öšu˜%íïï³ïþ^cz¥¼J‰ó+Ê3¡†O)«»c]²iXu4ZÔÆ¿.åÿŸ‡9Æè?Ûóïßýý›®üó¹òÇç¸"ôDçÞËÊ\®š$OD,wçpž˜9ÞGîŽÃ.?ŧÆV>螀³º/xzhhh•Ndt.št½ç1¹]ËCʰþ‚ 8Òš-@ ÚpN' 6¬ƒº/€Nˆm‹Á‡ ,ùN6þFlQ`Oxsrg;?f¿Û§ÜÌq¾Ru¯þåEæT“ëJÔ,y¶<$/"8‹Åó…s¡|¿[y"€ô¤ò0;FÎãžk6” pŽˆ\¸HÓÜ…šôz|‘¿Œw·Û‘3þ`7ÐÇm§UØ–ø*”óJ,õ<¤Â奛ýâø‹6Ë7Ê[ÉNÊ›­×´¾°öé¶Ôƒ†z0lßþ† „Ø`M&~H <`/P¨p'¦„ÅGò®‘œòj÷î=¿Óžÿ_ ÍhBí~yÔeïWòÞ ÇóG΢8Üc.× è¹ì‰Yå¶üO e8÷W¦¹Û:*DÒ¿ñ›ÿÔÔä©åËWŒjOêj„hïs‰*¡–Ûø.ÊC£RNx˜ÒÏÌÖ³Z/÷ÈáÃ:¨KŸFíœk/AV )ä©Ð1``»¶É,o[nŸvj{à¤þR5! «Ú,(,¿*’©¾`õ¼¨5-xñ'g+à…ó†ÝHЬÐkŒ&ób×û«ýb+Ì #§;œhÅ~-*õü9~؃›‡­Ž\òã±+x*ãWPk@IN tÒ*¦×,‹ÁXžö¯¯µ_ þþ<|nàó¡K×›OI‡¶·ÆÃ{hËò‡„À­éw]î36?³Y{þŸ4åßÞ;µäM©™ÚA÷‹M¹:Ê6'Ž„7*°ïæjmÕål›u¶À=:Uð˜Â·«~Åw€]ñ£ðó‡(ùÈØfÊnàrSvç´K_ÎO”r=.»ásœÛî7–¨z:<ùc—¡»§»Ø0488½|Åò[´¥ šz‹|),Ë÷p,»Qþä²1Ù]ÆËlC¬8-aB;~œ…ˆaõjõ5L| Â#è1mµ™}3Ó!A:¯ '´3€5t R5©Åå*…Hÿk˜s0z™î’ñy^e¨ÊjËY‚ðU¢=6¾»ìÏq9½Šæ¸ °ë«ÈÂ=fþˆ›<¦¶”§Hç(Ù™œ¥æœ‡çj«ƒ´aLûsÌ/Gü~kÉ·Co[¯Fïñ^”¼%»R°HPÂÛ#ìægž oÚtûnh“ò7?–âÆhþмô¦ÿ&“W”…pöv>ÅDã9„ê൩¯¼òÊ/u¯ÀKºöü<‹þÊSÿä¼Ï䄿î*3OÉ©òòÞák€ò»§±lx™ÉmRÙ•?°ûatÞrâĉ1}ŸŸ^2|ƒæÚ¥ÚÉHÄ[Ž›3uó„på cM8ãÀCBS\œ7ÞvuL眱EÃÃ#¶`漦Çb%ÁWŒ2³Èe’‚­57tÞŽM oNí uÉ8S=¸èð3ÿUéLêÉd£ýÂUd&¯šAØ<1¬Ú»¡ÿEJð”ýLßgÆÿ*ôµ÷Û/¥SÐÆIëE}ˆœæTûÅ¢g:eÛ^ß~è¡°{ϮЧëÙuBŸ¥µñ‘‹ÌgDÔ`H ɨ9O‹® ìÄ?í(8³sçŽ;µío“î9Í´¿Úw?ëßGü>õŸúQöî¦äÊßK¶? ãôq¹Wz –…˜ºV^RB;œÓ!ån‡±½#`±5P«E'4°õÖõôt/—n³Y•¼¤û-(s$„ 4o…0 ,LV,(×ô–éœÔ­Ü¸~ýúÐÛ«ûÄWpÓC§À:’ÅÝŸ>®-‚{,.ÍW} ¤&¤™»È°¹Ì|ôÜoÞ:t#÷¼@x>ÿ”¯ùxÔÛb«ŠGVö,;—Sò_ý3Áê1å?O(%y•ÜsðÌ[¶*..äÇ9ãÉwòŽQ®µMm §þI1íÐö¾£G„{ï½7¼µë­ÐÛC{'ÏÊ6oêŠT¢Èg`ÜdnÃ'DEí5‹ ¦Ÿ8þü}¿¹ïgÚ]pBí:£ÿ3bñé~l‡Qöùè?õ7Rþò2ËÌQjfñ^rĕР‘²œ·4+»Ë éô*\£9*;ñ„@Ý ­*3G׬^ó>)êݤ/b"ßM vÅi¨kƒŠ"Aÿ8z:l‘™ÒX¾‡)¼°N;ôù!œ=sÆÃ@@…^\£9¡r;Ü>b·îšÜŽN5ÁY•ñ®j;¦X‘‚Õi1¹–ÕÞgaM^M¨Cn×ñW"ë8.ÜñnÈ\h,в¾P ø*äð³ü§E;Ã,DéT…ÜÊÌÁwµÖ1_ñOzÝØycøáÈ_†k»® ç´ ¹³µÃ*[¾dÊ=59Ô®q9Úoï¿?lÛ¶UŠ_³Z Àè?úóœP [['·Ë2Aä?áãp4 #Çù)ùo;söì>üÙ¹c'#¤ áòÿråïŠßGü¹ò§$”;àòGN3åRSv;ßec/Ö ìEÃmǹ;–—ZV¸›Y;[³Çõ}¾[[7èv¿.“ˆ¥N-J¼ðŸÃ*" :çóÀÝâX5ËAGµÅ€££º@—bèÛU¬)fî›i6îÔÕöÀí+ëç^ §ÏŸv±M»”4Öx”𳜞ȳbNˆÎ ;ÆP¸—IŽ`CÂ,ÎÅðÊ‘ýOO ·a1¸NáçÔLHV„‘“ žƒÇËÏ‚ÊÐ,Á‹á­ØŠöá–þs¸¥çkÙø$‰ÂÆØ›c~žv”öL³ªáÉ'žO虘šÐµ¿Rþ–æpÓ¬ºŒä_nÍãGYFŽ \2:*0ŠN¥Q$=BWbMóF3¸ð%C™Õ¥|+[:24ë` g86³8XC‚­n`ˆºÜ|ØÐ±^‚´Âvb«J;å?#EÀ0ÍW9, ËÈܳ7ÇÌ _(?yXòSrá5 bªóñ®;ÊRËksW„åœ.ØÝ9k;ß;m{¸Èu8· ×Ü1¥‹øʾJJÁUPI.ÄÌÃïyÝ41hS0Ì|äáO¤ü»uЫ§tŠgü“÷éOIË®< h׊ÿ½{÷†»ï¹Û:tÚÚøîï™!æÁ‚ÁSúOtÑ Gò%Í„&öª4ôù™IÅ¥ýüÌÌ”NrýÅ£>ò¤XÎé8÷™ßý}ôÂÏöΰ?gÁà®xs%vHtJCn.Æí~ÜF°ü“pÜ-(¦6õ&Ïk=À!Ýä·\‹Z®Q¡ckàŒ˜TŽc¡-Êvà•(9Uør[Â9ž•±l œV˜ÌLœ›:(èlb[Ik”w*@ü ¿]­aMÇjÛ&øŠÖÐ h~ð´^˜o^æðC&\Œ™×_Î\ñåØZê1îÂv“ãÊpΓÃe>—W¶Ë~pÛ®EL]R§ãسêM¾½?ž×¹·&•>#ÚŽï ?|yè+vâf)™uŒ#å±ii¥š²Uý=ú„¹wÿ¾ð;ü_|é%uZBwW-v.øS¹ˆ >ʱ2•ä±èe~ÈÑ ~#žŸ‘l­B áè‘£ÿþ÷¿ÿ¥vS³åO×þŽË#ý»]î¸Ò÷9¹í%Émb‚™Ï¹.ã÷•Ú I­¬diën·3’ŽÏmàù<³3€ÌnQ¡šÖ"–ýš X¦SWRa˜mPýyù,-_0'ÀÒþ€©'kÓgô°÷ìÙc½æ+V„Þ>´e[fb…¨¯ÌÄ.^¸¦ý«°þ9ÀñÌŽg3W ½ ³¸ì±ÈÜ2a÷EùÃSùqŒÇ9Ñ­:®q\jª‹£ô ‡!lôž`ÇÕÙÛ—’WŸy™Å°~ÿen/DìÕÆC[Á7¦ý¿?òÃð•¡/‡Á¶Á"ÿ„Ä¢PKEÚ¢ˆÕí¥ZÝL—=üðCáñÇ]]öÝŸEÎÆ•f1]‘“¾ÌdFÍ¥€‡‘£ÇpbŽXûǦm1è"·Íšúÿ™ÚË¢N¨½öEþÝß•>¶úQðUJŸ(Må'F ê‚J[d¼œß‹±@z“QžY¹ÃÎW¶sàâQQ³ôÒyÒ§40±dhhEGgç°MiEmýªØX[d/Äg&Sö T‘±¶–€Ym÷KÓhs ²i7ÿi Í*U@0½lføŒÐ©Ï:×۔ݛoÚÂ@bÑ\åÅ€©YšßG,‘lÍÝsùÎù.Äß\2sZ!ß·)w;ì6<Àz°¼3‹-Ä·gª~ïBeWù­ˆçŸÛ,W=Ê¿ù³Ú¨m(|{ÉwŸ }5ôµ (ÛYpÌèŸÁ¶ÿQ(ŠX4h§˜1}à?„GyDíWºÜG™±EÖÄœü»ç(­ OöG…¦üŽaZ+jK8=>þúk[·Þ³e˳/‹ÉÎùWû™¯ø÷‘?ÊÞ; QþÄ,/‰9 íŠ7‹µà“·2w,U1“.ó9>—Õ¢=¦u„å¹ÑeËnÒ>þn+—&Ç老¢* › ÚO”»†óSѲÎ10·^’¡Ó«4½uÄöÓ^ýõ6ÇÁ­ª\1„ZÔX(h'kIŸ®ï¾A7ö„;l‹`LMs)@Z§½0Ÿž=æ+–Î õ³ ~¶?îÑi¹;Çå°ó¼ ;•û‹’°@¿žgn_TXW'o¥Øê÷ýᄯýi`61~ç쑴ߟvÍf3º×`ÈÚ¦ßýîwù?f‡›qе9â) ~åà]Ã"Çš»Ä†;>îÏDØ+)Áêlœxíµ×~þÈÃoŸO÷—íªÑ?8àü¡DáÆöG`Œ»Ê,°4Vy½t¸+¹@ªÕÊOLò»*eáñ‡Ls^‡Ýv\­-z*xç÷îÛwhtéÒ]ç{“ kÛÌÌô´<²† ´¼iz9­xÔì, 2–ùŒ€‘Äf1 ‡hÕ¢ÀÎÎŽ°öškl5-W 3C`‘¥bÄbkqm6€»*íµ×…+µEðõpz¦yX§ýÅÚ4[ùß‚åQï¤y§å½“qC–âwBîʺ¨üy'ây…ËàP±XöOá3ýŸ j;XCÄJÿ8ó¨G3ƒ…Ú,跇ྎhý’¾ÃÇö/ÍpâÉý"ż&ÊŠò\. ‹¾ÀG”¦÷§5Ðá÷É·ÞzëN…÷{u@Ω]>¯vù¬¸¼€’ÎGþ>ýO‰BÙãžïKaÊ¥±ì./wàJïxúR4rãîTdrR,ƒ “ï‘+/þƒ%÷ï0‹Ïë@ç¹³g'µ&ðÀÚµk—ëûüZT %7ùØR~/÷Eé°ADfëÉøÝ3K3ÚçÒ ýè¨à¡°|ùrëq³Xe_«<1Ò xfV©pm×uaÏÄn;6˜JÕüà©ôö옖äåEš·áuÎcñŠ,ïTEaž3ä·G¼ˆ0<Ü~{¸z|3íïæ¦®›Â?/ÿ?:äçÃÚê'Õ`eF)ªµH 4EL:cX¥µPöéq˳ÏêŒÿ_Ûâåù‹¼4~¸#yôïoÚ)oÃLº"5zƒnâ¦5ãÊ´»¤žÐ¢¿_蜖S}}}0”/úCñWü}Äâ÷ѾÛÄ6ä¬3Q2ëü_vŽÅØH¥¦.­Áù㙈¸ÌïîÜöG í 0¿Zq:9>>¾wåÊU:»º–jºa~ù`Ìf1Á&@/è‘ G„l=•±$7xþÕq0?‚9EkŸ¶Öô ÝQ1úͶcðCÕAUèåí˺Îõalf,ì™ÜmáÒ 0/D£iÞV ¤Tù½/JèÕ’)*·cÊi}12®f?¤Êß û£¾O†-ýkï{« üÖù£yÓC›‚OÏ6f Yô÷Œ®ö½ûî{ÂáÇ­­ÂŸ}Î4~oóR¡6 92ˆN–æ®ñlÈ!6^ji?}úµÇìÇÚfxPä Ê8æ7?ì§‘ò÷€¼#ÀÏq·À€÷Ÿ Ò£S0”è9þŠ€c€„¯Ê(ϧUÙŽsîÆ.?Hë>«'ªm''–,Z){D?n•-âáeÈ•µD*•¯lfÀ8œÝc μ·Ï‡±Ï׬»F— )¸™i› €É+ ¶­®UÏÂ*ºdpdðúŽ ºÀã\Ø?¹O]d:Ê|· RçºùzR 5u.‰×4•)pÑiZ)íêBz]§¾w·ô„Ï |>|wäûázÍZÓÕ§ý¹£ÄÚ%5tVi_Ô©Íb_WWwxòÉ'ÂýºàgÏÞ=ÖÞ ü½½¢Í²¼2.AÈP{åAŒ9 Ú ˆC ^Ò*î H ¢éLø_å•WÿK‡ý¼*_,ú›‰†ÌGýnÓ p…ínWöå‘¿X,æ4„þ€sSn Ënç»bìÅÒ Á­ e)_vg¤„‡Lt» $œ»¡çòÌ­‚Wà´ýäÐàààôÐ’¡k:Ú;à×–=OP5…“,ÿªPÝXÁOŒø3¯‰ƒ é?vÜ¦ÚøÎ¶N*ã9`k¬RYM$±¤ÒSÕ¸¹k¤c8ÜÒ}‹­Ø?µ_eOH&RëcçñiÚo?HÛw,}¯´lÊ ÷E&¥§ŸÛ)æª÷FúyG¿¿µ?|~ðŽð×£–w,·¶©Î6㳡)dO±Ô¦Ð–°(™kË;;:æM›Âï~ûÛðæ›oZÛC§¡fb‰Ç•]ñG‚°ñ_N€Èe¾*¾qk“¿‰sçö¿±ã»}ôÑbÑŸÐ(v_õïÊ…ŸÃ¸)‰®üÎmàü‘³0àsSvç´+^L=–œZò—ÝÎÞiU¶ãrþ*Xeµ¥U'\qN@ËÞ½ûö-Y2­í׫Ü¥¬3*í#~!Ï”yQt¨Šú+¨!Ýp‘Ñ»æÒ˧×tæuЖDû·jÕJÍôèJásFGL+¡¹µV ]×v²U°»­;ܬé¾6ý½9±Ófš‘Jï®!7ý½»!Kz¹œ]L€±H^ŒÏù¹di³ Ø]ùL^Ç[Ã7‡¿¾3òÝÐßÞ¯A†ÎQÛ`{üQÀ”·’ÔÔÔtJž O=ýt¸ï7¿ o%åÏbå8ÐIEÍü“£È²w .¹=HX1 tøSë¨ÍTñä¼zRçûßùèÃ<¨£ÑÏ©ÝVx~Ñ+û|êßGü(¿Û”bܘª]ÆÍ玒®À÷bï%±äÅÌÉá<»Àûãø²|ŽsXuA µ§§S½Ó©}û÷ï[6:ÚÑß߯“[:)9*ÈðZ刣ysÉ]*} ‘ªL©dÖÊ ••‘> :#èæ+­Â°…~¿¶Ç4†¬à#@Àzø¬ò]ß¹!,ÕgÝZxræD1S@ǤiÞÛˆMÞì÷{‹÷&´Ù¿2bޛЯ¾P|=þ«uRèFÿ*Ü1ð…ÐÛÖgÊ–O„Źþ´Xþ‡"¶Ö‹&¤ÅVöOë“ãÓÏ<~õ«_¥ƒÊÚâ¹ÿjÕ<äÓüѯ¥:M\È"òG\òfX£ú—¬Uþ§ÕÎݯÛï;«þXx8111×¢?Wþ>âwåïn8`ìò#TauC¸Ø:äš•©"ûêÝeš³•ñUnÇåvkÊŒŽðm×I“Ûe6¬ß°\7®Öh½]#nz±‘7}×2íOè6ü$+i€zÀEñA°ÍàÍfÅ¢¿Ó§Nk n§¦ä:tXÐ:Sðg¢ƒÙ<þ„Ï ÂŒ6+`80ˆ…KÛ—†cSGáéC†7ÍŽ€%Ó%}ÅœLys²x_Òˆ5ÜÊZÃz^ü÷4ðÞD¿ƒ)àõO]¾µûÖð¥ß Ÿêÿ´Í²Ø¿6FÿÖfÄ6ÅsÍ”—Àì8â„¿_ÝuW8|ˆ Aí‘Ødä3—cPžãN¼ø{òx1£k³«:ýñ»ï¾û'RúgÔÎNgÇü¢èýç34n凖ԕ#ÅOÑâ ®2óÑ«ü\¶¸ÅØ ±‹2T‚eDÎï<àïpÙ oŽCùjÚÿüy]´sÕÊU£Ü rL׸ÕJNQ|䭀ő4}¡ð ˆ Îf¶^ñî«M§bk'‚õÆÏž;6lØ`ßç¦5]g•‘ )F`ˆŒ*-ÕrZëbÚµ«æšÎkº® alúd82uXµiÒäæU7†Ø|_Šh”ð—"Ž„9_|æ£_ªx/ÆpIkÚþ8Ö÷cý9ú×áC}Ö® V«ûØœí¿ÜÓšxA[K]}Åä~ r¯ýëÀA?Lfm‹:n€b{ãCÄv'‚¼ã#ËÚ#+1rXbé€CDµ‰aFVë±cG7mÜøà¿³ÝOûÖ¯öÒ•~®ø}ÔŸ+Wúà¦)õG`[ ¢™ÞÀÛå‹^¬RÜÊQ–ô¹» “±ŽÃÎaá8ÜU0ŠŸÑ5§S·¨°NLNL‘ÑtÕjÑ 3TÊ(%+Sê#¡$’&\=¢ÆÀ2„…ÚVÁ•+WêÁ!Ð5e «˜xqCÍâÑlc0Ú±,|¨÷C¶`Ÿv°S i.ÿ Ù¼\þ.ÿÔºúbÈ9þ_üJøûeÿVu­¶ºN*øÉ~j̨5@ïÚpÜ~Ìt;k~sß}ASð‚-t¶¶#úLM™ B@rêK/{§òZ :vÔ IQ[Ø­'NœØüÜsÏýR'¯îQ[5¥vl’ùtÅ?Þp…í ¨Ûà0N‹®Ú;µ¾¢ì.W2°˜;äK,mµ*»<ŒíTÛÌB¢ ¯|ÑU+¡F†ÃœÏ±ô"GÓyØÛ¶nµ“G—. Zh·q’ýÅÒ#ír©ø‹ßôZ´.`]X©ÓOMŸ û¦ö™OV' î­i7S ™—I P‡Ûµ°—…~˜ô~4|é |Ê>ó‹k‡é‹;5®ø½õ¢ `0AöÖî]áž{î n|0nó³•þQ—š—'ä ÊÞ&3 .Àœf\¢¤)üX<àQã"å/J‹øMzéÅÿuË–-Ï3ò—ÉøÍg\ñ{G€Èñø,M^®ü­ ´Ðbó*°Î8½¹X‹½@¾Qs“»­ŒæÄ—y*X å|nƒ,`z±Rª-:ë¨ÖÑLÀMíím=«pRÐkꔯu5ƒ2®õÄ­œšÔÈc´Z05o@ÞPÞ±sgP$,[¶,,Yª‰ÒJ]öùš±š+Á)ƶ˜G„Iu²;´(hµF!|îüÙppò>¾Q層ÍÙKˆæ«™—I xäS^÷ùc)ýo/ýN¸½ïv;tBušUþìÓç‹!Ê»¦øAD7íC»FþjFÂÖm[ÃÏ~úÓðÌæÍvàOl¢hƒðm^#L:ÖÈÆ>-B7cr Gä7b Ï)|ŽÔZiMñ·¶MNM}õÕW~,å¿Eñ9«xrÐ+}lWöe›È¹âw¥ïhŽæYˆY(ßBd]vìtú¬b=/E|@[õÎè° õímíý:¹oJ%Ëè ÉXÖüŽJEÕ±Æm ‹@ %—X"—«–OëtÀ#:1ðÀþªÀaõš5¡[qœpq`*¿Æª4¶¾ NÛºE¨u|èh釵8𔎮u[b\šïf 4SàÒ¦€×I¶øýïᯇ.û˰ºkEŠõÿm¬òõ[ŽÕyˆ‚ OÃÁ?1®;ÍÂOl wi¥ÿVÍ$jn3„Uòã-”(y#º]Ù'ª,¦üÍ¥W³œŸÖÀ¤C«üïܹãO<ñÄcš8#9(yŸîÇÆíÊÞ;(wÜ®ðËʾì+¿Úà*ãÍimQà®–™•JŸå[—3²L£€s» FFÙŸñ©3Ù¥ìù°k÷®=º9pšN@[{k/µ$žXòK÷{–I3FŠt¯ô‘—ðñßnæBÙÏhÏîÁCµj÷¨±-_±2Œ ³‡Vn-Z¤|нwì;b±Kà=ЙëÃÉáÐÔ!“å9š¯f 4Sà=O¼¾_uôŸ–ÿS¸cèKú Ezjz˜õcqZ>Ötµ#©€’·ðµhÍÐH8qòDxH·ùýV§û¡ü™ ` ŸœÛZ ü'Bš5ÂE—k°‡Þò'àά†¯ì·(ÍÕÖ cыٙÃ9 |Ns›Âátwã77Î_à˜P¡nÓS:@c___ÿ¹!¬â¯ÓM½›n7Á.Õ‹!äÇŠ€¢!»0Å!BCß"…­c‚ÏQ¤;ŽésÀê +ŒU/Óè -½ù§ÒR;%~ê¼®Ö %íKÂ5ëÂ2}81}<™>"²êj_Õ)âÖš)ÐL·•(~þPüüÑ!ÿ‹¥ß _þjXÛµ–J«º¿÷s/Æ;±N»;Ömxh=8~ñ‹Ÿ‡~ÿ{[éß®}ÿVñÙ‘§ðð þN² '„ÔfD0NõÓŽÔ˜,~…âÖ¢O•§w½µë¿žzê©?ŒŸÓ™3iÑŸ÷wåRGùç+|" ÌÏÆ`; žãvt]Å﫽@ÖgÅÙJBÙíÅÃñØs çð»m‹ Hõp§:´O«ó§úÖkå~·Ö ¨›­Z‘$£ßsejëPîü¨IñÇ =‚Šx¾×aôˆdV×êèà½AgjëSÀˆŽ^f Äää”-2^¼Pil¶Öhá kùžÈÙá7÷Ül»8+€±‰/;·E¡ùj¦@3Þv ¸â§~õé"Ÿë`Nõ»cÉBŸŽôeÝ´ø½ŸúëõØíX§QÆLés¸æ¥_?ùÉOÂó/¼&t¡˜+§ûN1ÑD /2Q–b.^5¼kC 0ÉÑ験 §]Šþ¤f&ï’òÿÝ©S§NŠÌ”? ßû¡ìqçJ¸¬øsåOk‡âO­žÙ9,Ra_ ®& Ùˆ¹íåu!yOÉù­^$À^ œÇmcѾÚVp^€©C‡ïѧ€™¾¾¾kô‰ ‡õøW¥ÑP:*ðš2•X— “ÁÆ–"Ãè»;0Ææ^%–=º³ÀîíÞ³käL‡•«V­MÐm‚çlí@\ Ï!/²¢,üj‹®„ΨöM…ÕkÃ{?¨‡¶ M¼¥ÝñVA ¸ùj¦@3ÞÑð¶åÿ§ÃþzÙ߆k»®UMœ4EÛf7ù¥Ox Ùê./LšéCñÓ* †“:8lãÆ?„»î¼3lÛ¶M‚™ÐÙÙ;ÅàÂ3Ö2cËP >µ9àf¨$€ÆCT}VlŸšž:¡óýï•ò¿_ÊLø ͒ꞟN s¥_¶ùåÇ•}¹@óW~„* ´²©Â•yûjìy^=#«Üe\îBREwyÎ O]â-Ö¼`øpèС=ƒçûûú×µ¶µvƒWàÛ˜ÉOõ°œDFjžb̵à¬CPsÆ™ñPÉQæÜßÍɬàÔÀeš `‹ ¶á˜|Fcb¹YÀ”?¦«µ;ÜØó¾pc×Mᤎ>0¹ßðN7GóÕLf \T P©K(àô}4üãÊ ŸúBèoë·ºÉìœ}Ê‹3þVGc¥µFBáF›ñ úzz{uXØ®ð‡?ü>ܧÓý¶¿ñFÐ ÄŽ÷YÅ"²ò[;&ŸÐ‚)|!|Äÿè7…??£–Ï?­O:¹oÿ¾{Ÿ|òÉßèÓj&Õäi)À´+¿+ÿ|ô_¥üéx'À[5¶rX„hâO‹ýUøèIg ¨AÏ™«:W…[u½ðr-<8y0œÐÍ‚˜æ"AK†æ«™”L÷£øùÎO _«Ù¶Œþ(|ké·ÃÝ7…Îtyt_ÇC´zjŠ8Á ~ýbX ü?ÿó?áþûï'Uÿ‡4û7¥-ÃyË‚k|dc’K²ÔI8Ã;ŸñDÎÞ ¿dëb›†`gÒé½û˜öòþ±±S'4 á›ÿ95y®ôÝΧÿsÅï ßm¶¼Œ‰ ^ÑFdz;Í‘e·ãµ}5wÈØXºkY\v;…Ãin×|ÕÓœî¶Ë˜Å¯Å/3ª˜­š ˜<¨N€.:«©¹ë´0§3L…Á(xbŠ(Krá!a­"g<™WbbôÒDƒÀˆŸFaÛÖmZاu+â7ÿ8[!©ññ±!0Àzû„Ç¥"}mᆮð Ò0éÁ3çÇNƒ†ß¼‘á—5M3š)PK¯'Ôþ–´- îÿhøúÈŸ‡/-ùrì´šäo>ɹ²¦M08)娧ãN:^‡§Ÿz:üÛž{þyÍôMé¸_­òW=ÇDÿ ³ýƒ•I°É4TѨ3ÄÎñFO1nª÷úÖH`jjú”Žþ¯§Ÿ~šoþcŠ—¢1å£~ÿî_ù»òw…Ûa·½™Ãí0‘ÈaÜp¹)»sÚ¢†¯ö™kÅ9Ëå²ÛyòBÒˆ'c |<ø-û1·õŽ5"×!S:1pŸ:gúµ0PßÂzUÉÕf%N¬Ê&F’bD€°©`­N( `½ìÏà„‹ÜÑ ì¢yÝÐ! Ç5xõ•W‚*fX½zuµÏª ¦ì½qÁ«|Æ?  cÏ8eJÑíЙº×‡ÛuxP—v9Ò 89}Bô]-4@IDAT8’qŸ&¢ùj¦@3,¢:¥JÆ?Fÿ×w_þdøkáGË~dÛûL‰ÏÄþ|ª‹õ1& °fc…¶J­åÚ¾ÇÎ~uêu/‰úùïß·Ïnöc‹_ [%‚ÆÈš™ÔJ™ÛÛ‡ÔtYx &T‹súL݆ŒøµKÒý3SŠGÇôÔÔ1)ÿ_n~vóN§’iÛý䳬üé¸â÷EîFÉç~OSù+.Ä4;1µR±/’®ì†PÆ•ÝU<…ÀÌòÃL¿Úýt<°‡-‚ú$°V»û©–úF?Mý¶oú&AešUHŸ%Ö(Æ_ð‡gôß:éK†C‡i—À>»Y°K ­áô@]Äqn‚UþÄ&öG¨ßÈJõÜ€ø-Ò¢fðIà}=·¨¦N‡ûµ¼—:Þ4Íh¦À\)ЯE~_úbøÛå>5ø©ÐÝÚ£ºg7Ú±¾±ÒY퓘Z}t<³TV}RÔÖã¶ðìægý¿¾7üA—ùÐv?Ö0ò·öCíˆ)þ()F‹º¾ãGù AÆ0yGgÄElÄÁãt vKÝA¬aþaÍ2ÞùôÓÚê7>žüµé“ÿ¬ùô?J¾©üIïwÉ4;1a­Ì–Ò¸ÎË»Õùñåx¯òS;›‡N€*a‹Fý­ìسg÷[ý}½gµCàZõœu…×y:ñ° ¤™ò÷hP×=*L±Gã¶G)º]é'¦‚‘tžÕhôè¡ñ°cÇ»´§»'ôõ÷Y£¡øÙö!SþþuÂ|ѱCÀ™Â®"½­ïƒ6#ÀVÁ·&Þ4:#¦;›¦™Wk  *½xþìàçÂÿYùÿ„/.ùRXÚ>ªùª#©^™¢Œª—ú /&ÚLg§NÔÈþ䉓á™gž ÿýßÿì1›ò_²DŸç4å¯5FÆ‹hLÈpPQ§cØ‘ƒ°Í,É—áêðò£i Í Õ>ÿ_lz|Óšiä–8q­¯OýçJ?Ÿþ÷¿ÛùÈß›8õ”hNÏq7í”Í@­(Xñ­9 ªÂ9KNËa ^îvþÜ®¢ë³ÿÌ ³êŸß»oßnm<¤ {ƒFÖ½©8Û\¿åiªŸÎCªÌ±Äë]ý8Py`Ñ4=©ùQ¯Ÿï„,bZÿN|^ß ÏéØàeúЛN‹ ²âÏ /½…JhÛ—ÌR&,-ï\®ƒJ® +µXpZÛër!¿µŒFÿÞæ Õ„›)°ØR€Ž/þYŒí{è½Mßù¿¡)ÿ¯†{µÈÏÎî×~}ýQ7˜þÇDåO=óǰF£Þªý°ã¿¹Œ[üþó?ÿÓÖ÷p°n#µ©c6Y¼Š£GL¦Ñ¬a Ôžá€ùËê·ap .Õa‹—@kst‹Ïää¡×·½þãg6?ó˜Ö·'·¡¹çs9]=äóZxH½‚=Z¥»J+eG„šÖSÔA<˜§\‚á2UªŠè¨¹ÌQD¦ŽI2ˆ #Ú¦[Á88ˆ)Åk¯Ý`£:qÔŸö+DÌ[ :N&#Ö#í#á}`Es·¶žÑ-ƒcÚ:è !bÓ4S`±¦€Õ‰LñSÞ×w­Ÿú|øæè·Âg‡>†´€–ã¶8y“ŽŸÕø¶ï ŸzMÄQ¯Ô&¨®NÅ©})úM›6…ŸüÛO£>šîü ZÊŸ7È“PÑN80†$ǃ՟­>ŽÄ2¢­eŠMŒ-nÖ§í¯½öÚ¿¾ôòK[¤ôψžïÏWùçSý9ìŠß•½Ût üYðÉí89Í”Ý «p‘û*{7;³3œ^6U8çæj.>çÏíܯãUÌ €gtwÀa ßß××;¨oò«¡¨Â§þ5U¸¸Á)&¶P•–Oy¡¶*À+p_Ñ9+ÜŒ:Ø8>~ÆÒ¬D8¦c„¹LhT÷ cBk¥8B!,=EÂSþmm€5|jÚô©Ù€heóšÎ5võðÉ©“á¬:Eøò×4ÍXŒ)àe|Xá|<|oÙŸ-ýºÝºÉáZÔ;fÄ:X“#CÝŠ©^YýF6t)V»á“ïú»vïwýêWá×ÚÛÿÊ«¯„IuÒYÇã³.¯°©¯2„A½µÀq†°ˆA ßÐN¿þh2@Û& NžÛ¼mëÖ_¼üòËÏ+ž\é««~ë.öÉ•>òÏ•>ò÷ŽI䢃;À¹_6U¸2ÏUãnvª³Ú‹xN­Â9𬹸r~‡±Ý¯ãèPqeµ„C‡Õ"¼½ZØ« ½B8Vì±ÎFYí‹“‹*»EÍPZO!ó#Ðx’º=ZHífZñÍoÚÚÂVG`ddÄF ì !°ÑŠh±a€+û=š`» 'g||àáZ­v>3sZ Øl>yc™D4­f \‘)—åvUÝIñÿÝŠ¿ßÔžþµÝ×õk¸QÖÔêçÅ Ö¦ì©ùñ@/Vøë  ÓôÂÝwß~ÿû‚.µë¿9ð‡«}5?PÈ¡^bÃ1‡@*8ÿ Ã…½™£Œˆ0XSxª÷ÖXiÆrâÄÉ“›^yå•_n{ýõWõ{Pü6òW[R5íÂw¥ïvþ À¿+ý¬³FÊÝY#Fô)ýö#‡×6ÁmK—Ž vvv-Sß SžPßj jwº ”'u§DÖ°µõ ©ì‘–Þ¯GßîDiµFejz2¼øÒKáÍ·Þ ]Ý]Ö àdA2åOÃd­…|`[c–"……› „EãѦ£‚–u,×BÁÛl±à´¦>9VØcɵ¦w›£ùj¦Àež(}ºÞ"ºŸülø»•ÿ`ßù7ô\«-³:óKxj›­ñ­ÛÚ' u€ù£psßôu¢x8rô°®ï}(üÇüGxò‰'ì|FýxœQ½Å ‚ÇM¬“ÈŒäâ ó^à€ÁF’Ñ c<îÃ0ªÌªø2êlœÖVâÇuºß¿k'ÑâÁ÷þ Åï#~·]áçv®ø&©€1–l™mÈô‚V6U¸2ÏUçnvæÎòX êyªpÎÍénC£ðånp/”9Íq‘C<ªD|SkÑ7µ³o¼±ãÅeËF;{zz××e­@¬¦âOb¬ß€ôè6噿}§34ÊŸ bÃûBf±ADŒ¤¨Á§³«3œ;^yù•ðÖ›o†!­ Y iZ’Ï®øc’ÂHè,p¢l×·Î>íx\ÓµV÷ ¬±OúiǧŽŸÀÔhÓ4SàrM¼ŒRç(Ûléûhÿÿ _[ú§áKÃ_ ìÿ ]ÜCòŸvÉ™µºb?N•.9¬ gU’—*#ŸÔ8Äç]Þóãýqøíý¿ Ç×€n£³Â?ÖÙ(Æe˜d˜0¬è0§ã‹Hˆ*œ9ˆáãZÆþšæ?©%K÷oܸñßÕNqÀ&§òïþ6 _Ø(ú|äŸ+~à²Âççð4•¿á4ÍuþÔ,ŠÆZ…ËÈR`Ïí2nh^_szîß>  |õœ×TüΞޞq´J;4z%Þ­§(ŒŽygÓ{5 X<§ìÈ[&ûÛ*½dD? H´‚YŠþܹ³:`ä Í=zDg¬¶ã„ù.9¥Û žmLÌÄÆDCÊ"Ž’8ç<.$ð¥£á–Þ[ÃM=7©cÐÎÍLØ‚¨sÍ5–Í×å®z‡Ú†Âzˆõ¹%wè;ÿ÷Â'?F:–ÚÅY(~f¾ŠË{¨ª±ŽäpT·šV·úÆí}ª÷aïÞ½á§?ý©®ïý…]ï­qÕ3vP—LN)™ou/UÀØ)·ÕǤÖá‰L™ p`ÓŸñPSµ•Gh¶ùONNìg¿¶Þ§Uÿ¶Â_òýV?¾/úóÿ|ÊÙü~nËinpþ€›ËÀ×4 R Ùh0%t¬5dÙ]£Ì†œ× ¢»gsÎÆ¸ô¾­ P%oÑY“öïßÕÕÓsº»§gD È*Û§TM‘oa °£o–œàÅ)x|+¡qÈ#8¬7Iš,Õ{MC¶s‚¡u8`äÐÁCáìÙ³AçضAö"só 56ŠVl;hH0¸õ§ D[ñ¬‘P w& èü€ |(üñà'CokŸî8`— åßSëãÖt5SàÒ¦€—Íµ×Øªþ\õÏáT~ÚÒ¾û–Юѻ}ë·ú š@¥°z ¸'(* õÎ4ßò9ŒKŸÃcZÙÏB¿‡~8èT=;À‹OqÔ#fèLžMé! òŒ< ^ªhÔ?w;Έn7ewNkÂJf`áÅ€:’›²;§C÷Xæ-»À9·=ªyvVkt ÆÔ¾½{÷HùÓâÀåªxK£*áªíˆ3üEAtÅçyPY#蕇Nà ä!ý‰ÀÞb.Bñoݺ5háí `´Â¶Aj¨vBµÛ.AÑ#< @ WDGo>#@á´k»¯ <ð)]‚rc›9¥ˆ Ÿð_ëâ8¥i7SàÝK”½+|åƒ}·‡¿Yñ·áËhSýýºÃJw*ó±LGnCÙ"¿T¨yæó(ÓZëº5­?~æŒVõ¿ª©þûÃýüçaËsÏYyÔ•¾(ff㨑E=2Y±Cix§¹[¾‘c4ø’‰³wø•‰/M÷«¿n§… ™‡×vîØñËçž{îqÅá,ßü5 W»/öË~>ýïÊß~Þ  Åáf­O²ï8¡ SÆ•Ýc¨¥@³PK‹…@µÚ¹Ëî² è^s¸ÌWv—y=“ÅšÍØé€Ú&xPg÷¿®Qw¿¾Í¯Tí$O?†žb€"§™(LÔ솉,Q©t’gÁæßœJ>d1JaP§F"Ü#ðâ‹/†:7€NÀ®nÕ¢¥NÁìÀ›&hmø·†'Âgh½nžˆ‰Ûû5Šº¦{ÎG¿Ag Ü¢{ºt˜ÐáÀé‚®üùd€q·9š¯f ¼C)€Âg¦ÊöQθ’÷‡>¾3úð呯جÕPû•YJ¢-xUñn+ÖÅ h­à[½C1ÛŸUHc*Ÿ™¨Ã±cÇÂ#<þý'?Ñõ½­£­OúEZCS¹È/Ê 3™Ü„‘•x¢±±'ÿµ¸Bhç'uÊà/¾øÂ·oßþªØ'çi)ÿæïJ?ŸòG™ƒwÛ~Þ ÚûÓTþJŒwÛ4;žÂ^µÜgÙíx·¡{¡nÄ ÞùÜÆ?þ0n—ý^Û€ÆÔxM'õÓtáZuеHžÔ-W–ŸL¤ùàÕ¤½ÍiáQ¾µ`Œ”æý ›X¬ 'r¬±ÖTäØ)mMzúé }Àú$0®½î:uºÔ€E…MCÂg—ƒ›¨Zt ¶Ñ…×ng;=°£­S7 Ž„ë{¯ ëºÖéêáå¶xCSNM‰¯vá52㯴ˆ7_͸àÈËQ,ãqKÝZ-XýÈÀGÇ¿¬UýbðHçˆu&U©”@;ÃFúµ:eå\4+üÉ»üùÞ¯¾6öÈ£„ù— ÷i_?:Ú¬¿1¯é—PÆÑÖ^g­¶óJòÜZŽšß‚j„‚–äbéw[;«¾2žÐç¾;7?ûì/ôIâ P(ùüáÇ»Û;®ôsÛ;Øù#§¹•"f°ó'¡ ËùQv;¾iW¤@³P‘( @Õ&ñ–ÝeNw»L¯rçÙaìΦF Úz³C£î#½½=£íÃâeuî¤*±f bÕ¦©¨71J9ÖšCèå„ ´V¼Ï8(7J›ÌôÔ´íOÖÊà°k×®ð¦v 0K°zÕªÐ?Я©Â);v˜ÐÙ~‹BÄ0âiôhH Ïv´Ì¨#0nÖLÀ'þ(,ÑéiŒþÏÌœ±ƒ…|uuSù+=›æm¥eÈËQgK§ì ø_áOGþwøÑŠ…÷÷ ,éàô¾¸¢_U;˷±øÕËu,ÔEy¦&Æ2®Î­Ê4ÛõñÛ¾}¹Ÿ~æéðóŸý\Šÿ7AûéÃøéqÛfËÖ?y´ßƒÿ ÝäáNXcsX•Ò¿Öù§~™³¨k.'áåK³p“ªŸíðž9{fûî]»¶e˖ߟ={æ”üN(“z¸Ñ¯<íO'À¾üqó4RþÖª$º,k}‡]6e\Ù]æoºK)Ðì”䜱–Ô<”Ý5J„ S@s>wç7Çåx¤àž…£1Ð"¡õ&µC`oWgçÉ®îîíÏ_)Z#r*>A±`p…48-„,Õ8EZôçh¹ô£j.֠왪¤QÙ·o¿­VÖš}"³‹J–è¡ÁùãþÚ‡Ô6á$J©”\‰®æ•΀m›‚E#,Á×v_>9ôÉðþÞØâAfŽO~Œ+ 3LóÕLùS€²ÅŸ› *c_ùjø¡”þ×G¿nè½ÁÊžT>oQ6©‹¶Ÿ¿‚­8#ÄDÕÜÔöþwuuÛ?ÒÚºm[xàl‘ßC=do±µ¯O‡ýP½Ø÷°¸EGÂÅ@jøÜmG´YÜŠ8:8Úa£ C¯Å~!èúÞÍ{vï¾G[7iPqŽE‰š‘8Çç±åÊ·üÝv¥ÛGûàh4ÜæŽq·Û[{ƒÏMÙÓšpƒð\o@n¢çIªô+ãpûƒ8`Îp61ÇCÃín>nW=œ’»C ¿Gät¬]»vÝÍ7ßüM­ ø˜ªt{¬Ez[ËLÚ{5£‹À_îŽN¦ÕeL qÖp!lN8fÀpb· ~ô£ wÜñ…pÛ>`G +â|®°½ÍÖy0IÕ/‚±n ¨þèhĈð³C›<N ¯ŸÙž=µ9<9öDŸ7/_'À…D)–­ \)€Ò¤#‰ás’›ÞÖ^mßû#›Þ¿±çF;®w°}ÐÉf³NÅ> Mc4+–|”)l¸"1–Y­kIgi1oÐç;»t åÿÌÓÏhqÝ›%Ãò£ob))Qk›¸(›0D1ñÌ?8,fÁÖYˆqA-xÍ[BÅø!È—ˆ<ªSª·Ohï/µëàMùžT;s^íLþ½¿Jé»ò÷Ñ>ŠÞ;àøYUÊ?&]¤»[ ™²d.r7ßs¦€eÿœMâ|)P•†enÇ9Œíì2œw€Qôn{gÀ;îîuà×1¡ƒ·ÜzëgV®ZõU58ËQ®Ep¦€ÂVS„6ÿ¸'‡<ã†.LŽhdX—ÝÑ? ùÇ7ÿ‰› íð™àcûXø³?û³pûí·kų:æ1òÈ[4žr¢Ç1PAµpL™ åÊݺ7rï8»#ìÔóú™×ë㯘{\G»É¿ï²~ i®žh”÷l9½®çºpsïÍá)}f—®•Ûª É£rE'…iŸ¥ Jéæå´.Q¤ò¤ _âœmâÜ„Vôo wÝugxB§øñÙ¬½ƒzÀÍßpG% „§bf!…i<¼’@${<£f$D†nåÈj1Èw €á¨P’ êéÁƒÜ­Ï>}zLRPô>âw]î ØÁ»ÂÇvØ•?v“Bþ¬ƒqcR*FGzWáꚎÆ)@Ñhš·ŸUéXÆávʃÛ?0 Þqàq»íÊ·+üÜ.¿êo§”)4Ãm¸öÚo¼þ†oèÛû‡Q¡3Ó3“ªáLí),ÕþM¹F[þ„RÕ32ìnª/ãŒí4`7VÝÝ‘ì*\‰¥éœ+,ÿçbhÒœUiYÆávŠã8wÏ× ðŽ Þaì¢ ¸]•µK J§-r;ׯ[ýºõë>­ |>¥„ý¬ ¤òYø¹7埪 °uÄi<ò@ýÖa¢;z2´^†3sü6ÒžNÀÉã'´×¹'èsE¸õ–[Ç?òa›à¢!â8~vœ‹-.dCƒX ¹Š¾§¨Å+òèÞd)\…Å7ZÌØÔXxiüŰéäãáùSÏk+á![8Øœ°ä¹j^Ìt螉ÑÎeáöþÛÃ'u`kHú;âÿ´>I¡;­ûiåË«§ŠU*r±è¥Âg…2T­3ªrËLßË1'Ož´‘þ3›Ÿ /½ô²ŽÒ~Ùø-^by²,eܤš8 (× ÛøÄKÔ䦸×xj2Œ%Æ7¼æ_þ¨Aš¢Ð§4›`PçäÔñÇÞ³k÷Co¾õÖ6Ñ'ÙÎ˱¾ªÏ®ô½€"weïpþоcó(†æ–U¸Áa cp;ÎnðeçmÚ”—¦yçR *=Ë8«£)H‡ÝöVÛªµl”;0vÎgÊ0‚vÖLMO·ë¢ŽÖ%·ÜrëFG—~º££}¥š‹6”´F8ºUP͈ª”)üX‹_ ` ]2gñT)úÔÕØa0õ±Á…Ûû>¬{n³3&ì ^7}Q²0ñs”ËqÛ%×uD¤š€ÒŒŸ‚Ý«Óò–-Ïi‘ßïì{¿‘1ÈmZ+}*¿&——•mý‚_ö!F@rcF vÆ©Ö"" 4\¹¼Ë­º¡[üPüV“¦¥ä÷=zt㫯¾ú€¾ûŸPçeJÏ´¾÷3UæÊß}Ù.+}WüØ$§wÖ)h<˜Ž˜ÍÝΗ»›ðE¦e¤iÞÙ¨JÓ2·ã.Ûhð®øq;Œí³ã6ÅŸh«‚wh!^§z÷æÞ°aÃ×_Ý×{zûÞ¯Úo7ˆDšK5N«•ØFA†‘M¡Ðÿo{gâeÇq÷&1ƒ}#@ìà nåÄ¢Q²œË‘•ý·9G9Žb'qdE²)ÊG–ÄÜ$’X‰•ù~·ú{s§ÐýæÍ`°Ì j¦ú®µôízu««««ašB{]ýÔKêžßëyIÃIE^d ” +±êàþýÝßüô§ÝO~ò“N c‹Qgâ‚iÙ’”œJi‘O0 ÚU/‹©t0J·IF*OS»§ôHàÌ3ݧWNto]z«{÷ëw»w.¿£Åƒ ëÈ‹Al£²ã\ú#²îz¨ë©+«1,SõµÃßÏõéÙþ±îù­Ïu‡ôÁ©½ó{º}÷÷öôM‰¶®Ô¥ÐN iK ?ÖÁ³”JÔC‹]Ëszy˫׺«´~øá‡Ý/~ñ‹îüÝßv'?ý¬Ûªwü7ÌñsuûïóV›äÇùE±Ê5Úªù¢‹RèYFÙ¡éÔ²#qá‚‚ÅÀ ü—ü{=ÿ/k·Á?jW¿ÿ¦×uß”Î5 ^®ó ±~û¾Û·ÃÏ;}dÙáÛÙ×ΟûP¤úæƒ;À«Ã¯ÖiôŒPsháX`È®5Ú¼·ã‡Oo„G„6wô`À3· ¤ /øzµh—f~¼ÿ¾ŸéQÁNýØo¨»S¾Q%nâ'3áE Í` ü Kzà… ^Ò¨¤s :ä^ÑŒ¡ÏCõ(ž¨3ea Û‹~÷»ßíþëÏ~Ö½ôÒKݽÅ"Bö )â†Ç¦Œ²T·c‘w_f_N©ÏeËÜÆ¼ÞrÊáŒvüàòûÝGW>Rü¸ûôê'š8ݽv&¾Eus÷Èùâ<{ÜòWÇG¦ìÀ™Å ;õ‰½úðΣóûºÃ›wG7=Ö=®ó÷é=~7qÒ^ÓǦ õÝ`—(í'åÜã4©$YüÔ–"Ýõ3£uQïí¿úê¿tÿýç?ï^{íwÝe}4ëêUm€¥)~ x›ëpà‘Wï ]N?œ³²ía”œø‹éÞ2¤w~‹ò‚-FÉ+šªÐ›ºùß ME¿:}úÔϹë×B¿sÊ!;üìì3ŽáíôÁqüà`1h ~X1ñ„NäàÖ3 âey×i¾™,3USŸÅC¶­y¦Žä Ž“7Ü^Øñ›çÀ-PÉÆ~&Ý9O<ñÄwö8ð…ÿ¹2~XwÊtìü;¿>ý…£Vê…‰~š)´è€ë· `WÌÉ’Üû¦wR§Ê÷®¨ãÜ£õûöíï{ì±î/~ðƒîû¯ü {ò©'¥­u—uE» †QÔ¹r‹T:Mrë DªBbü!.ºˆÞˆeª‹ ýcy“ è£xòêgšx³{çÒÛÝ›—Þè>Ñ€àª!°€ðŠâ˜¢ˆî¼|±ˆÈ–Ñ|RšüÇôžþó[ŸïlTœ:áƒßï~óëßtÿôO¿ÒÆWêõ¾Ïb?²¬`ºßmŸüs¦] Fµñ èëHÔ_„>x…ÂÀáÄÀ8„%­ =»™õ×yÞÔC¿ÓF]ÿ[³ÿfçÏ”¿~{þ’ŸïúkhG<0‚;B›WŒ[hó%îV‚õL‡xYÞðX oI+HÙ’Ìb!ûNã!Ë=8ÍÔþsšÚÿö¶»G´f¬mU»‰©üÒªúµ¢â• > q\mÂ$”ëß3hÃ}DG»lNœ>[ô¾¯ï^¼þúŸ:m’£Å}ÔöׯG>,dõìs”ží:ZY_D ‚×W>îÜo>úø£:ùéÉ•ào è¯j`ã¯øÙ¹sçoÖ£ƒ¿þP¯~UTGîX釋 â®U•Åš'iJ}s*X4.¶Å¯> žß® »b ÿèæ£»ò=µå©nïÜÞnó†-Ý}1rÛܶ…} âê–«VÚ víKåšö£[;_ßÞ“FócØŠ·â¹>Sû~{…z.\¸Ø:õy¬êÿÕ¯~¥~ÿÚ}ùå—¾ÌÙmÖî~*®zðé§žú±¶êÕë‚êaõ;µ#/¸ŽêIø‹¨=NxÑܲ’®¤)ÉHÇÿÌ’ž41Kù•lègJ:aLêsS»—¿÷r÷Ÿÿæ'Ý¿ÓÛÛ5ˆŽ‹(¸þ=YÒÒÀJžt£Â&éJÚIÝ$C'Nˆ4½î××/wg®ŸÑ Á™îËk_jPp¦û\Né5CpÖðõ«±ez$¿åy÷;ҹΔM¨á-‰ïF8"ÕåNHC³Ð>—¡*—ýö÷Å3|žãïÓkzLンçþ5É=·Èñûzú‚Ý`?œ»fppµñ§µ$C´“¡ad„…ô™FýiØ}½ÉƒkrñÒÅNŸ¿Õ§y¡çü¯jp*ø P}Í"‹8”²YLXìAþÂz…€°øs9ÈKMzÝYI¶@“Q”@Ú>õ„ìs-¸rðg¾úê«|ïý÷ÿáÌéÓŸJÁN>?ó7Ï?Óü¬íð h‡„gh\¬àC †5Âþu2¿á«hh*«˜_Ëjº†ì]ó ÍË8¿uÓ@Ó@"^ >ŽÝóê¼…\w;›Ž=òìc?öÓíÛw¼¢žÎM‹•YÉG9°á ã7Ÿiô ­c/‹</º…v§iê¤42@x(øÀÝÎôwùtxzÅ1ž³î×Û?úѺW^y%<E—EX|t%\ƒÆöÄ*"ND‡8 ²ÒYt NQ+–H„~éçpg8›9½S!PA¹£=¡7 >Ó@àó«'µ ð¤ð“Ýš1`{âˆ7.ÆGŒ.kF¡^½^r>Ö¯(ÖZ›Õ‚ÒÅ--NœyË­û– [uç¾¹Ûºa[Ç–»ÛDïÖžpö‡lRd3&\'ìPGvãþìï€ÉãºâÃûQ3_1d}B_ÛÐé3<9°î$ÞLQRÚm‡@;ûýïßýú׿֧y[÷²>…­­IÃô9Êv{.yÆ1ò`ö êõìë¤$¨rˆsÎ@„A]ò±~Á ˆH.¼Û¿äz¾ÿk½†ø·ŠÇu.¼Ò‡cç#>Ú.c²ÊgüfgŸOǸÙùC—D‘™;t¡ @ãÁ á×:Ömp•-PZÖ*gÚ²›j!›×<Ó@G25Žƒ'ØÑ×çώ߸ü T§kÔ©ÅààÐáCO:xèe}Oà%Mk>®X=¤ ‹ÖzAõ6åŸlq¢ nÿ3v§8ªXäB{^r¿äÓçIÞêõKº’™óE™ƒ’N˜u.\Z cÁà±cÇ´fà…îym2ô­o=¯Žº8i}Ò´»¢mXÉ‘éQ:ûI'J® =h!zxãš© ûë­¿HxmÓ÷˜ŠLt|å“î¤'®~ðsíAðÙ ™%àl'Ì6Â},¾5À‡‡ŽiËáCú±^}Ô6Ä;¢<1“ »¿zJQ{è‘Ayt G zŒÀÌÁ—׉_Æî…ƒÎÀĉð—CE3kY8»ê±în-ÂãSÍLÓ³H¯¼Ž÷¨ÿþ˜Îç- Êf“œ²µ.T¸{îM-C•÷ØÇl[± ùD³ £¦óéÑrŽèôƒ ¥!Wš-2îÜqàºSîNéÃ<Ÿž<Ù×—ùXÔdz}>gƒçë–[´k%A¿È×íÖyÔrÂÇöÅyªL`σ(è‚nÑ+'ÂqÒ.IHèÓKOUàû½ÛCÜ3š¡øƒ>2ôß{÷½ßK;¿~75PÚÔÇÎÀÎÞ¸~íø¡ @Lfí¡À„g¼: «Ã¯Öiô*Z oM«˜cËjV ŒÙ~ˆ/GÊ€¦÷2ß80G;|xÆ ³ó¯q6Š(ù¦C·>÷Üs¹wïÞ¿š›Ÿ;ªþf“æ¥,}r<%ˆžya@P·j€œ¿$žè‰N¨‰âÎ,¸}Gã ÒF6’!Fgq ³tÇNG}åÊ=¸Ú13ðÒ÷¾[ ¿¤½X¥àU.îæ¨¯–»Bî<1­˜ôÆ•Õs }Ë1Î@:Ò¢^¥w<å&i¹ë#yŸs`%uÉ'Åk†_ß¼ðŠÞW¿"üšf ÿë¿~¡»xSQøEílÈâÄ˱8±@tI{5ò‘”>ñ®”VŽ8gîÒ7éÒnTÜô0øæXdÇB;ž»—éúmš²'n×ç—µðrN ߤGºyÒèos¤çõ¼’ßu¸%ÄÉc+þËþClE¡R Ò[0Öí‡)ëÅztÒ.úëYÚ†ìqýZ¼®÷Úkúzä?ÿs÷ÛßþV¯ñ}0Ù‡ÂK¥Ý:×¥a)jñõræ´æ7"p ¢ã/*Ω³h„š #³ Áå7róŠÎå#½™ðÇß:þZíYí[ÍYúÜüæšÚ×´»~;Ã1ç_WfÑݾyTYŽðr@6ÆøCº·Jˆ¦´Jyµl–o!ûOã!s¤4pºHó2 ŸHÏ›qÓž€6n3=Þ„Ö]ÐÎgžyæ¯4ø/ÚÑlý¶œ^Y}––;égެwÌѧãìYÊ®§…E@ ˆ¿´ô=^§ ~ä%…BI_ÊcP€£§såNŽ †^~ùe}à¥x½ìÐÇ1°^€YÝÉô­d¤+Áe«Ó †ï0Ë@%¸Y7Î…´½6õ7°?ëžÏ1;Fi.Šrt§ ©§»PŸxéÆ% .u—o|-çïƒm@#çÏ áš¶[–€æúÙò¡¬ rþs‚ æä´çE‡C3/}K@[ôÜÁfˆºÉ6)sÚZNç$G'a‘‡‹ë³ޱûóEçRÅÖC33.*’&"¬¬²ËD(;sräC<¯ýîµẊógu?uåqΞëíˆþJC´›¾^qÆäË_1§‘G%ˆ."tŽbOÓT$ýæ!=åGëúõg¾øâìß¾ûî»ÿK³_lèÃ]|íð¡qð¾Ã74oÈé#ƒo'oˆ¹‰¦…m¾i`q™2Cø¯Riä°€šS ÷ØC×`ˆG5á;Ö´ùÀ¾' çm§oè´£?pQTãE‚èÎmÙ¼e‡ <£AÀKšJÿžñ"¾Æ,*R¯¤¾‰y\z©øSªw¿ð$ ±8Ò vaDÊèàgæ ‡ÒEÆJè˜îg6€~÷îÝÚC`o, Ü·o_÷â‹/Æ@€5‡^”3ixT Î6ÏŠ#ßEZ=Au±0¡T}Ê!”s+8^мƒ¯óå~˜cV*Ž” ™Tq±®_ Ê8r D.‚òÂŽã,BÏ } —¬” žQj[œ0\ôY0ÇL…×&¸þ‘ûÃè˸)åYdŸ^‰­U"g2 ˆÈ-ê8Œ8/fmbÙ×Iߺˆ÷õ­ÔsðN;Þé™þ›z_ÿÝçZÁϺ‘Ó§OÇ+| YøçE}‘¯+˜3Z.ö.‰Üm²–vèPþe³`DBÒI¦³§Bš'Ñ(–>!|úÂÅ ÿröì¿Õ”ÿq bølo8v¥ñ×û s´Ãæˆ3'Ú鱸¡=¼:еˆ]ÒÔaˆWë4úY€ÖÂýc¡ë‘y5mžq»`ŽÈëòpê=œ:ÐÝÓ&u*s<þœÞ»ß­}ú¿»k÷®ïéŽúMóî ÒsmõqŸ*= *ˆÏ_ü»S ,ýÁë»ã}ŠH‹ §ýù)„^Ag:ºó¥£÷£žý´R÷ä“OvGŽéŽj×Á'D³fàÀÁ« ¹®íˆ™>&¯IÇ-Àõg‡M‰9œøôD÷ùgŸwŸœ8Ñ}¨)}m|Ó}òñ'Ýï ]û>UžëóˆH¾žæ·Ýr~+ƹæJl{–óÔfðáˆèiÈ&:§ªvOúQÔŽ†ç¯^ùúOç¾:÷ªV÷¿váâEvʺŽ-¤|M³Xlè³”Ó‚ô²Ó7޹Ás4²¸=Ì4:–Õ84!Ë §ï‰ÜäîIá­ÐA ]“1|ˌקn^x0`§Ÿ¡gà÷¬À9Ð lÖT)?ä¹={öÓü¡^Åû®î S÷µ5^§’—PgT~ìòSÑ‹ù·/Ž\9Á”£ÛË R”uV}<¸Ëgú7ªf uݳZ<ø¼v ÔþÝÖízmMNƒ ‡´}ò-…â@4¢;Ò^¤:FçNEÝã#™dˆ}Bqn‘¸¨‘œD·ðË%òÀ¦RQv â"ppR9Eö%q$qrä‘…Ò’<Î'˜pƒ55LÒH+RÅ@&R«ÎŹ»j9#7x¼IÀ4þ¢V¯Ìô=9®ÝÅ‹ºwß}/îô¿}¼{óÍ7c!yqíx äWüìðw2Pç…z—Ç åBËĹ£C%8^Œ¤Y‚ž#KúƇ.œÿí‰'þŸž÷Ÿ”öuÎ §¯óÈÓþܹÛÉ×8´#'ž=—‰ßx %Z$ƒÎý: ñjFß% DúKeµbf·ÀÐuã™Ì8^Í<ãÀ÷žñ<0>qüÒËød]€:¶9u®víÚõ¨ÚýxÇŽ?šÛ°aŸº­˜.8ƒÒ[ÄQ]M™èV®ð%Œž¡WyAŠ’8!æp‡ÂB'Ýש/‹éþ·µ ñ·¾õ­î™gžÕVÄÏuO>ùT,8¤þP¸».U¤®ìrgÇÌyÇ·Ê9™³ø ÍE»—˜µXq!»ecdèÌŠ#ê¯È$§àJ­\ÏðQ"Äè9çéKSl€n¯7ÉeéÝÉ"D¾JØ“Éa".\ïPÃÖK±ÛÞûï¿×½ñú›Ýñ·ß‰-yÙŽ—ÁwÑ’<]!1\ÿÞ…CÔ<Ϋ?3Õ»œe¡©[‰jEYµêë,:xú1èÃ=Ÿkï_~ôчÿÕWçN+ Smvðì䇳ö]CçX;}d4Œ3.2hxË µp´|St3Ýð{l¾EÞãZ´â‡,P_›švø–Õ8ÎÝ< é Áé)ŽÙùÃFÇÐÖ \w"[·mÛ¾çÈ‘Ã?Ð3ö¿Þ07÷(^À+Õ›iR=À3ÀW„èEÒ]rTŠŽµÔ;żø‰Wù`g“qda |>êBdʘ»HÖ<óÌ±îØ±gâ-ƒ§Ÿ~:%ÔUâëƒúàzä= ô슬}+er‰–>û¬mKÇRò°££˜…áGi“RªÌ*r¢ˆ2b•g?|),c ÐS¥Ì…šEÙJç—2/×·0Êuf…=õ,©ym½Û/qœ}à•<-t‹UúïèÝü·ßyGÏ￈}x¤Ã=DògÁƒ2Gò°MœßÝ8ó…’ÊõïAcÛ¢¤*jŸ QÚÆ'Þè×àó´þÿÔ†_i`s¦z•‡Ó»ó·ÜÎßÞ|Ó\ ð Á3-2hxË µøhskÚüï¡R³¼‡µhEY`ìúÔ|Ó‚Eºù8op ¿¡vð@2o^íF9ˇÕy…LSä»÷>úèÓ;wí|nû¶m/Íoœ‚î"zŒ›7¯«£É\õÈý£å¢@=Expýõ]GÅQí;{ì;äIMݘ à1!:z¢Å„{4x$¾QðÈ#»cí€6QÒžû;-˜ìì?Ðí,°9‘VrÇùG¹8)9+ƒ-É ]¬Â> ¶”,ÄØÄ‘4ò@?òÆé„^Qö¤„ËD…TÌÂn)  \Ê,åRBODú8Äù‘ÉØsû>çNÛÖvŸü¬;©/êi#›î¤ð¿Ð~ûì¹O@¼9Ý¿¢Ç/éŽù )–ÛPœ‹·ã–ÞõŠ©òèG“€é¯ï>J'¾@+ﻦù{§¬°vàêU×ba!ƒö`áÁÚÒV@Þ:ÐΊÝöz‡~ÛŽn›ÖðhÁÌL;!Ο™ˆØÈF6Q ÊÝw? ˜ô²²f1¨ä}†Ð Í!¬/’ú`#Ãé§ô–LB<™êWš‰°¤k£üx+-dìn…ܩ˩ÅJ|>ð¤çÚݹsçºÓgÎ.8}mÊsRñS-äÃùãä™}2ãgù”ïȬÍý&öǼØ=1Ÿ¢‘pÿb$Lm¹}ýÆõOôÿûúbßoß{ï½_ë\bZ_ƒËúàåu/ð«} #fgŸi;ú su…ŽâȪì!žõ¼,PÚå}P‘V…%-0t­¦ñ,:Rˆ¾qÓvö5´Ó‡o>Ž;ÔÑN>ÕÔïî§‘­Ë¿€FRÑ\ÊŤÍèÂ\ÑÌÿå+W¯¼~úô™¿—-þMu¼.Ù Ù€/ö]Õ9û=þìø‡œ¾=Ð8½Ž4ó„Fópã@æhY†à¥Y™*pˆ·X£Q÷Ü¥Þój´ Ìh¡ë[–e¸#Å7ôºvþ5mÇß<|â™6 ]ÝçlÔ{Lóš2?ªWëþ“î|$§{ò²™:A€º(0Ýߣ:EwúEvÿÎÇç¥SgOôL0î˜u:ðyœÀÌû8°__:Ô—ñôµÃ½{öÆìÁž=Ó¦Dg$ÊÎ<¦þoÈé£o‡_C;w;ýLÇÏMi £~=-0ù‘Y/‡±á?§mø}bÚb kÓC×n)^ô=ýé¯!ž>êvîæeÚx8ö>¸éÉ@@ûœ:ùMý^Èçõ=x^ iúû…ùùOk!Ô6úLý+2 7Ò£R1#°¶û‘P¨8c›2]îG  ŽŒ)tl£“œþöX;ÀúíÎØmÚ¬-{û7pö dÏxNÎÀBë/:}ÇAζ|¾–G ìhÈ.z¹L->Ã9ÅbÇp䢯ʉ_¹¢ï ô+í¼{ïÈ`]¾ˆc·ƒ/Ž}a*Ÿ2‘q„roÅC¸†:/N*œª®1m=~Uº†´Ô»T½.§ÿŽfEÞЀˆ¨˜î×"@}dóæM¦ûíìó ÀÞ2ò7¯ÆMS28ÑuE±'|pzu˜•W§kô}fÒ Ýg•jÕ™Ù ^Ž.È<<º˜çAŽÍ4¸é‰Óïyð3õóêð6Ê¡ÑW†|ÿ¾ýO?²ç‘ïʉ½ GqHÎi§ ek3%×@¦ŠÃi–:Š¿¾BnqŽçt3˜‡a0À `QÔt:Ÿf€‡öbмŽX^És!èqØÐÀ…«è»yç©lÉ=ò_à¿A™ë4èÔlÏø ÅiŠ§Ï Ü<§AÖ ¿ÞpøÿÛ†ÓÇv’ןèű×ÇoÇ>ÍñggŸqÞ<ÓÔÑiH³ÀŒè}~q¢jP´ÙIÐ:Œ3Чäø?’³KÏ÷ßTüT áÄqøJC¬ÀÑû]~Ú¨?8¹qdÖ1ÏlíM°ÐÁ:зÜ0§™Æ³¬Á5jÖ1®Ñ 7cµÇ®ï?óÀ)Êø¤S$"7ÄaCgh= áØ+: bS!u¤ñ*!‹{Ý Ú‡ÿˆÞ›ÿs þL«Ü¨£Ýª¸¹î¤¥ïú´Ð,0‹Û ƒKů/éŽÿcMóÿ›ÞÝÿâÇÊ4œ¾¤´o­Z¹©Í}ôiÈž/8˾;é¨C íð­‡Î´(ñ"94ÁçW¨râéæt _£hâ½pˬö´ëœeàt@GвŽytpÓ†vðÐC8¼z@P¬ãAM›dpÀ÷ø$ñ|GŸ{ý3Í °Ó ƒ‚Ä“•ÙÐÔ­…fiG˜ÚM´Ñ×u§ÿ:N_wù¿×·ÞW;c@€“¶sÏGy™6nH¹No Gæ(t¢¡õ8ó³x²^-kô:°@ë ×ÁEœñÆ®õß¼iÎÙÜ´qëš?ë@M“&œ~_ÞùÀß 7v(nWÜ«7xLðë4{» J‡ þ::lêQ8°…Ð4ö$ª)Ж&íAwñçåôßÒêý·´˜ï Á³¢Ï ž—žµ¼¹§òk~MÛ±Aê…Ìœ`ýB-è GX ­r´næñk½F¯a Lú>‡VõÙ-0v½ÇøäŒÌÑ%™vºLgÜštÆkˆ¾=2ã†5Ï| kâïÄ+8ï9=Ø¢µGY8¨}Žj pDS³µxðpíðåÜÉFÝûñx ë˽¿Ÿ8|šmfPP[úDÓû'åèY½Ï³ýúúàÇý†=´ÛMµ'îóþ>ÌéÛYG§%}ãh­SC‰&2ô†":™MâÉ¡0ÆÒm¼5lÖÉ­á‹wUŸvÝÇdð-«qªbÞ´sFž!ÎÚÎݺ™6ž¡qô=ˆ}ÔÓáÂG‡õ‡õ:á³<­ÁÁQuÞhð°]€HÙ‹‚:v:ã,ºEgQ‚FÜG¦kIݾÑŤ=, ’xEï¼ÿ—ròéîþMñ—ÓÿDÊvÜ75x$=‹ùìô-fÜÎÝݲLS)ø@ø54þX”(d†èÕxÏšëL ™&Kj ]/hÚz¹’+?±60Ä7/ÃŒS è±Hj¸é ÍÏÐŽHzÓè.ÂÕáCÇ£uòBäúúÞ‹zµð9=.xŽƒÉ£Ì!GAâÖ¾Ôì„åÇožg!Ÿþ[zeïM}vø:Cäá°Õ8ap6ì?Ë]> ÍßpR¦dÆÑ74n8%uüYî@^CaŒ?¤ÛxëÌÑÂ×Ù9µÓYž¦µ!Yæfœ@ãPÀ­g™¡õÂKÏ9NÜÐüfçlˆ6?Òªs× ÝÜf9þy="Ð’Oivà˜Oj@pLüÍÊgz@gI]Ê"PÓ…ÛŽ÷º<áÇìÌâ:éúp­'Aÿ²îðßÑÔ>›ô×3ýäü¿RZ}þàÚeA;ìâœá™?DÃË]êa˜eàóÐ:ZG¬‰ 9ÁzƃÙó:é §É²^ÃסZçµ/ê NiZ;“e>¸é!˜åtʦ™6Ì8zXf¯¦­ŒY9ö˜ÃY(Z>I¯M†öh0°[ëvi °Oµ‡ô¸à€ û•Ï-¡wt¢>'tT äÄ&à-ÜždêðUvXÀ°;×u(kÝÝ®iýÏäÜy–Oä}ý/¿Ôÿ¢ z”žíy¹p¬ê玟¼ygßNÚÐ݃š¶#7ß´Ó“¯y5nZ*0ÍËòPN‡¬“ØN“Õº^‡ˆ^jžW;¥•[`¬MŒñ)É2`Ƈd–〳~¦íœ-Ï´qCtìÄÍ3âÃS?ÿkb@ úa9 :åEéôJánÍ °ˆp¿ h0°K³ÄŠ;ÉHén òîä³<«gþ-éPF8¤â{'SÀÇv\Ÿ[ÎZw÷çy–ó—³?©;|>³{B»ó}©„vÚáŒu-Ù5’÷õqú|þ"Ñc<ó©[ä—`æÑ…ÌòLÃ3]ÃZV4­³˜»0¨ù~-Ð:¡ð¢ÏxÊÓÚÆ˜Ìü S,xŽ™çÎÝrhð 'ÞË¢?-Ïp¢'GÀ`™áCr$"£Nó0›iPð˜fëQÁÅCJÇBм‘dÓ¢¥ÂXÍù­÷°âs—“¾Ö;kžÇ_‘ï¾ gϼÜ៳ÿP›ò°x2|~g/Úïìó ʈðú8Ñí´–ÕÐò kœ4ðj`¾i`Ž¡”xÐY×ò -Ï<ãÓdÖið³ÀƒÐÙ<`—tÕOwZ“™? føRÑΚ4NpCóÍ›8î”Æ:¤A^ÓæAë2H`"‡æôºá-(|\ƒ‚ƒ<2ÐLADÍlSþcÁ2å<èaI[ÈÑ_ÄÑ+žàΞ)}9ûOØvW×Ïê ,ºƒ'Ï!§žùvìÖ3á¾åÈÀ Á YŽl©H럑å@Ú±0M6–¦ñ ´Îç¹Ð·yšÓÚɘ,óÁkÚU‚o‡=ÆË:ÎËi24n}hó†èÕüšçA‚ùÔÑùê‹ÅoÔlÀ&9ÿ-Š›DÏk@ðÄT¼}Š{ÅDw£{8ÑXÀ›IF´S¡üjÙ„§ü³î"Y-˜B/r¥JíE²ž[óLÇ5Pn©Ô$7!Êÿºf]ÎÊÙ!GZñ”žßŸÑ4>ÿ+ñ¯JÎB¾«Š—D{U¾.23·ƒ7´>º5ÏiÇøÎßz5Dn ¡¦§ñ"AŸÆé­oY†Y'󧥩õý€Z`êòµI;íék3³ð³ømžå@ˆƒ.t8AëšÎ0ãÖrúè9¢7¦wÿòe~T×(Ù¢$Ï<ëfž²˜8tóG§¦s^È C<øÎœPÓ…»øˆÎPãé6^³@X€N¡…fÛ±ÀRmhH^ó éÈ,7ÏÐõ„¶“ƒg9«¡;–Gã7 Ü—Xª3¸/+Ý*Õ,pXI›_Iª8–nˆ?ÄóiN“YgV¸T^ËqŽK•9-¯!Ù2Æø·SþXÚ•–5–_ã7 Ü·Xª3¸o+Þ*Ö,°ŠXÉï`%ir•—JŸåƇœ2â,—gÜy™®áròAwHßedYÆë2¡—’¥É¼•¤_Iš\fÛÖ´üC]Ó'Ñ*ß,° °Íp©l­›õVò›ºÝ|f)Ó:K9½Yõ8ç¥òÊv©u‡Î9ëá+͇t.Óp¬ŒÆox ,°á8Ëv’Í+³€árS¯4Ý,åÜɼsùµ£Í²iøJÓMËÓ²•æ½Òt.·Áfui»Õ™¬K㵓z -p»¿™ÛM¿Þ~»ÎúvÓ¯wû¶ók˜X uFS4¤Yà¶-°š¿§ÕÌë¶Ol2XMǼšy­Â©µ,šÖ¦Ö['³6¯B«õz·À½þ­´ü{íhïuùë½]¶ó{À-°ÒŽá7[;ýfe[à^ÿÖ–[þ½v¾÷ºüe_à– Y`­Y`¹ÂZ;¿Vßfµdå÷ØœûZj•­®ëÖJ‡³n/`;±Ö÷Ûo·9õ¶)¶ohhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh¸—øÿ–ÄI`„pIEND®B`‚nzbget-12.0+dfsg/osx/Resources/Images/statusicon-inv.png000066400000000000000000000007401226450633000233320ustar00rootroot00000000000000‰PNG  IHDRÐZüùbKGDÿÿÿ ½§“ pHYs  šœtIMEÝ %"_âóçiTXtCommentCreated with GIMPd.eDIDAT8Ë­’MjÃ0…ŸekZ0$ÐU¡Ûô=E—=@W¹YïÐE»/dÕ^!„d‘ð–4–£nTPœ85!³>ͼ'àJ 0³;U'¢ˆ™"²a]\8À¾_H†:Û¶•RJÀõ&¢ñ )e;$3GDä®±ÚQŠ¢ÐuÝ òSÇ>i”.E¡‹q›3N'Ìl½^îbûW[,’™]˜A -ï÷ív»Çƒ‰ò<HÓô]ñ4F\笵ó4Mß@"X¯×wZëŸþ«ý4Æèªªæ~ÂCÃò<°\.§ZëoÉ€½‡¨rÚÅ,ËbX­V¥Ô—fî<ÄÔuý Ƙ۳»gY&`»ÝN›¦ùô°Vkmëº~ñõKÿ4Ûl6¥Ô‡ÖÚUUõ|R“±Q–å}Q³ÿú~Þd »u,‘IEND®B`‚nzbget-12.0+dfsg/osx/Resources/Images/statusicon-inv@2x.png000066400000000000000000000014641226450633000237100ustar00rootroot00000000000000‰PNG  IHDR"":G ÂbKGDùC» pHYs  šœtIMEÝ 0å!ë&iTXtCommentCreated with GIMPd.e˜IDATXÃÍW¿oÓ@~Ï—6$•=DŠD¥V) 11²tA°táÇÂÈοÃÊÈÂTu®˜`(Hù\U†¦BMÓÚiì;¿øüˆ«RJëscš'dYïî¾ûî½÷½00ÏóÐÄ?ŽãÂþ…‰…LD\tŽ¢ðú–Ám(aD„³R©Í Sš­*ý«^IÖXBˆl²æ%k€™€!x¦@ò“!âÍŸïûvUÁZ3YxeeEK)oÀUt£R 666ÒJY__o&¬Ô h¾%„ÏC‰ç’{T“¾%Œg $› õBè2\VÐÎòéÇññ±åº®‘–\ׄ˜w~§ŒØöïZEÑÛ+Êxa Ìü×ÖZCϦ¬üy5KKK™çyèºî»ñxü"˲k-"ž™ÖƒÁ=Çq¶<ÏÃf³É.zrrbµZ­, Ã7õzý="2Î@`¦Œ¨Ñh´æ8Î×ýý}\]]Í.=]Þ{½n4,ËR̼P3"j­u†áZ§Óù:ŽÃ…h&"[¡‡Ãá«F£ñѶm † U𦋣ÑèQ§ÓéåÁY꾃ÁãV«õÙ²,6,l:MS{ggÇív»‘bRºŽôû}l·Û_¤”/³,â3ƒÖzÁƒn·moo__‰ónüððð¹RЉˆ’$É.RÜüÇý½½ûUôžµ)˜'RJ&"ù2Š¢I¿ß¿kò¤0)ñ)@’$[a>ÔZ/œÓFD=™L¬ÍÍÍÛívûût^5Õëõð 3I’$LDùuüô}™ˆêÿõÅwppðTJOAüØÝݽs¯¸ZžÚqò}@)eÝœ*÷¢i×~Þ~,rò´:ô=`IEND®B`‚nzbget-12.0+dfsg/osx/Resources/Images/statusicon.png000066400000000000000000000006541226450633000225440ustar00rootroot00000000000000‰PNG  IHDRÐZüùbKGDÿÿÿ ½§“ pHYs  šœtIMEÝ 0&Çu§8iTXtCommentCreated with GIMPd.eIDAT8˽“ANÄ0 Eì:i¤&#ÑE«¢îà=KÀN ûÙ×QKË&#eJÚŽ„%/9ÏñÏð1-$dóbº°É8ßÈ–*EDú¾§èqã µ1Z*TÈé/FÛk-3k€CC ëä$*qå£k£ €!–àR|þ®ëdÁ/1pLù«(ŠÛ¢sÞWŒx’J©IDçu]_ÑÛOcÌSòåsmÛîˆè5"À D'UôÞ34Mã™y_rÐZ?€R*_õ‘÷ž ,Ë]–e/ÖÑ µ¾}–)šUUå™ù™ˆ&cÌݯ~Cžçµµöf«îzRL¢UPÚNIEND®B`‚nzbget-12.0+dfsg/osx/Resources/Images/statusicon@2x.png000066400000000000000000000012201226450633000231040ustar00rootroot00000000000000‰PNG  IHDR"":G ÂbKGDùC» pHYs  šœtIMEÝ 0$-öº¦iTXtCommentCreated with GIMPd.eôIDATXÃÍ—=‹Û@†ß™Ùµ±åe±Œ… †¤ÄM Ä:$µƒ›”éóçü òcÒ¤ ¤É™‹OÚ4+ðïꤳ¦ÑÇêÑ̼3»@„­×kŠy^)ü|ÌÂÀyo|}ŽXTPϨiV­3 mƒwæG[Q (» tE5D âéõzιÛW^–eÒV±F›ˆhQW\'I¢w»]; ‡Ãat6oB<8*1ÄÀ©†|)änºúŸM¾5Ì5 RvbÔLÜw"’$ Ÿ}èšb ¯9=)Š¢úz¥ÃÑ?À‰Œ1}Ǿ¼9Ç;fv‘=#Ä q‹Åâ%ùïf¼ßï3 ƒ/Ì숨l‚ˆ31oÐr¹ä`5F£Ï>2G"ª àAD~[kßÀp8Œ‰ø4íEÄ8Ö€(Ü)¥œµöõ“át:}ç#›¦¥”ËóÜøQßÒ4%¯¦O¾f‚S""¿f³Y@o·ÛF&1Àd2©ÔôçRWוR?çóyÞFVæý¥šñG­õ)MÓ­î‹Ó4%kí+Sœ”NZëûý~ß«À[³ÍfCg‘¹÷5S¥ãG–e+ýg=ñc>ˆÈ‡ø¾Z­–·ØK«JÚJ©o>`æ›3+ižÚ¬þ!Ԇġ¾!IEND®B`‚nzbget-12.0+dfsg/osx/Resources/Localizable.strings000066400000000000000000000076061226450633000222750ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 807 $ * $Date: 2013-08-31 23:14:39 +0200 (Sat, 31 Aug 2013) $ * */ "Status.Downloading"="Downloading at %i KB/s"; "Status.Post-Processing"="Post-Processing"; "Status.Fetching NZBs"="Fetching NZBs"; "Status.Fetching Feeds"="Fetching Feeds"; "Status.Paused"="Paused"; "Status.Idle"="Idle"; "Status.Left"="%@ left"; "Status.NoConnection"="Downloader Not Responding"; "Status.Restarting"="Restarting"; "Left.Days"="%i days"; "Left.Days.Hours"="%id %ih"; "Left.Hours"="%i hours"; "Left.Hours.Minutes"="%ih %02im"; "Left.Minutes"="%im"; "Left.Minutes.Seconds"="%im %02is"; "Left.Seconds"="%is"; "Menu.LinkHomePage"="http://nzbget.sourceforge.net"; "Menu.LinkDownloads"="http://nzbget.sourceforge.net/Download"; "Menu.LinkForum"="http://nzbget.sourceforge.net/forum"; "ShowWebUINoConnection.MessageText"="Could not establish connection to downloader (background process)."; "ShowWebUINoConnection.InformativeText"="Try to restart the program via Troubleshooting-menu."; "RestartNoConnection.MessageText"="Could not establish connection to downloader (background process)."; "RestartNoConnection.InformativeText"="Try to restart the program in recovery mode via Troubleshooting-menu."; "Restarted.MessageText"="The program has been successfully restarted."; "Restarted.InformativeText"=""; "Restarted.WebUI"="Show Web-Interface"; "Restarted.OK"="OK"; "RestartedRecoveryMode.MessageText"="The program has been successfully restarted and is now working on address http://127.0.0.1:6789 without password."; "RestartedRecoveryMode.InformativeText"="To access the program via address, port and password defined in settings the program must be restarted in normal mode using Troubleshooting-menu. A soft-restart via web-interface is not sufficient!"; "FactoryResetted.MessageText"="The program has been reset to factory defaults."; "FactoryResetted.InformativeText"=""; "FactoryReset.MessageText"="Reset to factory defaults?"; "FactoryReset.InformativeText"="All settings will be reset to defaults. The download queue, history, statstics, log-file, default incoming nzb-directory and default ppscripts-directory will be erased. "; "FactoryReset.Cancel"="Cancel"; "FactoryReset.Reset"="Erase and Reset"; "AlreadyRunning.MessageText"="NZBGet is already running."; "AlreadyRunning.InformativeTextWithIcon" = "You can control NZBGet using web-interface or icon in menubar."; "AlreadyRunning.InformativeTextWithoutIcon" = "You can control NZBGet using web-interface. The icon in menubar is currently not displayed because the option 'Show in menubar' was unselected in preferences."; "AlreadyRunning.Quit"="Quit NZBGet"; "AlreadyRunning.Preferences"="Open Preferences"; "AlreadyRunning.WebUI"="Show Web-Interface"; "CantShowInFinder.MessageText"="Cannot open %@."; "CantShowInFinder.InformativeTextWithOption"="The path doesn't exist or refers to a volume not mounted at the moment. Check option %@."; "CantShowInFinder.InformativeTextForCategory"="The path doesn't exist or refers to a volume not mounted at the moment. Please also note that destination directories for categories are created upon first use."; nzbget-12.0+dfsg/osx/Resources/Welcome.rtf000066400000000000000000000021661226450633000205450ustar00rootroot00000000000000{\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf510 {\fonttbl\f0\fnil\fcharset0 LucidaGrande;\f1\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \margl1440\margr1440\vieww9000\viewh8400\viewkind0 \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural \f0\fs32 \cf0 Welcome! \f1\fs24 \ \ \pard\pardeftab720\sa260 \f0\fs26 \cf0 NZBGet is a downloader from binary newsgroups. An account on a news server is needed to use this program. \fs18 \ \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural \fs26 \cf0 NZBGet works as a background process and can be controlled from a web-browser. An icon in menubar gives a quick access to few important functions. \fs18 \ \pard\pardeftab720 \fs26 \cf0 \ This program is free software; you can redistribute it and/or modify it under the terms of the {\field{\*\fldinst{HYPERLINK "http://www.gnu.org/licenses/gpl-2.0.html"}}{\fldrslt GNU General Public License}}.\ \ For more information visit {\field{\*\fldinst{HYPERLINK "http://nzbget.sourceforge.net"}}{\fldrslt NZBGet home page}}.}nzbget-12.0+dfsg/osx/Resources/licenses/000077500000000000000000000000001226450633000202355ustar00rootroot00000000000000nzbget-12.0+dfsg/osx/Resources/licenses/license-bootstrap.txt000066400000000000000000000236751226450633000244500ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS nzbget-12.0+dfsg/osx/Resources/licenses/license-jquery-GPL.txt000066400000000000000000000431311226450633000243570ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. nzbget-12.0+dfsg/osx/Resources/licenses/license-jquery-MIT.txt000066400000000000000000000021131226450633000243610ustar00rootroot00000000000000Copyright 2013 jQuery Foundation and other contributors http://jquery.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. nzbget-12.0+dfsg/osx/WebClient.h000066400000000000000000000025261226450633000165100ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 807 $ * $Date: 2013-08-31 23:14:39 +0200 (Sat, 31 Aug 2013) $ * */ #import @interface WebClient : NSObject { id _receiver; SEL _successCallback; SEL _failureCallback; NSURLConnection *connection; NSMutableData *data; NSInteger failureCode; NSDictionary *responseHeaderFields; } - (id)initWithURLString:(NSString*)urlStr receiver:(id)receiver success:(SEL)successCallback failure:(SEL)failureCallback; - (void)start; - (void)success; - (void)failure; @end nzbget-12.0+dfsg/osx/WebClient.m000066400000000000000000000054111226450633000165110ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 807 $ * $Date: 2013-08-31 23:14:39 +0200 (Sat, 31 Aug 2013) $ * */ #import #import "WebClient.h" @implementation WebClient - (id)initWithURLString:(NSString*)urlStr receiver:(id)receiver success:(SEL)successCallback failure:(SEL)failureCallback { self = [super init]; _receiver = receiver; _successCallback = successCallback; _failureCallback = failureCallback; NSURL *url = [NSURL URLWithString:urlStr]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; [connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; return self; } - (void) start { [connection start]; } - (void)connection:(NSURLConnection *)aConnection didReceiveResponse:(NSHTTPURLResponse *)aResponse { responseHeaderFields = [aResponse allHeaderFields]; if ([aResponse statusCode] != 200) { failureCode = [aResponse statusCode]; [connection cancel]; [self failure]; return; } NSInteger contentLength = [[responseHeaderFields objectForKey:@"Content-Length"] integerValue]; if (contentLength > 0) { data = [[NSMutableData alloc] initWithCapacity:contentLength]; } else { data = [[NSMutableData alloc] init]; } } - (void)connection:(NSURLConnection *)aConnection didReceiveData:(NSData *)newData { [data appendData:newData]; } - (void)connectionDidFinishLoading:(NSURLConnection *)aConnection { [connection cancel]; [self success]; } - (void)connection:(NSURLConnection *)aConnection didFailWithError:(NSError *)error { if ([[error domain] isEqual:NSURLErrorDomain]) { failureCode = [error code]; } [connection cancel]; [self failure]; } - (void)success { SuppressPerformSelectorLeakWarning([_receiver performSelector:_successCallback withObject:data];); } - (void)failure { SuppressPerformSelectorLeakWarning([_receiver performSelector:_failureCallback];); } @end nzbget-12.0+dfsg/osx/WelcomeDialog.h000066400000000000000000000025351226450633000173470ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 807 $ * $Date: 2013-08-31 23:14:39 +0200 (Sat, 31 Aug 2013) $ * */ #import @interface WelcomeDialog : NSWindowController { IBOutlet NSButton *_okButton; IBOutlet NSButton *_dontShowAgainButton; IBOutlet NSImageView *_badgeView; IBOutlet NSImageView * _imageView; IBOutlet NSScrollView * _messageTextScrollView; IBOutlet NSTextView * _messageText; id _mainDelegate; } - (void)setMainDelegate:(id)mainDelegate; - (IBAction)okButtonPressed:(id)sender; - (void)showDialog; @end nzbget-12.0+dfsg/osx/WelcomeDialog.m000066400000000000000000000051711226450633000173530ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 807 $ * $Date: 2013-08-31 23:14:39 +0200 (Sat, 31 Aug 2013) $ * */ #import "WelcomeDialog.h" @implementation WelcomeDialog - (id)init { self = [super initWithWindowNibName:@"WelcomeDialog"]; return self; } - (void)setMainDelegate:(id)mainDelegate{ _mainDelegate = mainDelegate; } - (void)showDialog { BOOL doNotShowWelcomeDialog = [[NSUserDefaults standardUserDefaults] boolForKey:@"DoNotShowWelcomeDialog"]; if (doNotShowWelcomeDialog) { [_mainDelegate performSelector:@selector(welcomeContinue)]; return; } DLog(@"creating window"); NSWindow *window = [self window]; DLog(@"creating window - END"); // set file icon NSImage *image = [NSImage imageNamed:@"mainicon.icns"]; [_imageView setImage:image]; // load warning icon [_badgeView setImage:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kAlertNoteIcon)]]; [[_messageText textStorage] readFromURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"Welcome" ofType:@"rtf"]] options:nil documentAttributes:nil]; // adjust height of text control and window [[_messageText layoutManager] ensureLayoutForTextContainer:[_messageText textContainer]]; NSSize scrollSize = [_messageTextScrollView frame].size; float deltaHeight = [_messageText frame].size.height - scrollSize.height + 6; if (deltaHeight < 0) { deltaHeight = 0; } NSSize winSize = [[window contentView] frame ].size; winSize.height += deltaHeight; [window setContentSize:winSize]; // set active control [window makeFirstResponder:_okButton]; // show modal dialog [NSApp activateIgnoringOtherApps:TRUE]; [window center]; [self showWindow:nil]; } - (IBAction)okButtonPressed:(id)sender { [self close]; [_mainDelegate performSelector:@selector(welcomeContinue)]; } @end nzbget-12.0+dfsg/osx/WelcomeDialog.xib000066400000000000000000000640331226450633000177030ustar00rootroot00000000000000 1060 11G63 3084 1138.51 569.00 com.apple.InterfaceBuilder.CocoaPlugin 3084 NSButton NSButtonCell NSCustomObject NSImageCell NSImageView NSScrollView NSScroller NSTextView NSUserDefaultsController NSView NSWindowTemplate com.apple.InterfaceBuilder.CocoaPlugin PluginDependencyRecalculationVersion WelcomeDialog FirstResponder NSApplication YES 1 2 {{196, 240}, {497, 136}} 611844096 NZBGet NSWindow 256 268 Apple PDF pasteboard type Apple PICT pasteboard type Apple PNG pasteboard type NSFilenamesPboardType NeXT Encapsulated PostScript v1.2 pasteboard type NeXT TIFF v4.0 pasteboard type {{20, 52}, {64, 64}} 2 YES 134348288 33554432 NSImage NSInfo 0 3 0 NO YES -2147483380 Apple PDF pasteboard type Apple PICT pasteboard type Apple PNG pasteboard type NSFilenamesPboardType NeXT Encapsulated PostScript v1.2 pasteboard type NeXT TIFF v4.0 pasteboard type {{60, 60}, {16, 16}} 2 YES 134348288 33554432 0 3 0 NO YES 292 {{17, 18}, {153, 18}} 2 YES 67239424 131072 Don't show again LucidaGrande 11 16 1211912703 2 NSImage NSSwitch NSSwitch 200 25 289 {{387, 12}, {97, 32}} 2 YES 67239424 134217728 Continue LucidaGrande 13 1044 -2038284033 129 DQ 200 25 274 2304 2322 {375, 36} 134 375 1 67119717 0 3 MQA 6 System selectedTextBackgroundColor 3 MC42NjY2NjY2NjY3AA 6 System selectedTextColor 3 MAA 1 MCAwIDEAA {8, -8} 13 1 6 {463, 10000000} {223, 36} {375, 64} {4, 5} 79691776 file://localhost/Applications/Xcode.app/Contents/SharedFrameworks/DVTKit.framework/Resources/DVTIbeamCursor.tiff 3 MCAwAA 2 -2147483392 {{-100, -100}, {15, 62}} _doScroller: 1 0.85256409645080566 -2147483392 {{-100, -100}, {87, 18}} 1 _doScroller: 1 0.94565218687057495 {{102, 52}, {375, 64}} 133120 {497, 136} {{0, 0}, {1440, 878}} {10000000000000, 10000000000000} WelcomeDIalog YES okButtonPressed: 428 _badgeView 441 _imageView 442 _okButton 443 window 445 _dontShowAgainButton 449 _messageText 454 _messageTextScrollView 455 value: values.DoNotShowWelcomeDialog value: values.DoNotShowWelcomeDialog value values.DoNotShowWelcomeDialog 2 424 0 -2 File's Owner -1 First Responder -3 Application 356 383 384 387 388 397 398 417 418 426 427 450 451 452 453 com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin AccessibilityDescription AccessibilityDescription information com.apple.InterfaceBuilder.CocoaPlugin AccessibilityDescription AccessibilityDescription information com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin 455 0 IBCocoaFramework com.apple.InterfaceBuilder.CocoaPlugin.macosx com.apple.InterfaceBuilder.CocoaPlugin.macosx YES 3 {32, 32} {15, 15} nzbget-12.0+dfsg/ppscripts/000077500000000000000000000000001226450633000156745ustar00rootroot00000000000000nzbget-12.0+dfsg/ppscripts/EMail.py000077500000000000000000000206211226450633000172410ustar00rootroot00000000000000#!/usr/bin/env python # # E-Mail post-processing script for NZBGet # # Copyright (C) 2013 Andrey Prygunkov # # 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # $Revision: 905 $ # $Date: 2013-11-08 22:54:44 +0100 (Fri, 08 Nov 2013) $ # ############################################################################## ### NZBGET POST-PROCESSING SCRIPT ### # Send E-Mail notification. # # This script sends E-Mail notification when the job is done. # # NOTE: This script requires Python to be installed on your system. ############################################################################## ### OPTIONS ### # Email address you want this email to be sent from. #From="NZBGet" # Email address you want this email to be sent to. #To=myaccount@gmail.com # SMTP server host. #Server=smtp.gmail.com # SMTP server port (1-65535). #Port=25 # Secure communication using TLS/SSL (yes, no). #Encryption=yes # SMTP server user name, if required. #Username=myaccount # SMTP server password, if required. #Password=mypass # Append list of files to the message (yes, no). # # Add the list of downloaded files (the content of destination directory). #FileList=yes # Append broken-log to the message (yes, no). # # Add the content of file _brokenlog.txt. This file contains the list of damaged # files and the result of par-check/repair. For successful downloads the broken-log # is usually deleted by cleanup-script and therefore is not sent. #BrokenLog=yes # Append post-processing log to the message (Always, Never, OnFailure). # # Add the post-processing log of active job. #PostProcessLog=OnFailure ### NZBGET POST-PROCESSING SCRIPT ### ############################################################################## import os import sys import datetime import smtplib from email.mime.text import MIMEText try: from xmlrpclib import ServerProxy # python 2 except ImportError: from xmlrpc.client import ServerProxy # python 3 # Exit codes used by NZBGet POSTPROCESS_SUCCESS=93 POSTPROCESS_ERROR=94 # Check if the script is called from nzbget 11.0 or later if not 'NZBOP_SCRIPTDIR' in os.environ: print('*** NZBGet post-processing script ***') print('This script is supposed to be called from nzbget (11.0 or later).') sys.exit(POSTPROCESS_ERROR) print('[DETAIL] Script successfully started') sys.stdout.flush() required_options = ('NZBPO_FROM', 'NZBPO_TO', 'NZBPO_SERVER', 'NZBPO_PORT', 'NZBPO_ENCRYPTION', 'NZBPO_USERNAME', 'NZBPO_PASSWORD', 'NZBPO_FILELIST', 'NZBPO_BROKENLOG', 'NZBPO_POSTPROCESSLOG') for optname in required_options: if (not optname in os.environ): print('[ERROR] Option %s is missing in configuration file. Please check script settings' % optname[6:]) sys.exit(POSTPROCESS_ERROR) # Check par and unpack status for errors. success=False if os.environ['NZBPP_PARSTATUS'] == '1' or os.environ['NZBPP_UNPACKSTATUS'] == '1': subject = 'Failure for "%s"' % (os.environ['NZBPP_NZBNAME']) text = 'Download of "%s" has failed.' % (os.environ['NZBPP_NZBNAME']) elif os.environ['NZBPP_UNPACKSTATUS'] in ('3', '4'): subject = 'Unpack failure for "%s"' % (os.environ['NZBPP_NZBNAME']) text = 'Download of "%s" has failed.' % (os.environ['NZBPP_NZBNAME']) elif os.environ['NZBPP_PARSTATUS'] == '4': subject = 'Damaged for "%s"' % (os.environ['NZBPP_NZBNAME']) text = 'Download of "%s" requires par-repair.' % (os.environ['NZBPP_NZBNAME']) else: subject = 'Success for "%s"' % (os.environ['NZBPP_NZBNAME']) text = 'Download of "%s" has successfully completed.' % (os.environ['NZBPP_NZBNAME']) success=True # NZBPP_PARSTATUS - result of par-check: # 0 = not checked: par-check is disabled or nzb-file does # not contain any par-files; # 1 = checked and failed to repair; # 2 = checked and successfully repaired; # 3 = checked and can be repaired but repair is disabled; # 4 = par-check needed but skipped (option ParCheck=manual); parStatus = { '0': 'skipped', '1': 'failed', '2': 'repaired', '3': 'repairable', '4': 'manual' } text += '\nPar-Status: %s' % parStatus[os.environ['NZBPP_PARSTATUS']] # NZBPP_UNPACKSTATUS - result of unpack: # 0 = unpack is disabled or was skipped due to nzb-file # properties or due to errors during par-check; # 1 = unpack failed; # 2 = unpack successful; # 3 = write error (usually not enough disk space); # 4 = wrong password (only for rar5 archives); unpackStatus = { '0': 'skipped', '1': 'failed', '2': 'success', '3': 'write error (usually not enough disk space)', '4': 'wrong password' } text += '\nUnpack-Status: %s' % unpackStatus[os.environ['NZBPP_UNPACKSTATUS']] # add list of downloaded files if os.environ['NZBPO_FILELIST'] == 'yes': text += '\n\nFiles:' for dirname, dirnames, filenames in os.walk(os.environ['NZBPP_DIRECTORY']): for filename in filenames: text += '\n' + os.path.join(dirname, filename)[len(os.environ['NZBPP_DIRECTORY']) + 1:] # add _brokenlog.txt (if exists) if os.environ['NZBPO_BROKENLOG'] == 'yes': brokenlog = '%s/_brokenlog.txt' % os.environ['NZBPP_DIRECTORY'] if os.path.exists(brokenlog): text += '\n\nBrokenlog:\n' + open(brokenlog, 'r').read().strip() # add post-processing log if os.environ['NZBPO_POSTPROCESSLOG'] == 'Always' or \ (os.environ['NZBPO_POSTPROCESSLOG'] == 'OnFailure' and not success): # To get the post-processing log we connect to NZBGet via XML-RPC # and call method "postqueue", which returns the list of post-processing job. # The first item in the list is current job. This item has a field 'Log', # containing an array of log-entries. # For more info visit http://nzbget.sourceforge.net/RPC_API_reference # First we need to know connection info: host, port and password of NZBGet server. # NZBGet passes all configuration options to post-processing script as # environment variables. host = os.environ['NZBOP_CONTROLIP']; port = os.environ['NZBOP_CONTROLPORT']; username = os.environ['NZBOP_CONTROLUSERNAME']; password = os.environ['NZBOP_CONTROLPASSWORD']; if host == '0.0.0.0': host = '127.0.0.1' # Build an URL for XML-RPC requests rpcUrl = 'http://%s:%s@%s:%s/xmlrpc' % (username, password, host, port); # Create remote server object server = ServerProxy(rpcUrl) # Call remote method 'postqueue'. The only parameter tells how many log-entries to return as maximum. postqueue = server.postqueue(10000) # Get field 'Log' from the first post-processing job log = postqueue[0]['Log'] # Now iterate through entries and save them to message text if len(log) > 0: text += '\n\nPost-processing log:'; for entry in log: text += '\n%s\t%s\t%s' % (entry['Kind'], datetime.datetime.fromtimestamp(int(entry['Time'])), entry['Text']) # Create message msg = MIMEText(text) msg['Subject'] = subject msg['From'] = os.environ['NZBPO_FROM'] msg['To'] = os.environ['NZBPO_TO'] # Send message print('[DETAIL] Sending E-Mail') sys.stdout.flush() try: smtp = smtplib.SMTP(os.environ['NZBPO_SERVER'], os.environ['NZBPO_PORT']) if os.environ['NZBPO_ENCRYPTION'] == 'yes': smtp.starttls() if os.environ['NZBPO_USERNAME'] != '' and os.environ['NZBPO_PASSWORD'] != '': smtp.login(os.environ['NZBPO_USERNAME'], os.environ['NZBPO_PASSWORD']) smtp.sendmail(os.environ['NZBPO_FROM'], os.environ['NZBPO_TO'], msg.as_string()) smtp.quit() except Exception as err: print('[ERROR] %s' % err) sys.exit(POSTPROCESS_ERROR) # All OK, returning exit status 'POSTPROCESS_SUCCESS' (int <93>) to let NZBGet know # that our script has successfully completed. sys.exit(POSTPROCESS_SUCCESS) nzbget-12.0+dfsg/ppscripts/Logger.py000077500000000000000000000071711226450633000174760ustar00rootroot00000000000000#!/usr/bin/env python # # Logger post-processing script for NZBGet # # Copyright (C) 2013 Andrey Prygunkov # # 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # $Revision: 707 $ # $Date: 2013-06-12 22:25:06 +0200 (Wed, 12 Jun 2013) $ # ############################################################################## ### NZBGET POST-PROCESSING SCRIPT ### # Save post-processing log into a file. # # This script saves post-processing log of nzb-file into file # _postprocesslog.txt in the destination directory. # # NOTE: This script requires Python to be installed on your system. ### NZBGET POST-PROCESSING SCRIPT ### ############################################################################## import os import sys import datetime try: from xmlrpclib import ServerProxy # python 2 except ImportError: from xmlrpc.client import ServerProxy # python 3 # Exit codes used by NZBGet POSTPROCESS_SUCCESS=93 POSTPROCESS_NONE=95 POSTPROCESS_ERROR=94 # Check if the script is called from nzbget 11.0 or later if not 'NZBOP_SCRIPTDIR' in os.environ: print('*** NZBGet post-processing script ***') print('This script is supposed to be called from nzbget (11.0 or later).') sys.exit(POSTPROCESS_ERROR) if not os.path.exists(os.environ['NZBPP_DIRECTORY']): print('Destination directory doesn\'t exist, exiting') sys.exit(POSTPROCESS_NONE) # To get the post-processing log we connect to NZBGet via XML-RPC # and call method "postqueue", which returns the list of post-processing job. # The first item in the list is current job. This item has a field 'Log', # containing an array of log-entries. # For more info visit http://nzbget.sourceforge.net/RPC_API_reference # First we need to know connection info: host, port and password of NZBGet server. # NZBGet passes all configuration options to post-processing script as # environment variables. host = os.environ['NZBOP_CONTROLIP']; port = os.environ['NZBOP_CONTROLPORT']; username = os.environ['NZBOP_CONTROLUSERNAME']; password = os.environ['NZBOP_CONTROLPASSWORD']; if host == '0.0.0.0': host = '127.0.0.1' # Build an URL for XML-RPC requests rpcUrl = 'http://%s:%s@%s:%s/xmlrpc' % (username, password, host, port); # Create remote server object server = ServerProxy(rpcUrl) # Call remote method 'postqueue'. The only parameter tells how many log-entries to return as maximum. postqueue = server.postqueue(10000) # Get field 'Log' from the first post-processing job log = postqueue[0]['Log'] # Now iterate through entries and save them to the output file if len(log) > 0: f = open('%s/_postprocesslog.txt' % os.environ['NZBPP_DIRECTORY'], 'wb') for entry in log: f.write((u'%s\t%s\t%s\n' % (entry['Kind'], datetime.datetime.fromtimestamp(int(entry['Time'])), entry['Text'])).encode('utf8')) f.close() # All OK, returning exit status 'POSTPROCESS_SUCCESS' (int <93>) to let NZBGet know # that our script has successfully completed. sys.exit(POSTPROCESS_SUCCESS) nzbget-12.0+dfsg/svn_version.cpp000066400000000000000000000002571226450633000167300ustar00rootroot00000000000000/* 930 */ /* This file is automatically regenerated on each build. Do not edit it. */ const char* svn_version(void) { const char* SVN_Version = "930"; return SVN_Version; } nzbget-12.0+dfsg/webui/000077500000000000000000000000001226450633000147605ustar00rootroot00000000000000nzbget-12.0+dfsg/webui/config.js000066400000000000000000002102261226450633000165660ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2012-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 881 $ * $Date: 2013-10-17 21:35:43 +0200 (Thu, 17 Oct 2013) $ * */ /* * In this module: * 1) Loading of program options and post-processing scripts options; * 2) Settings tab; * 3) Function "Reload". */ /*** OPTIONS AND CONFIGS (FROM CONFIG FILES) **************************************/ var Options = (new function($) { 'use strict'; // Properties (public) this.options; this.postParamConfig; this.categories = []; // State var _this = this; var serverTemplateData = null; var serverValues; var loadComplete; var loadConfigError; var loadServerTemplateError; var shortScriptNames = []; var HIDDEN_SECTIONS = ['DISPLAY (TERMINAL)', 'POSTPROCESSING-PARAMETERS', 'POST-PROCESSING-PARAMETERS', 'POST-PROCESSING PARAMETERS']; var POSTPARAM_SECTIONS = ['POSTPROCESSING-PARAMETERS', 'POST-PROCESSING-PARAMETERS', 'POST-PROCESSING PARAMETERS']; this.init = function() { } this.update = function() { // RPC-function "config" returns CURRENT configurations settings loaded in NZBGet RPC.call('config', [], function(_options) { _this.options = _options; initCategories(); // loading config templates and build list of post-processing parameters _this.postParamConfig = []; RPC.call('configtemplates', [], function(data) { initPostParamConfig(data); RPC.next(); }, RPC.next); }); } this.cleanup = function() { serverTemplateData = null; serverValues = null; } this.option = function(name) { var opt = findOption(this.options, name); return opt ? opt.Value : null; } function initCategories() { _this.categories = []; for (var i=0; i < _this.options.length; i++) { var option = _this.options[i]; if ((option.Name.toLowerCase().substring(0, 8) === 'category') && (option.Name.toLowerCase().indexOf('.name') > -1)) { _this.categories.push(option.Value); } } } /*** LOADING CONFIG ********************************************************************/ this.loadConfig = function(callbacks) { loadComplete = callbacks.complete; loadConfigError = callbacks.configError; loadServerTemplateError = callbacks.serverTemplateError; // RPC-function "loadconfig" reads the configuration settings from NZBGet configuration file. // that's not neccessary the same settings returned by RPC-function "config". This could be the case, // for example, if the settings were modified but NZBGet was not restarted. RPC.call('loadconfig', [], serverValuesLoaded, loadConfigError); } function serverValuesLoaded(data) { serverValues = data; RPC.call('configtemplates', [], serverTemplateLoaded, loadServerTemplateError); } function serverTemplateLoaded(data) { serverTemplateData = data; complete(); } function complete() { initShortScriptNames(serverTemplateData); if (serverTemplateData === null) { // the loading was cancelled and the data was discarded (via method "cleanup()") return; } var config = []; var serverConfig = readConfigTemplate(serverTemplateData[0].Template, undefined, HIDDEN_SECTIONS, '', ''); mergeValues(serverConfig.sections, serverValues); config.values = serverValues; config.push(serverConfig); // read scripts configs for (var i=1; i < serverTemplateData.length; i++) { var scriptName = serverTemplateData[i].Name; var scriptConfig = readConfigTemplate(serverTemplateData[i].Template, undefined, HIDDEN_SECTIONS, scriptName + ':'); scriptConfig.scriptName = scriptName; scriptConfig.id = scriptName.replace(/ |\/|\\|[\.|$|\:|\*]/g, '_'); scriptConfig.name = scriptName.substr(0, scriptName.lastIndexOf('.')) || scriptName; // remove file extension scriptConfig.name = scriptConfig.name.replace(/\\/, ' \\ ').replace(/\//, ' / '); scriptConfig.shortName = shortScriptName(scriptName); scriptConfig.shortName = scriptConfig.shortName.replace(/\\/, ' \\ ').replace(/\//, ' / '); mergeValues(scriptConfig.sections, serverValues); config.push(scriptConfig); } serverValues = null; loadComplete(config); } this.reloadConfig = function(_serverValues, _complete) { loadComplete = _complete; serverValues = _serverValues; complete(); } /*** PARSE CONFIG AND BUILD INTERNAL STRUCTURES **********************************************/ function readConfigTemplate(filedata, visiblesections, hiddensections, nameprefix) { var config = { description: '', nameprefix: nameprefix, sections: [] }; var section = null; var description = ''; var firstdescrline = ''; var data = filedata.split('\n'); for (var i=0, len=data.length; i < len; i++) { var line = data[i].replace(/\r+$/,''); // remove possible trailing CR-characters if (line.substring(0, 4) === '### ') { var section = {}; section.name = line.substr(4, line.length - 8).trim(); section.id = (nameprefix + section.name).replace(/ |\/|\\|[\.|$|\:|\*]/g, '_'); section.options = []; description = ''; section.hidden = !(hiddensections === undefined || (hiddensections.indexOf(section.name) == -1)) || (visiblesections !== undefined && (visiblesections.indexOf(section.name) == -1)); section.postparam = POSTPARAM_SECTIONS.indexOf(section.name) > -1; config.sections.push(section); } else if (line.substring(0, 2) === '# ' || line === '#') { if (description !== '') { description += ' '; } if (line[2] === ' ' && line[3] !== ' ' && description.substring(description.length-4, 4) != '\n \n ') { description += '\n'; } description += line.substr(1, 10000).trim(); var lastchar = description.substr(description.length - 1, 1); if (lastchar === '.' && firstdescrline === '') { firstdescrline = description; description = ''; } if ('.;:'.indexOf(lastchar) > -1 || line === '#') { description += '\n'; } } else if (line.indexOf('=') > -1) { if (!section) { // bad template file; create default section. section = {}; section.name = 'OPTIONS'; section.id = (nameprefix + section.name).replace(/ |\/|[\.|$|\:|\*]/g, '_'); section.options = []; description = ''; config.sections.push(section); } var option = {}; var enabled = line.substr(0, 1) !== '#'; option.caption = line.substr(enabled ? 0 : 1, line.indexOf('=') - (enabled ? 0 : 1)).trim(); option.name = (nameprefix != '' ? nameprefix : '') + option.caption; option.defvalue = line.substr(line.indexOf('=') + 1, 1000).trim(); option.value = null; option.sectionId = section.id; option.select = []; var pstart = firstdescrline.lastIndexOf('('); var pend = firstdescrline.lastIndexOf(')'); if (pstart > -1 && pend > -1 && pend === firstdescrline.length - 2) { var paramstr = firstdescrline.substr(pstart + 1, pend - pstart - 1); var params = paramstr.split(','); for (var pj=0; pj < params.length; pj++) { option.select.push(params[pj].trim()); } firstdescrline = firstdescrline.substr(0, pstart).trim() + '.'; } if (option.name.substr(nameprefix.length, 1000).indexOf('1.') > -1) { section.multi = true; section.multiprefix = option.name.substr(0, option.name.indexOf('1.')); } if (!section.multi || option.name.indexOf('1.') > -1) { section.options.push(option); } if (section.multi) { option.template = true; } option.description = firstdescrline + description; description = ''; firstdescrline = ''; } else { if (!section && firstdescrline !== '') { config.description = firstdescrline + description; } else if (section && section.options.length === 0) { section.description = firstdescrline + description; } description = ''; firstdescrline = ''; } } return config; } function findOption(options, name) { if (!options) { return null; } name = name.toLowerCase(); for (var i=0; i < options.length; i++) { var option = options[i]; if ((option.Name && option.Name.toLowerCase() === name) || (option.name && option.name.toLowerCase() === name)) { return option; } } return null; } this.findOption = findOption; function mergeValues(config, values) { // copy values for (var i=0; i < config.length; i++) { var section = config[i]; if (section.multi) { // multi sections (news-servers, scheduler) var subexists = true; for (var k=1; subexists; k++) { subexists = false; for (var m=0; m < section.options.length; m++) { var option = section.options[m]; if (option.name.indexOf('1.') > -1) { var name = option.name.replace(/1/, k); var val = findOption(values, name); if (val) { subexists = true; break; } } } if (subexists) { for (var m=0; m < section.options.length; m++) { var option = section.options[m]; if (option.template) { var name = option.name.replace(/1/, k); // copy option var newoption = $.extend({}, option); newoption.name = name; newoption.caption = option.caption.replace(/1/, k); newoption.template = false; newoption.multiid = k; newoption.value = null; section.options.push(newoption); var val = findOption(values, name); if (val) { newoption.value = val.Value; } } } } } } else { // simple sections for (var j=0; j < section.options.length; j++) { var option = section.options[j]; option.value = null; var val = findOption(values, option.name); if (val) { option.value = val.Value; } } } } } this.mergeValues = mergeValues; function initShortScriptNames(configTemplatesData) { for (var i=1; i < configTemplatesData.length; i++) { shortScriptNames[configTemplatesData[i].Name] = configTemplatesData[i].DisplayName; } } function shortScriptName(scriptName) { var shortName = shortScriptNames[scriptName]; return shortName ? shortName : scriptName; } this.shortScriptName = shortScriptName; function initPostParamConfig(data) { initShortScriptNames(data); // Create one big post-param section. It consists of one item for every post-processing script // and additionally includes all post-param options from post-param section of each script. var section = {}; section.id = 'PP-Parameters'; section.options = []; section.description = ''; section.hidden = false; section.postparam = true; _this.postParamConfig = [section]; for (var i=1; i < data.length; i++) { var scriptName = data[i].Name; var sectionId = (scriptName + ':').replace(/ |\/|[\.|$|\:|\*]/g, '_'); var option = {}; option.name = scriptName + ':'; option.caption = shortScriptName(scriptName); option.caption = option.caption.replace(/\\/, ' \\ ').replace(/\//, ' / '); option.defvalue = 'no'; option.description = (data[i].Template.trim().split('\n')[0].substr(1, 1000).trim() || 'Post-processing script ' + scriptName + '.'); option.value = null; option.sectionId = sectionId; option.select = ['yes', 'no']; section.options.push(option); var templateData = data[i].Template; var postConfig = readConfigTemplate(templateData, POSTPARAM_SECTIONS, undefined, scriptName + ':'); for (var j=0; j < postConfig.sections.length; j++) { var sec = postConfig.sections[j]; if (!sec.hidden) { for (var n=0; n < sec.options.length; n++) { var option = sec.options[n]; option.sectionId = sectionId; section.options.push(option); } } } } } }(jQuery)); /*** SETTINGS TAB (UI) *********************************************************/ var Config = (new function($) { 'use strict'; // Controls var $ConfigNav; var $ConfigData; var $ConfigTabBadge; var $ConfigTabBadgeEmpty; var $ConfigContent; var $ConfigInfo; var $ConfigTitle; var $ConfigTable; var $ViewButton; var $LeaveConfigDialog; var $Body; // State var config = null; var values; var filterText = ''; var lastSection; var reloadTime; var updateTabInfo; var restored = false; var compactMode = false; var configSaved = false; var leaveTarget; this.init = function(options) { updateTabInfo = options.updateTabInfo; $Body = $('html, body'); $ConfigNav = $('#ConfigNav'); $ConfigData = $('#ConfigData'); $ConfigTabBadge = $('#ConfigTabBadge'); $ConfigTabBadgeEmpty = $('#ConfigTabBadgeEmpty'); $ConfigContent = $('#ConfigContent'); $ConfigInfo = $('#ConfigInfo'); $ConfigTitle = $('#ConfigTitle'); $ViewButton = $('#Config_ViewButton'); $LeaveConfigDialog = $('#LeaveConfigDialog'); Util.show('#ConfigBackupSafariNote', $.browser.safari); $('#ConfigTable_filter').val(''); compactMode = UISettings.read('$Config_ViewCompact', 'no') == 'yes'; setViewMode(); $(window).bind('beforeunload', userLeavesPage); $ConfigNav.on('click', 'li > a', navClick); $ConfigTable = $({}); $ConfigTable.fasttable( { filterInput: $('#ConfigTable_filter'), filterClearButton: $("#ConfigTable_clearfilter"), filterInputCallback: filterInput, filterClearCallback: filterClear }); } this.config = function() { return config; } this.show = function() { removeSaveBanner(); $('#ConfigSaved').hide(); $('#ConfigLoadInfo').show(); $('#ConfigLoadServerTemplateError').hide(); $('#ConfigLoadError').hide(); $ConfigContent.hide(); configSaved = false; } this.shown = function() { Options.loadConfig({ complete: buildPage, configError: loadConfigError, serverTemplateError: loadServerTemplateError }); } this.hide = function() { Options.cleanup(); config = null; $ConfigNav.children().not('.config-static').remove(); $ConfigData.children().not('.config-static').remove(); } function loadConfigError(message, resultObj) { $('#ConfigLoadInfo').hide(); $('#ConfigLoadError').show(); if (resultObj && resultObj.error && resultObj.error.message) { message = resultObj.error.message; } $('#ConfigLoadErrorText').text(message); } function loadServerTemplateError() { $('#ConfigLoadInfo').hide(); $('#ConfigLoadServerTemplateError').show(); var optConfigTemplate = Options.option('ConfigTemplate'); $('#ConfigLoadServerTemplateErrorEmpty').toggle(optConfigTemplate === ''); $('#ConfigLoadServerTemplateErrorNotFound').toggle(optConfigTemplate !== ''); $('#ConfigLoadServerTemplateErrorWebDir').text(Options.option('WebDir')); $('#ConfigLoadServerTemplateErrorConfigFile').text(Options.option('ConfigFile')); } function findOptionByName(name) { name = name.toLowerCase(); for (var k=0; k < config.length; k++) { var sections = config[k].sections; for (var i=0; i < sections.length; i++) { var section = sections[i]; for (var j=0; j < section.options.length; j++) { var option = section.options[j]; if (!option.template && ((option.Name && option.Name.toLowerCase() === name) || (option.name && option.name.toLowerCase() === name))) { return option; } } } } return null; } this.findOptionByName = findOptionByName; function findOptionById(formId) { for (var k=0; k < config.length; k++) { var sections = config[k].sections; for (var i=0; i < sections.length; i++) { var section = sections[i]; for (var j=0; j < section.options.length; j++) { var option = section.options[j]; if (option.formId === formId) { return option; } } } } return null; } function findSectionById(sectionId) { for (var k=0; k < config.length; k++) { var sections = config[k].sections; for (var i=0; i < sections.length; i++) { var section = sections[i]; if (section.id === sectionId) { return section; } } } return null; } /*** GENERATE HTML PAGE *****************************************************************/ function buildOptionsContent(section) { var html = ''; var lastmultiid = 1; var firstmultioption = true; var hasoptions = false; for (var i=0, op=0; i < section.options.length; i++) { var option = section.options[i]; if (!option.template) { if (section.multi && option.multiid !== lastmultiid) { // new set in multi section html += buildMultiRowEnd(section, lastmultiid, true, true); lastmultiid = option.multiid; firstmultioption = true; } if (section.multi && firstmultioption) { html += buildMultiRowStart(section, option.multiid, option); firstmultioption = false; } html += buildOptionRow(option, section); hasoptions = true; op++; } } if (section.multi) { html += buildMultiRowEnd(section, lastmultiid, false, hasoptions); } return html; } this.buildOptionsContent = buildOptionsContent; function buildMultiSetContent(section, multiid) { var html = ''; var firstmultioption = true; var hasoptions = false; for (var i=0, op=0; i < section.options.length; i++) { var option = section.options[i]; if (!option.template && option.multiid === multiid) { if (firstmultioption) { html += buildMultiRowStart(section, multiid, option); firstmultioption = false; } html += buildOptionRow(option, section); hasoptions = true; op++; } } html += buildMultiRowEnd(section, multiid, true, hasoptions); return html; } function buildOptionRow(option, section) { var value = option.value; if (option.value === null) { value = option.defvalue; } option.formId = (option.name.indexOf(':') == -1 ? 'S_' : '') + option.name.replace(/ |\/|\\|[\.|$|\:|\*]/g, '_'); var caption = option.caption; if (section.multi) { caption = '' + caption.substring(0, caption.indexOf('.') + 1) + '' + caption.substring(caption.indexOf('.') + 1); } var html = '
'+ ''+ '
'; if (option.nocontent) { option.type = 'info'; html += '
'; } else if (option.select.length > 1) { option.type = 'switch'; html += '
'; var valfound = false; for (var j=0; j < option.select.length; j++) { var pvalue = option.select[j]; if (value && pvalue.toLowerCase() === value.toLowerCase()) { html += ''; valfound = true; } else { html += ''; } } if (!valfound) { html += ''; } html +='
'; } else if (option.select.length === 1) { option.type = 'numeric'; html += '
'+ ''+ ''+ option.select[0] +''+ '
'; } else if (option.name.toLowerCase() === 'serverpassword') { option.type = 'password'; html += ''; } else if (option.name.toLowerCase().indexOf('username') > -1 || option.name.toLowerCase().indexOf('password') > -1 || (option.name.indexOf('IP') > -1 && option.name.toLowerCase() !== 'authorizedip')) { option.type = 'text'; html += ''; } else if (option.editor) { option.type = 'text'; html += '
'; html += ''; html += ''; html += ''; html += '
'; } else { option.type = 'text'; html += ''; } if (option.description !== '') { var htmldescr = option.description; htmldescr = htmldescr.replace(/NOTE: do not forget to uncomment the next line.\n/, ''); // replace option references var exp = /\<([A-Z0-9]*)\>/ig; htmldescr = htmldescr.replace(exp, '$1'); htmldescr = htmldescr.replace(/&/g, '&'); // replace URLs exp = /(http:\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig; htmldescr = htmldescr.replace(exp, "$1"); // highlight first line htmldescr = htmldescr.replace(/\n/, '\n'); htmldescr = '' + htmldescr; htmldescr = htmldescr.replace(/\n/g, '
'); htmldescr = htmldescr.replace(/NOTE: /g, 'NOTE: '); htmldescr = htmldescr.replace(/INFO: /g, 'INFO: '); if (htmldescr.indexOf('INFO FOR DEVELOPERS:') > -1) { htmldescr = htmldescr.replace(/INFO FOR DEVELOPERS:
/g, ''); htmldescr += ''; } if (htmldescr.indexOf('MORE INFO:') > -1) { htmldescr = htmldescr.replace(/MORE INFO:
/g, ''); htmldescr += ''; } if (section.multi) { // replace strings like "TaskX.Command" and "Task1.Command" htmldescr = htmldescr.replace(new RegExp(section.multiprefix + '[X|1]\.', 'g'), ''); } html += '

' + htmldescr + '

'; } html += '
'; html += '
'; return html; } function buildMultiRowStart(section, multiid, option) { var name = option.caption; var setname = name.substr(0, name.indexOf('.')); var html = '
' + setname + '
'; return html; } function buildMultiRowEnd(section, multiid, hasmore, hasoptions) { var name = section.options[0].caption; var setname = name.substr(0, name.indexOf('1')); var html = ''; if (hasoptions) { html += '
'; html += ''; if (setname.toLowerCase() === 'feed') { html += ' '; } html += '
'; html += '
'; } if (!hasmore) { var nextid = hasoptions ? multiid + 1 : 1; html += '
'; html += ''; html += '
'; } return html; } function buildPage(_config) { config = _config; extendConfig(); $ConfigNav.children().not('.config-static').remove(); $ConfigData.children().not('.config-static').remove(); $ConfigNav.append('
  • '); for (var k=0; k < config.length; k++) { if (k == 1) { $ConfigNav.append('
  • '); } var conf = config[k]; var added = false; for (var i=0; i < conf.sections.length; i++) { var section = conf.sections[i]; if (!section.hidden) { var html = $('
  • ' + section.name + '
  • '); $ConfigNav.append(html); var content = buildOptionsContent(section); $ConfigData.append(content); added = true; } } if (!added) { var html = $('
  • ' + conf.name + '
  • '); $ConfigNav.append(html); } } $ConfigNav.append('
  • '); $ConfigNav.append('
  • SEARCH RESULTS
  • '); $ConfigNav.toggleClass('long-list', $ConfigNav.children().length > 20); showSection('Config-Info', false); if (filterText !== '') { filterInput(filterText); } $('#ConfigLoadInfo').hide(); $ConfigContent.show(); } function extendConfig() { for (var i=1; i < config.length; i++) { var conf = config[i]; var firstVisibleSection = null; var visibleSections = 0; for (var j=0; j < conf.sections.length; j++) { if (!conf.sections[j].hidden) { if (!firstVisibleSection) { firstVisibleSection = conf.sections[j]; } visibleSections++; } } // rename sections for (var j=0; j < conf.sections.length; j++) { var section = conf.sections[j]; section.name = conf.shortName.toUpperCase() + (visibleSections > 1 ? ' - ' + section.name.toUpperCase() + '' : ''); section.caption = conf.name.toUpperCase() + (visibleSections > 1 ? ' - ' + section.name.toUpperCase() + '' : ''); } if (!firstVisibleSection) { // create new section for virtual option "About". var section = {}; section.name = conf.shortName.toUpperCase(); section.caption = conf.name.toUpperCase(); section.id = conf.id + '_'; section.options = []; firstVisibleSection = section; conf.sections.push(section); } // create virtual option "About" with scripts description. var option = {}; var shortName = conf.scriptName.replace(/^.*[\\\/]/, ''); // leave only file name (remove path) shortName = shortName.substr(0, shortName.lastIndexOf('.')) || shortName; // remove file extension option.caption = 'About ' + shortName; option.name = conf.nameprefix + option.caption; option.value = ''; option.defvalue = ''; option.sectionId = firstVisibleSection.id; option.select = []; var description = conf.description; option.description = description !== '' ? description : 'No description available.\n\nNOTE: The script doesn\'t have a description section. '+ 'It\'s either not NZBGet script or a script created for an older NZBGet version and might not work properly.'; option.nocontent = true; firstVisibleSection.options.unshift(option); } // register editors for options "DefScript", "ScriptOrder" and FeedX.Filter var conf = config[0]; for (var j=0; j < conf.sections.length; j++) { var section = conf.sections[j]; for (var k=0; k < section.options.length; k++) { var option = section.options[k]; var optname = option.name.toLowerCase(); if (optname.indexOf('scriptorder') > -1) { option.editor = { caption: 'Reorder', click: 'Config.editScriptOrder' }; } if (optname.indexOf('defscript') > -1) { option.editor = { caption: 'Choose', click: 'Config.editDefScript' }; } if (optname.indexOf('.filter') > -1) { option.editor = { caption: 'Change', click: 'Config.editFilter' }; } } } } function scrollOptionIntoView(optFormId) { var option = findOptionById(optFormId); // switch to tab and scroll the option into view showSection(option.sectionId, false); var element = $('#' + option.formId); var parent = $('html,body'); parent[0].scrollIntoView(true); var offsetY = 15; if ($('body').hasClass('navfixed')) { offsetY = 55; } parent.animate({ scrollTop: parent.scrollTop() + element.offset().top - parent.offset().top - offsetY }, { duration: 'slow', easing: 'swing' }); } this.switchClick = function(control) { var state = $(control).val().toLowerCase(); $('.btn', $(control).parent()).removeClass('btn-primary'); $(control).addClass('btn-primary'); } function switchGetValue(control) { var state = $('.btn-primary', $(control).parent()).val(); return state; } /*** CHANGE/ADD/REMOVE OPTIONS *************************************************************/ function navClick(event) { event.preventDefault(); var sectionId = $(this).attr('href').substr(1); showSection(sectionId, true); } function showSection(sectionId, animateScroll) { var link = $('a[href="#' + sectionId + '"]', $ConfigNav); $('li', $ConfigNav).removeClass('active'); link.closest('li').addClass('active'); $ConfigContent.removeClass('search'); Util.show($ViewButton, sectionId !== 'Config-Info'); $ConfigInfo.hide(); if (sectionId === 'Search') { search(); return; } lastSection = sectionId; if (sectionId === 'Config-Info') { $ConfigInfo.show(); $ConfigData.children().hide(); $ConfigTitle.text('INFO: SETTINGS'); return; } if (sectionId === 'Config-System') { $ConfigData.children().hide(); $('.config-system', $ConfigData).show(); markLastControlGroup(); $ConfigTitle.text('SYSTEM'); return; } $ConfigData.children().hide(); var opts = $('.' + sectionId, $ConfigData); opts.show(); markLastControlGroup(); var section = findSectionById(sectionId); $ConfigTitle.text(section.caption ? section.caption : section.name); $Body.animate({ scrollTop: 0 }, { duration: animateScroll ? 'slow' : 0, easing: 'swing' }); } this.deleteSet = function(control, setname, sectionId) { var multiid = parseInt($(control).attr('data-multiid')); $('#ConfigDeleteConfirmDialog_Option').text(setname + multiid); ConfirmDialog.showModal('ConfigDeleteConfirmDialog', function() { deleteOptionSet(setname, multiid, sectionId); }); } function deleteOptionSet(setname, multiid, sectionId) { // remove options from page, using a temporary div for slide effect var opts = $('.' + sectionId + '.multiid' + multiid, $ConfigData); var div = $('
    '); opts.first().before(div); div.append(opts); div.slideUp('normal', function() { div.remove(); }); // remove option set from config var section = findSectionById(sectionId); for (var j=0; j < section.options.length; j++) { var option = section.options[j]; if (!option.template && option.multiid === multiid) { section.options.splice(j, 1); j--; } } // reformat remaining sets (captions, input IDs, etc.) reformatSection(section, setname); section.modified = true; } function reformatSection(section, setname) { var oldMultiId = -1; var newMultiId = 0; for (var j=0; j < section.options.length; j++) { var option = section.options[j]; if (!option.template) { if (option.multiid !== oldMultiId) { oldMultiId = option.multiid; newMultiId++; // reformat multiid var div = $('#' + setname + oldMultiId); div.attr('id', setname + newMultiId); // update captions $('.config-settitle.' + section.id + '.multiid' + oldMultiId, $ConfigData).text(setname + newMultiId); $('.' + section.id + '.multiid' + oldMultiId + ' .config-multicaption', $ConfigData).text(setname + newMultiId + '.'); $('.' + section.id + '.multiid' + oldMultiId + ' .config-delete', $ConfigData).text('Delete ' + setname + newMultiId).attr('data-multiid', newMultiId); $('.' + section.id + '.multiid' + oldMultiId + ' .config-feed', $ConfigData).attr('data-multiid', newMultiId); //update class $('.' + section.id + '.multiid' + oldMultiId, $ConfigData).removeClass('multiid' + oldMultiId).addClass('multiid' + newMultiId); } // update input id var oldFormId = option.formId; option.formId = option.formId.replace(new RegExp(option.multiid), newMultiId); $('#' + oldFormId).attr('id', option.formId); // update name option.name = option.name.replace(new RegExp(option.multiid), newMultiId); option.multiid = newMultiId; } } // update add-button var addButton = $('.config-add.' + section.id, $ConfigData); addButton.text('Add ' + (newMultiId > 0 ? 'another ' : '') + setname); } this.addSet = function(setname, sectionId) { // find section var section = findSectionById(sectionId); // find max multiid var multiid = 0; for (var j=0; j < section.options.length; j++) { var option = section.options[j]; if (!option.template && option.multiid > multiid) { multiid = option.multiid; } } multiid++; // create new multi set for (var j=0; j < section.options.length; j++) { var option = section.options[j]; if (option.template) { var name = option.name.replace(/1/, multiid); // copy option var newoption = $.extend({}, option); newoption.name = name; newoption.caption = option.caption.replace(/1/, multiid); newoption.template = false; newoption.multiid = multiid; section.options.push(newoption); } } section.modified = true; // visualize new multi set var html = buildMultiSetContent(section, multiid); var addButton = $('.config-add.' + section.id, $ConfigData); addButton.text('Add another ' + setname); // insert before add-button, using a temporary div for slide effect var div = $('
    ' + html + '
    '); div.hide(); addButton.parent().before(div); div.slideDown('normal', function() { var opts = div.children(); opts.detach(); div.after(opts); div.remove(); }); } this.viewMode = function() { compactMode = !compactMode; UISettings.write('$Config_ViewCompact', compactMode ? 'yes' : 'no'); setViewMode(); } function setViewMode() { $('#Config_ViewCompact i').toggleClass('icon-ok', compactMode).toggleClass('icon-empty', !compactMode); $ConfigContent.toggleClass('hide-help-block', compactMode); } /*** OPTION SPECIFIC EDITORS *************************************************/ this.editScriptOrder = function(optFormId) { var option = findOptionById(optFormId); ScriptListDialog.showModal(option, config); } this.editDefScript = function(optFormId) { var option = findOptionById(optFormId); ScriptListDialog.showModal(option, config); } /*** RSS FEEDS ********************************************************************/ this.editFilter = function(optFormId) { var option = findOptionById(optFormId); FeedFilterDialog.showModal( getOptionValue(findOptionByName('Feed' + option.multiid + '.Name')), getOptionValue(findOptionByName('Feed' + option.multiid + '.URL')), getOptionValue(findOptionByName('Feed' + option.multiid + '.Filter')), getOptionValue(findOptionByName('Feed' + option.multiid + '.PauseNzb')), getOptionValue(findOptionByName('Feed' + option.multiid + '.Category')), getOptionValue(findOptionByName('Feed' + option.multiid + '.Priority')), function(filter) { var control = $('#' + option.formId); control.val(filter); }); } this.previewFeed = function(control, setname, sectionId) { var multiid = parseInt($(control).attr('data-multiid')); FeedDialog.showModal(0, getOptionValue(findOptionByName('Feed' + multiid + '.Name')), getOptionValue(findOptionByName('Feed' + multiid + '.URL')), getOptionValue(findOptionByName('Feed' + multiid + '.Filter')), getOptionValue(findOptionByName('Feed' + multiid + '.PauseNzb')), getOptionValue(findOptionByName('Feed' + multiid + '.Category')), getOptionValue(findOptionByName('Feed' + multiid + '.Priority'))); } /*** SAVE ********************************************************************/ function getOptionValue(option) { var control = $('#' + option.formId); if (option.type === 'switch') { return switchGetValue(control); } else { return control.val(); } } this.getOptionValue = getOptionValue; // Checks if there are obsolete or invalid options function invalidOptionsExist() { var hiddenOptions = ['ConfigFile', 'AppBin', 'AppDir', 'Version']; for (var i=0; i < Options.options.length; i++) { var option = Options.options[i]; var confOpt = findOptionByName(option.Name); if (!confOpt && hiddenOptions.indexOf(option.Name) === -1) { return true; } } return false; } function prepareSaveRequest(onlyUserChanges) { var modified = false; var request = []; for (var k=0; k < config.length; k++) { var sections = config[k].sections; for (var i=0; i < sections.length; i++) { var section = sections[i]; if (!section.postparam) { for (var j=0; j < section.options.length; j++) { var option = section.options[j]; if (!option.template && !(option.type === 'info')) { var oldValue = option.value; var newValue = getOptionValue(option); if (section.hidden) { newValue = oldValue; } if (newValue != null) { if (onlyUserChanges) { modified = modified || (oldValue != newValue && oldValue !== null); } else { modified = modified || (oldValue != newValue) || (option.value === null); } var opt = {Name: option.name, Value: newValue}; request.push(opt); } } modified = modified || section.modified; } } } } return modified || (!onlyUserChanges && invalidOptionsExist()) || restored ? request : []; } this.saveChanges = function() { $LeaveConfigDialog.modal('hide'); var serverSaveRequest = prepareSaveRequest(false); if (serverSaveRequest.length === 0) { Notification.show('#Notif_Config_Unchanged'); return; } showSaveBanner(); Util.show('#ConfigSaved_Reload, #ConfigReload', serverSaveRequest.length > 0); if (serverSaveRequest.length > 0) { $('#Notif_Config_Failed_Filename').text(Options.option('ConfigFile')); RPC.call('saveconfig', [serverSaveRequest], saveCompleted); } } function showSaveBanner() { $('#Config_Save').attr('disabled', 'disabled'); } function removeSaveBanner() { $('#Config_Save').removeAttr('disabled'); } function saveCompleted(result) { removeSaveBanner(); if (result) { $ConfigContent.fadeOut(function() { $('#ConfigSaved').fadeIn(); }); } else { Notification.show('#Notif_Config_Failed'); } configSaved = true; } this.canLeaveTab = function(target) { if (!config || prepareSaveRequest(true).length === 0 || configSaved) { return true; } leaveTarget = target; $LeaveConfigDialog.modal({backdrop: 'static'}); return false; } function userLeavesPage(e) { if (config && !configSaved && !UISettings.connectionError && prepareSaveRequest(true).length > 0) { return "Discard changes?"; } } this.discardChanges = function() { configSaved = true; $LeaveConfigDialog.modal('hide'); leaveTarget.click(); } this.scrollToOption = function(event, control) { event.preventDefault(); if ($(control).hasClass('option-name') && !$ConfigContent.hasClass('search')) { // Click on option title scrolls only from Search-page, not from regual pages return; } var optid = $(control).attr('data-optid'); if (!optid) { var optname = $(control).text(); var option = findOptionByName(optname); if (option) { optid = option.formId; } } if (optid) { scrollOptionIntoView(optid); } } this.showSpoiler = function(control) { $(control).hide(); $(control).next().show(); } function filterInput(value) { filterText = value; if (filterText.trim() !== '') { $('.ConfigSearch').show(); showSection('Search', true); } else { filterClear(); } } function filterClear() { filterText = ''; showSection(lastSection, true); $('.ConfigSearch').hide(); $ConfigTabBadge.hide(); $ConfigTabBadgeEmpty.show(); } function search() { $ConfigTabBadge.show(); $ConfigTabBadgeEmpty.hide(); $ConfigContent.addClass('search'); $ConfigData.children().hide(); var words = filterText.toLowerCase().split(' '); var total = 0; var available = 0; for (var k=0; k < config.length; k++) { var sections = config[k].sections; for (var i=0; i < sections.length; i++) { var section = sections[i]; if (!section.hidden) { for (var j=0; j < section.options.length; j++) { var option = section.options[j]; if (!option.template) { total++; if (filterOption(option, words)) { available++; var opt = $('#' + option.formId).closest('.control-group'); opt.show(); } } } } } } filterStaticPages(words); markLastControlGroup(); $ConfigTitle.text('SEARCH RESULTS'); $Body.animate({ scrollTop: 0 }, { duration: 0 }); updateTabInfo($ConfigTabBadge, { filter: true, available: available, total: total}); } function filterOption(option, words) { return filterWords(option.caption + ' ' + option.description + ' ' + (option.value === null ? '' : option.value), words); } function filterStaticPages(words) { $ConfigData.children().filter('.config-static').each(function(index, element) { var text = $(element).text(); Util.show(element, filterWords(text, words)); }); } function filterWords(text, words) { var search = text.toLowerCase(); for (var i = 0; i < words.length; i++) { if (search.indexOf(words[i]) === -1) { return false; } } return true; } function markLastControlGroup() { $ConfigData.children().removeClass('last-group'); $ConfigData.children().filter(':visible').last().addClass('last-group'); } /*** RELOAD ********************************************************************/ function restart(callback) { Refresher.pause(); $('#ConfigReloadInfoNotes').hide(); $('body').fadeOut(function() { $('#Navbar, #MainContent').hide(); $('#ConfigSaved').hide(); $('body').toggleClass('navfixed', false); $('body').show(); $('#ConfigReloadInfo').fadeIn(); reloadTime = new Date(); callback(); }); } this.reloadConfirm = function() { ConfirmDialog.showModal('ReloadConfirmDialog', Config.reload); } this.reload = function() { $('#ConfigReloadAction').text('Stopping all activities and reloading...'); restart(function() { RPC.call('reload', [], reloadCheckStatus); }); } function reloadCheckStatus() { RPC.call('status', [], function(status) { // OK, checking if it is a restarted instance if (status.UpTimeSec >= Status.status.UpTimeSec) { // the old instance is not restarted yet // waiting 0.5 sec. and retrying setTimeout(reloadCheckStatus, 500); reloadCheckNotes(); } else { // restarted successfully $('#ConfigReloadAction').text('Reloaded successfully. Refreshing the page...'); // refresh page document.location.reload(true); } }, function() { // Failure, waiting 0.5 sec. and retrying setTimeout(reloadCheckStatus, 500); reloadCheckNotes(); }); } function reloadCheckNotes() { // if reload takes more than 30 sec. show additional tips if (new Date() - reloadTime > 30000) { $('#ConfigReloadInfoNotes').show(1000); } } this.applyReloadedValues = function(values) { Options.reloadConfig(values, buildPage); restored = true; } /*** SHUTDOWN ********************************************************************/ this.shutdownConfirm = function() { ConfirmDialog.showModal('ShutdownConfirmDialog', Config.shutdown); } this.shutdown = function() { $('#ConfigReloadTitle').text('Shutdown NZBGet'); $('#ConfigReloadAction').text('Stopping all activities...'); restart(function() { RPC.call('shutdown', [], shutdownCheckStatus); }); } function shutdownCheckStatus() { RPC.call('version', [], function(version) { // the program still runs, waiting 0.5 sec. and retrying setTimeout(shutdownCheckStatus, 500); }, function() { // the program has been stopped $('#ConfigReloadTransmit').hide(); $('#ConfigReloadAction').text('The program has been stopped.'); }); } /*** UPDATE ********************************************************************/ this.checkUpdates = function() { UpdateDialog.showModal(); } }(jQuery)); /*** CHOOSE SCRIPT DIALOG *******************************************************/ var ScriptListDialog = (new function($) { 'use strict' // Controls var $ScriptListDialog; var $ScriptTable; var option; var config; var scriptList; var orderChanged; var orderMode; this.init = function() { $ScriptListDialog = $('#ScriptListDialog'); $('#ScriptListDialog_Save').click(save); $ScriptTable = $('#ScriptListDialog_ScriptTable'); $ScriptTable.fasttable( { pagerContainer: $('#ScriptListDialog_ScriptTable_pager'), headerCheck: $('#ScriptListDialog_ScriptTable > thead > tr:first-child'), infoEmpty: 'No scripts found. If you just changed option "ScriptDir", save settings and reload NZBGet.', pageSize: 1000 }); $ScriptTable.on('click', 'tbody div.check', function(event) { $ScriptTable.fasttable('itemCheckClick', this.parentNode.parentNode, event); }); $ScriptTable.on('click', 'thead div.check', function() { $ScriptTable.fasttable('titleCheckClick') }); $ScriptTable.on('mousedown', Util.disableShiftMouseDown); $ScriptListDialog.on('hidden', function() { // cleanup $ScriptTable.fasttable('update', []); }); } this.showModal = function(_option, _config) { option = _option; config = _config; orderChanged = false; orderMode = option.name === 'ScriptOrder'; if (orderMode) { $('#ScriptListDialog_Title').text('Reorder scripts'); $('#ScriptListDialog_Instruction').text('Hover mouse over table elements for reorder buttons to appear.'); } else { $('#ScriptListDialog_Title').text('Choose scripts'); $('#ScriptListDialog_Instruction').html('Select scripts for option ' + option.name + '.'); } $ScriptTable.toggleClass('table-hidecheck', orderMode); $ScriptTable.toggleClass('table-check table-cancheck', !orderMode); $('#ScriptListDialog_OrderInfo').toggleClass('alert alert-info', !orderMode); Util.show('#ScriptListDialog_OrderInfo', orderMode, 'inline-block'); buildScriptList(); var selectedList = parseCommaList(Config.getOptionValue(option)); updateTable(selectedList); $ScriptListDialog.modal({backdrop: 'static'}); } function updateTable(selectedList) { var reorderButtons = '
    '; var data = []; for (var i=0; i < scriptList.length; i++) { var scriptName = scriptList[i]; var scriptShortName = Options.shortScriptName(scriptName); var fields = ['
    ', '' + scriptShortName + '' + reorderButtons]; var item = { id: scriptName, fields: fields, search: '' }; data.push(item); if (!orderMode && selectedList && selectedList.indexOf(scriptName) > -1) { $ScriptTable.fasttable('checkRow', scriptName, true); } } $ScriptTable.fasttable('update', data); } function parseCommaList(commaList) { var valueList = commaList.split(/[,;]+/); for (var i=0; i < valueList.length; i++) { valueList[i] = valueList[i].trim(); if (valueList[i] === '') { valueList.splice(i, 1); i--; } } return valueList; } function buildScriptList() { var orderList = parseCommaList(Config.getOptionValue(Config.findOptionByName('ScriptOrder'))); var availableScripts = []; for (var i=1; i < config.length; i++) { availableScripts.push(config[i].scriptName); } availableScripts.sort(); scriptList = []; // first add all scripts from orderList for (var i=0; i < orderList.length; i++) { var scriptName = orderList[i]; if (availableScripts.indexOf(scriptName) > -1) { scriptList.push(scriptName); } } // second add all other scripts from script list for (var i=0; i < availableScripts.length; i++) { var scriptName = availableScripts[i]; if (scriptList.indexOf(scriptName) == -1) { scriptList.push(scriptName); } } return scriptList; } function save(e) { e.preventDefault(); if (!orderMode) { var selectedList = ''; var checkedRows = $ScriptTable.fasttable('checkedRows'); for (var i=0; i < scriptList.length; i++) { var scriptName = scriptList[i]; if (checkedRows.indexOf(scriptName) > -1) { selectedList += (selectedList == '' ? '' : ', ') + scriptName; } } var control = $('#' + option.formId); control.val(selectedList); } if (orderChanged) { var scriptOrderOption = Config.findOptionByName('ScriptOrder'); var control = $('#' + scriptOrderOption.formId); control.val(scriptList.join(', ')); } $ScriptListDialog.modal('hide'); } this.move = function(control, direction) { var index = parseInt($('span', $(control).closest('tr')).attr('data-index')); if ((index === 0 && (direction === 'up' || direction === 'top')) || (index === scriptList.length-1 && (direction === 'down' || direction === 'bottom'))) { return; } switch (direction) { case 'up': case 'down': { var newIndex = direction === 'up' ? index - 1 : index + 1; var tmp = scriptList[newIndex]; scriptList[newIndex] = scriptList[index]; scriptList[index] = tmp; break; } case 'top': case 'bottom': { var tmp = scriptList[index]; scriptList.splice(index, 1); if (direction === 'top') { scriptList.unshift(tmp); } else { scriptList.push(tmp); } break; } } if (!orderChanged && !orderMode) { $('#ScriptListDialog_OrderInfo').fadeIn(500); } orderChanged = true; updateTable(); } }(jQuery)); /*** BACKUP/RESTORE SETTINGS *******************************************************/ var ConfigBackupRestore = (new function($) { 'use strict' // State var settings; var filename; this.init = function(options) { $('#Config_RestoreInput')[0].addEventListener('change', restoreSelectHandler, false); } /*** BACKUP ********************************************************************/ this.backupSettings = function() { var settings = ''; for (var i=0; i < Config.config().values.length; i++) { var option = Config.config().values[i]; if (option.Value !== null) { settings += settings==='' ? '' : '\n'; settings += option.Name + '=' + option.Value; } } var pad = function(arg) { return (arg < 10 ? '0' : '') + arg } var dt = new Date(); var datestr = dt.getFullYear() + pad(dt.getMonth() + 1) + pad(dt.getDate()) + '-' + pad(dt.getHours()) + pad(dt.getMinutes()) + pad(dt.getSeconds()); var filename = 'nzbget-' + datestr + '.conf'; if (window.Blob) { var blob = new Blob([settings], {type: "text/plain;charset=utf-8"}); if (navigator.msSaveBlob) { navigator.msSaveBlob(blob, filename); } else { var URL = window.URL || window.webkitURL || window; var object_url = URL.createObjectURL(blob); var save_link = document.createElement('a'); save_link.href = object_url; save_link.download = filename; var event = document.createEvent('MouseEvents'); event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); save_link.dispatchEvent(event); } } else { alert('Unfortunately your browser doesn\'t support access to local file system.\n\n'+ 'To backup settings you can manually save file "nzbget.conf" (' + Options.option('ConfigFile')+ ').'); } } /*** RESTORE ********************************************************************/ this.restoreSettings = function() { if (!window.FileReader) { alert("Unfortunately your browser doesn't support FileReader API."); return; } var testreader = new FileReader(); if (!testreader.readAsBinaryString && !testreader.readAsDataURL) { alert("Unfortunately your browser doesn't support neither \"readAsBinaryString\" nor \"readAsDataURL\" functions of FileReader API."); return; } var inp = $('#Config_RestoreInput'); // Reset file input control (needed for IE10) inp.wrap('
    ').closest('form').get(0).reset(); inp.unwrap(); inp.click(); } function restoreSelectHandler(event) { if (!event.target.files) { alert("Unfortunately your browser doesn't support direct access to local files."); return; } if (event.target.files.length > 0) { restoreFromFile(event.target.files[0]); } } function restoreFromFile(file) { var reader = new FileReader(); reader.onload = function (event) { if (reader.readAsBinaryString) { settings = event.target.result; } else { var base64str = event.target.result.replace(/^data:[^,]+,/, ''); settings = atob(base64str); } filename = file.name; if (settings.indexOf('MainDir=') < 0) { alert('File ' + filename + ' is not a valid NZBGet backup.'); return; } RestoreSettingsDialog.showModal(Config.config(), restoreExecute); }; if (reader.readAsBinaryString) { reader.readAsBinaryString(file); } else { reader.readAsDataURL(file); } } function restoreExecute(selectedSections) { $('#Notif_Config_Restoring').show(); setTimeout(function() { var values = restoreValues(selectedSections); Config.applyReloadedValues(values); $('#Notif_Config_Restoring').hide(); $('#SettingsRestoredDialog').modal({backdrop: 'static'}); }, 50); } function restoreValues(selectedSections) { var config = Config.config(); var values = config.values; settings = settings.split('\n'); for (var i=0; i < settings.length; i++) { var optstr = settings[i]; var ind = optstr.indexOf('='); var option = { Name: optstr.substr(0, ind).trim(), Value: optstr.substr(ind+1, 100000).trim() }; settings[i] = option; } function removeValue(name) { var name = name.toLowerCase(); for (var i=0; i < values.length; i++) { if (values[i].Name.toLowerCase() === name) { values.splice(i, 1); return true; } } return false; } function addValue(name) { var name = name.toLowerCase(); for (var i=0; i < settings.length; i++) { if (settings[i].Name.toLowerCase() === name) { values.push(settings[i]); return true; } } return false; } function restoreOption(option) { if (!option.template && !option.multiid) { removeValue(option.name); addValue(option.name); } else if (option.template) { // delete all multi-options for (var j=1; ; j++) { var optname = option.name.replace('1', j); if (!removeValue(optname)) { break; } } // add all multi-options for (var j=1; ; j++) { var optname = option.name.replace('1', j); if (!addValue(optname)) { break; } } } } for (var k=0; k < config.length; k++) { var conf = config[k]; for (var i=0; i < conf.sections.length; i++) { var section = conf.sections[i]; if (!section.hidden && selectedSections.indexOf(section.id) > -1) { for (var m=0; m < section.options.length; m++) { restoreOption(section.options[m]); } } } } return values; } }(jQuery)); /*** RESTORE SETTINGS DIALOG *******************************************************/ var RestoreSettingsDialog = (new function($) { 'use strict' // Controls var $RestoreSettingsDialog; var $SectionTable; // State var config; var restoreClick; this.init = function() { $RestoreSettingsDialog = $('#RestoreSettingsDialog'); $('#RestoreSettingsDialog_Restore').click(restore); $SectionTable = $('#RestoreSettingsDialog_SectionTable'); $SectionTable.fasttable( { pagerContainer: $('#RestoreSettingsDialog_SectionTable_pager'), headerCheck: $('#RestoreSettingsDialog_SectionTable > thead > tr:first-child'), infoEmpty: 'No sections found.', pageSize: 1000 }); $SectionTable.on('click', 'tbody div.check', function(event) { $SectionTable.fasttable('itemCheckClick', this.parentNode.parentNode, event); }); $SectionTable.on('click', 'thead div.check', function() { $SectionTable.fasttable('titleCheckClick') }); $SectionTable.on('mousedown', Util.disableShiftMouseDown); $RestoreSettingsDialog.on('hidden', function() { // cleanup $SectionTable.fasttable('update', []); }); } this.showModal = function(_config, _restoreClick) { config = _config; restoreClick = _restoreClick; updateTable(); $RestoreSettingsDialog.modal({backdrop: 'static'}); } function updateTable() { var data = []; for (var k=0; k < config.length; k++) { var conf = config[k]; for (var i=0; i < conf.sections.length; i++) { var section = conf.sections[i]; if (!section.hidden) { var fields = ['
    ', '' + section.name + '']; var item = { id: section.id, fields: fields, search: '' }; data.push(item); } } } $SectionTable.fasttable('update', data); } function restore(e) { e.preventDefault(); var selectedSections = []; var checkedRows = $SectionTable.fasttable('checkedRows'); if (checkedRows.length === 0) { Notification.show('#Notif_Config_RestoreSections'); return; } checkedRows = $.extend([], checkedRows); // clone $RestoreSettingsDialog.modal('hide'); setTimeout(function() { restoreClick(checkedRows); }, 0); } }(jQuery)); /*** UPDATE DIALOG *******************************************************/ var UpdateDialog = (new function($) { 'use strict' // Controls var $UpdateDialog; var $UpdateProgressDialog; var $UpdateProgressDialog_Log; // State var VersionInfo; var PackageInfo; var UpdateInfo; var lastUpTimeSec; var installing = false; this.init = function() { $UpdateDialog = $('#UpdateDialog'); $('#UpdateDialog_InstallStable,#UpdateDialog_InstallTesting,#UpdateDialog_InstallDevel').click(install); $UpdateProgressDialog = $('#UpdateProgressDialog'); $UpdateProgressDialog_Log = $('#UpdateProgressDialog_Log'); $UpdateDialog.on('hidden', resumeRefresher); $UpdateProgressDialog.on('hidden', resumeRefresher); } function resumeRefresher() { if (!installing) { Refresher.resume(); } } this.showModal = function() { $('#UpdateDialog_Install').hide(); $('#UpdateDialog_CheckProgress').show(); $('#UpdateDialog_CheckFailed').hide(); $('#UpdateDialog_Versions').hide(); $('#UpdateDialog_UpdateAvail').hide(); $('#UpdateDialog_UpdateNotAvail').hide(); $('#UpdateDialog_UpdateNoInfo').hide(); $('#UpdateDialog_InstalledInfo').show(); $('#UpdateDialog_VerInstalled').text(Options.option('Version')); PackageInfo = {}; VersionInfo = {}; UpdateInfo = {}; installing = false; Refresher.pause(); $UpdateDialog.modal({backdrop: 'static'}); RPC.call('readurl', ['http://nzbget.sourceforge.net/info/nzbget-version.php?nocache=' + new Date().getTime(), 'version info'], loadedUpstreamInfo, error); } function error(e) { $('#UpdateDialog_CheckProgress').hide(); $('#UpdateDialog_CheckFailed').show(); } function parseJsonP(jsonp) { var p = jsonp.indexOf('{'); var obj = JSON.parse(jsonp.substr(p, 10000)); return obj; } function loadedUpstreamInfo(data) { VersionInfo = parseJsonP(data); if (VersionInfo['devel-version']) { loadPackageInfo(); } else { loadSvnVerData(); } } function loadSvnVerData() { // fetching devel version number from svn viewer RPC.call('readurl', ['http://svn.code.sf.net/p/nzbget/code/trunk/', 'svn revision info'], function(svnRevData) { RPC.call('readurl', ['http://svn.code.sf.net/p/nzbget/code/trunk/configure.ac', 'svn branch info'], function(svnBranchData) { var rev = svnRevData.match(/.*Revision (\d+).*/); if (rev.length > 1) { var ver = svnBranchData.match(/.*AM_INIT_AUTOMAKE\(nzbget, (.*)\).*/); if (ver.length > 1) { VersionInfo['devel-version'] = ver[1] + '-r' + rev[1]; } } loadPackageInfo(); }, error); }, error); } function loadPackageInfo() { $.get('package-info.json', loadedPackageInfo, 'html').fail(loadedAll); } function loadedPackageInfo(data) { PackageInfo = parseJsonP(data); if (PackageInfo['update-info-link']) { RPC.call('readurl', [PackageInfo['update-info-link'], 'update info'], loadedUpdateInfo, loadedAll); } else if (PackageInfo['update-info-script']) { RPC.call('checkupdates', [], loadedUpdateInfo, loadedAll); } else { loadedAll(); } } function loadedUpdateInfo(data) { UpdateInfo = parseJsonP(data); loadedAll(); } function formatTesting(str) { return str.replace('-testing-', '-'); } function revision(version) { var rev = version.match(/.*r(\d+)/); return rev && rev.length > 1 ? parseInt(rev[1]) : 0; } function vernumber(version) { var ver = version.match(/([\d.]+).*/); return ver && ver.length > 1 ? parseFloat(ver[1]) : 0; } function loadedAll() { var installedVersion = Options.option('Version'); $('#UpdateDialog_CheckProgress').hide(); $('#UpdateDialog_Versions').show(); $('#UpdateDialog_InstalledInfo').show(); $('#UpdateDialog_CurStable').text(VersionInfo['stable-version'] ? VersionInfo['stable-version'] : 'no data'); $('#UpdateDialog_CurTesting').text(VersionInfo['testing-version'] ? formatTesting(VersionInfo['testing-version']) : 'no data'); $('#UpdateDialog_CurDevel').text(VersionInfo['devel-version'] ? formatTesting(VersionInfo['devel-version']) : 'no data'); $('#UpdateDialog_CurNotesStable').attr('href', VersionInfo['stable-release-notes']); $('#UpdateDialog_CurNotesTesting').attr('href', VersionInfo['testing-release-notes']); $('#UpdateDialog_CurNotesDevel').attr('href', VersionInfo['devel-release-notes']); Util.show('#UpdateDialog_CurNotesStable', VersionInfo['stable-release-notes']); Util.show('#UpdateDialog_CurNotesTesting', VersionInfo['testing-release-notes']); Util.show('#UpdateDialog_CurNotesDevel', VersionInfo['devel-release-notes']); $('#UpdateDialog_AvailStable').text(UpdateInfo['stable-version'] ? UpdateInfo['stable-version'] : 'not available'); $('#UpdateDialog_AvailTesting').text(UpdateInfo['testing-version'] ? formatTesting(UpdateInfo['testing-version']) : 'not available'); $('#UpdateDialog_AvailDevel').text(UpdateInfo['devel-version'] ? formatTesting(UpdateInfo['devel-version']) : 'not available'); $('#UpdateDialog_AvailNotesStable').attr('href', UpdateInfo['stable-package-info']); $('#UpdateDialog_AvailNotesTesting').attr('href', UpdateInfo['testing-package-info']); $('#UpdateDialog_AvailNotesDevel').attr('href', UpdateInfo['devel-package-info']); Util.show('#UpdateDialog_AvailNotesStableBlock', UpdateInfo['stable-package-info']); Util.show('#UpdateDialog_AvailNotesTestingBlock', UpdateInfo['testing-package-info']); Util.show('#UpdateDialog_AvailNotesDevelBlock', UpdateInfo['devel-package-info']); var installedRev = revision(installedVersion); var installedVer = vernumber(installedVersion); var installedStable = installedRev === 0 && installedVersion.indexOf('testing') === -1; var canInstallStable = UpdateInfo['stable-version'] && ((installedStable && installedVer < vernumber(UpdateInfo['stable-version'])) || (!installedStable && installedVer <= vernumber(UpdateInfo['stable-version']))); var canInstallTesting = UpdateInfo['testing-version'] && ((installedStable && installedVer < vernumber(UpdateInfo['testing-version'])) || (!installedStable && (installedRev === 0 || installedRev < revision(UpdateInfo['testing-version'])))); var canInstallDevel = UpdateInfo['devel-version'] && ((installedStable && installedVer < vernumber(UpdateInfo['devel-version'])) || (!installedStable && (installedRev === 0 || installedRev < revision(UpdateInfo['devel-version'])))); Util.show('#UpdateDialog_InstallStable', canInstallStable); Util.show('#UpdateDialog_InstallTesting', canInstallTesting); Util.show('#UpdateDialog_InstallDevel', canInstallDevel); var hasUpdateSource = PackageInfo['update-info-link'] || PackageInfo['update-info-script']; var hasUpdateInfo = UpdateInfo['stable-version'] || UpdateInfo['testing-version'] || UpdateInfo['devel-version']; var canUpdate = canInstallStable || canInstallTesting || canInstallDevel; Util.show('#UpdateDialog_UpdateAvail', canUpdate); Util.show('#UpdateDialog_UpdateNotAvail', hasUpdateInfo && !canUpdate); Util.show('#UpdateDialog_UpdateNoInfo', !hasUpdateSource); Util.show('#UpdateDialog_CheckFailed', hasUpdateSource && !hasUpdateInfo); $('#UpdateDialog_AvailRow').toggleClass('hide', !hasUpdateInfo); } function install(e) { e.preventDefault(); var kind = $(this).attr('data-kind'); var script = PackageInfo['install-script']; var info = PackageInfo['install-' + kind + '-info']; if (!script) { alert('Something is wrong with a package configuration file "package-info.json".'); return; } RPC.call('status', [], function(status) { lastUpTimeSec = status.UpTimeSec; RPC.call('startupdate', [kind], updateStarted); }); } function updateStarted(started) { if (!started) { Notification.show('#Notif_StartUpdate_Failed'); return; } installing = true; $UpdateDialog.fadeOut(250, function() { $UpdateProgressDialog_Log.text(''); $UpdateProgressDialog.fadeIn(250, function() { $UpdateDialog.modal('hide'); $UpdateProgressDialog.modal({backdrop: 'static'}); updateLog(); }); }); } function updateLog() { RPC.call('logupdate', [0, 100], function(data) { updateLogTable(data); setTimeout(updateLog, 500); }, function() { // rpc-failure: the program has been terminated. Waiting for new instance. setLogContentAndScroll($UpdateProgressDialog_Log.html() + '\n' + 'NZBGet has been terminated. Waiting for restart...'); setTimeout(checkStatus, 500); }, 1000); } function setLogContentAndScroll(html) { var scroll = $UpdateProgressDialog_Log.prop('scrollHeight') - $UpdateProgressDialog_Log.prop('scrollTop') === $UpdateProgressDialog_Log.prop('clientHeight'); $UpdateProgressDialog_Log.html(html); if (scroll) { $UpdateProgressDialog_Log.scrollTop($UpdateProgressDialog_Log.prop('scrollHeight')); } } function updateLogTable(messages) { var html = ''; for (var i=0; i < messages.length; i++) { var message = messages[i]; var text = Util.textToHtml(message.Text); if (message.Kind === 'ERROR') { text = '' + text + ''; } html = html + text + '\n'; } setLogContentAndScroll(html); } function checkStatus() { RPC.call('status', [], function(status) { // OK, checking if it is a restarted instance if (status.UpTimeSec >= lastUpTimeSec) { // the old instance is not restarted yet // waiting 0.5 sec. and retrying setTimeout(checkStatus, 500); } else { // restarted successfully, refresh page setLogContentAndScroll($UpdateProgressDialog_Log.html() + '\n' + 'Successfully started. Refreshing the page...'); setTimeout(function() { document.location.reload(true); }, 1000); } }, function() { // Failure, waiting 0.5 sec. and retrying setTimeout(checkStatus, 500); }, 1000); } }(jQuery)); nzbget-12.0+dfsg/webui/downloads.js000066400000000000000000000555341226450633000173240ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2012-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 900 $ * $Date: 2013-11-04 21:59:20 +0100 (Mon, 04 Nov 2013) $ * */ /* * In this module: * 1) Download tab; * 2) Functions for html generation for downloads, also used from other modules (edit and add dialogs). */ /*** DOWNLOADS TAB ***********************************************************/ var Downloads = (new function($) { 'use strict'; // Controls var $DownloadsTable; var $DownloadsTabBadge; var $DownloadsTabBadgeEmpty; var $DownloadQueueEmpty; var $DownloadsRecordsPerPage; var $DownloadsTable_Name; // State var notification = null; var updateTabInfo; var groups; var urls; var nameColumnWidth = null; this.init = function(options) { updateTabInfo = options.updateTabInfo; $DownloadsTable = $('#DownloadsTable'); $DownloadsTabBadge = $('#DownloadsTabBadge'); $DownloadsTabBadgeEmpty = $('#DownloadsTabBadgeEmpty'); $DownloadQueueEmpty = $('#DownloadQueueEmpty'); $DownloadsRecordsPerPage = $('#DownloadsRecordsPerPage'); $DownloadsTable_Name = $('#DownloadsTable_Name'); var recordsPerPage = UISettings.read('$DownloadsRecordsPerPage', 10); $DownloadsRecordsPerPage.val(recordsPerPage); $DownloadsTable.fasttable( { filterInput: $('#DownloadsTable_filter'), filterClearButton: $("#DownloadsTable_clearfilter"), pagerContainer: $('#DownloadsTable_pager'), infoContainer: $('#DownloadsTable_info'), headerCheck: $('#DownloadsTable > thead > tr:first-child'), filterCaseSensitive: false, infoEmpty: ' ', // this is to disable default message "No records" pageSize: recordsPerPage, maxPages: UISettings.miniTheme ? 1 : 5, pageDots: !UISettings.miniTheme, fillFieldsCallback: fillFieldsCallback, renderCellCallback: renderCellCallback, updateInfoCallback: updateInfo }); $DownloadsTable.on('click', 'a', itemClick); $DownloadsTable.on('click', 'tbody div.check', function(event) { $DownloadsTable.fasttable('itemCheckClick', this.parentNode.parentNode, event); }); $DownloadsTable.on('click', 'thead div.check', function() { $DownloadsTable.fasttable('titleCheckClick') }); $DownloadsTable.on('mousedown', Util.disableShiftMouseDown); } this.applyTheme = function() { $DownloadsTable.fasttable('setPageSize', UISettings.read('$DownloadsRecordsPerPage', 10), UISettings.miniTheme ? 1 : 5, !UISettings.miniTheme); } this.update = function() { if (!groups) { $('#DownloadsTable_Category').css('width', DownloadsUI.calcCategoryColumnWidth()); } RPC.call('listgroups', [], groups_loaded); } function groups_loaded(_groups) { groups = _groups; RPC.call('postqueue', [100], posts_loaded); } function posts_loaded(posts) { mergequeues(posts); prepare(); RPC.call('urlqueue', [], urls_loaded); } function urls_loaded(_urls) { urls = _urls; RPC.next(); } function mergequeues(posts) { var lastPPItemIndex = -1; for (var i=0, il=posts.length; i < il; i++) { var post = posts[i]; var found = false; for (var j=0, jl=groups.length; j < jl; j++) { var group = groups[j]; if (group.NZBID === post.NZBID) { found = true; if (!group.post) { group.post = post; } lastPPItemIndex = j; break; } } if (!found) { // create a virtual group-item: // post-item has most of fields the group-item has, // we use post-item as basis and then add missing fields. group = $.extend({}, post); group.post = post; group.MaxPriority = 0; group.LastID = 0; group.MinPostTime = 0; group.RemainingSizeMB = 0; group.RemainingSizeLo = 0; group.PausedSizeMB = 0; group.PausedSizeLo = 0; group.RemainingFileCount = 0; group.RemainingParCount = 0; // insert it after the last pp-item if (lastPPItemIndex > -1) { groups.splice(lastPPItemIndex + 1, 0, group); } else { groups.unshift(group); } } } } function prepare() { for (var j=0, jl=groups.length; j < jl; j++) { detectStatus(groups[j]); } } this.redraw = function() { redraw_table(); Util.show($DownloadsTabBadge, groups.length > 0); Util.show($DownloadsTabBadgeEmpty, groups.length === 0 && UISettings.miniTheme); Util.show($DownloadQueueEmpty, groups.length === 0); } this.resize = function() { calcProgressLabels(); } /*** TABLE *************************************************************************/ function redraw_table() { var data = []; for (var i=0; i < groups.length; i++) { var group = groups[i]; var nametext = group.NZBName; var priority = DownloadsUI.buildPriorityText(group.MaxPriority); var estimated = DownloadsUI.buildEstimated(group); var age = Util.formatAge(group.MinPostTime + UISettings.timeZoneCorrection*60*60); var size = Util.formatSizeMB(group.FileSizeMB, group.FileSizeLo); var remaining = Util.formatSizeMB(group.RemainingSizeMB-group.PausedSizeMB, group.RemainingSizeLo-group.PausedSizeLo); var dupe = DownloadsUI.buildDupeText(group.DupeKey, group.DupeScore, group.DupeMode); var item = { id: group.NZBID, group: group, data: { age: age, estimated: estimated, size: size, remaining: remaining }, search: group.status + ' ' + nametext + ' ' + priority + ' ' + dupe + ' ' + group.Category + ' ' + age + ' ' + size + ' ' + remaining + ' ' + estimated }; data.push(item); } $DownloadsTable.fasttable('update', data); } function fillFieldsCallback(item) { var group = item.group; var status = DownloadsUI.buildStatus(group); var priority = DownloadsUI.buildPriority(group.MaxPriority); var progresslabel = DownloadsUI.buildProgressLabel(group, nameColumnWidth); var progress = DownloadsUI.buildProgress(group, item.data.size, item.data.remaining, item.data.estimated); var dupe = DownloadsUI.buildDupe(group.DupeKey, group.DupeScore, group.DupeMode); var name = '' + Util.textToHtml(Util.formatNZBName(group.NZBName)) + ''; var health = ''; if (group.Health < 1000 && (!group.postprocess || (group.status === 'pp-queued' && group.post.TotalTimeSec === 0))) { health = ' health: ' + Math.floor(group.Health / 10) + '% '; } var category = Util.textToHtml(group.Category); if (!UISettings.miniTheme) { var info = name + ' ' + priority + dupe + health + progresslabel; item.fields = ['
    ', status, info, category, item.data.age, progress, item.data.estimated]; } else { var info = '
    ' + name + '' + ' ' + (group.status === 'queued' ? '' : status) + ' ' + priority + dupe + health; if (category) { info += ' ' + category + ''; } if (progresslabel) { progress = '
    ' + progresslabel + '
    ' + progress; } item.fields = [info, progress]; } } function renderCellCallback(cell, index, item) { if (4 <= index && index <= 7) { cell.className = 'text-right'; } } function detectStatus(group) { group.paused = (group.PausedSizeLo != 0) && (group.RemainingSizeLo == group.PausedSizeLo); group.postprocess = group.post !== undefined; if (group.postprocess) { switch (group.post.Stage) { case 'QUEUED': group.status = 'pp-queued'; break; case 'LOADING_PARS': group.status = 'checking'; break; case 'VERIFYING_SOURCES': group.status = 'checking'; break; case 'REPAIRING': group.status = 'repairing'; break; case 'VERIFYING_REPAIRED': group.status = 'verifying'; break; case 'RENAMING': group.status = 'renaming'; break; case 'MOVING': group.status = 'moving'; break; case 'UNPACKING': group.status = 'unpacking'; break; case 'EXECUTING_SCRIPT': group.status = 'processing'; break; case 'FINISHED': group.status = 'finished'; break; default: group.status = 'error: ' + group.post.Stage; break; } } else if (group.ActiveDownloads > 0) { group.status = 'downloading'; } else if (group.paused) { group.status = 'paused'; } else { group.status = 'queued'; } } this.recordsPerPageChange = function() { var val = $DownloadsRecordsPerPage.val(); UISettings.write('$DownloadsRecordsPerPage', val); $DownloadsTable.fasttable('setPageSize', val); } function updateInfo(stat) { updateTabInfo($DownloadsTabBadge, stat); } function calcProgressLabels() { var progressLabels = $('.label-inline', $DownloadsTable); if (UISettings.miniTheme) { nameColumnWidth = null; progressLabels.css('max-width', ''); return; } progressLabels.hide(); nameColumnWidth = Math.max($DownloadsTable_Name.width(), 50) - 4*2; // 4 - padding of span progressLabels.css('max-width', nameColumnWidth); progressLabels.show(); } /*** EDIT ******************************************************/ function itemClick(e) { e.preventDefault(); var nzbid = $(this).attr('nzbid'); $(this).blur(); DownloadsEditDialog.showModal(nzbid, groups); } function editCompleted() { Refresher.update(); if (notification) { Notification.show(notification); notification = null; } } /*** CHECKMARKS ******************************************************/ function checkBuildEditIDList(UseLastID) { var checkedRows = $DownloadsTable.fasttable('checkedRows'); var hasIDs = false; var checkedEditIDs = []; for (var i = 0; i < groups.length; i++) { var group = groups[i]; if (checkedRows.indexOf(group.NZBID) > -1) { if (group.postprocess) { Notification.show('#Notif_Downloads_CheckPostProcess'); return null; } checkedEditIDs.push(UseLastID ? group.LastID : group.NZBID); } } if (checkedEditIDs.length === 0) { Notification.show('#Notif_Downloads_Select'); return null; } return checkedEditIDs; } /*** TOOLBAR: SELECTED ITEMS ******************************************************/ this.editClick = function() { var checkedEditIDs = checkBuildEditIDList(false); if (!checkedEditIDs) { return; } if (checkedEditIDs.length == 1) { DownloadsEditDialog.showModal(checkedEditIDs[0], groups); } else { DownloadsMultiDialog.showModal(checkedEditIDs, groups); } } this.mergeClick = function() { var checkedEditIDs = checkBuildEditIDList(false); if (!checkedEditIDs) { return; } if (checkedEditIDs.length < 2) { Notification.show('#Notif_Downloads_SelectMulti'); return; } DownloadsMergeDialog.showModal(checkedEditIDs, groups); } this.pauseClick = function() { var checkedEditIDs = checkBuildEditIDList(true); if (!checkedEditIDs) { return; } notification = '#Notif_Downloads_Paused'; RPC.call('editqueue', ['GroupPause', 0, '', checkedEditIDs], editCompleted); } this.resumeClick = function() { var checkedEditIDs = checkBuildEditIDList(true); if (!checkedEditIDs) { return; } notification = '#Notif_Downloads_Resumed'; RPC.call('editqueue', ['GroupResume', 0, '', checkedEditIDs], function() { if (Options.option('ParCheck') === 'force') { editCompleted(); } else { RPC.call('editqueue', ['GroupPauseExtraPars', 0, '', checkedEditIDs], editCompleted); } }); } this.deleteClick = function() { var checkedRows = $DownloadsTable.fasttable('checkedRows'); var downloadIDs = []; var postprocessIDs = []; for (var i = 0; i < groups.length; i++) { var group = groups[i]; if (checkedRows.indexOf(group.NZBID) > -1) { if (group.postprocess) { postprocessIDs.push(group.post.ID); } if (group.LastID > 0) { downloadIDs.push(group.LastID); } } } if (downloadIDs.length === 0 && postprocessIDs.length === 0) { Notification.show('#Notif_Downloads_Select'); return; } notification = '#Notif_Downloads_Deleted'; var deletePosts = function() { if (postprocessIDs.length > 0) { RPC.call('editqueue', ['PostDelete', 0, '', postprocessIDs], editCompleted); } else { editCompleted(); } }; var deleteGroups = function(command) { if (downloadIDs.length > 0) { RPC.call('editqueue', [command, 0, '', downloadIDs], deletePosts); } else { deletePosts(); } }; DownloadsUI.deleteConfirm(deleteGroups, true); } this.moveClick = function(action) { var checkedEditIDs = checkBuildEditIDList(true); if (!checkedEditIDs) { return; } var EditAction = ''; var EditOffset = 0; switch (action) { case 'top': EditAction = 'GroupMoveTop'; checkedEditIDs.reverse(); break; case 'bottom': EditAction = 'GroupMoveBottom'; break; case 'up': EditAction = 'GroupMoveOffset'; EditOffset = -1; break; case 'down': EditAction = 'GroupMoveOffset'; EditOffset = 1; checkedEditIDs.reverse(); break; } notification = ''; RPC.call('editqueue', [EditAction, EditOffset, '', checkedEditIDs], editCompleted); } }(jQuery)); /*** FUNCTIONS FOR HTML GENERATION (also used from other modules) *****************************/ var DownloadsUI = (new function($) { 'use strict'; // State var categoryColumnWidth = null; var dupeCheck = null; this.fillPriorityCombo = function(combo) { combo.empty(); combo.append(''); combo.append(''); combo.append(''); combo.append(''); combo.append(''); } this.fillCategoryCombo = function(combo) { combo.empty(); combo.append(''); for (var i=0; i < Options.categories.length; i++) { combo.append($('').text(Options.categories[i])); } } this.buildStatus = function(group) { if (group.postprocess && group.status !== 'pp-queued') { if (Status.status.PostPaused) { return '' + group.status + ''; } else { return '' + group.status + ''; } } switch (group.status) { case 'pp-queued': return 'pp-queued'; case 'downloading': return 'downloading'; case 'paused': return 'paused'; case 'queued': return 'queued'; default: return 'internal error(' + group.status + ')'; } } this.buildProgress = function(group, totalsize, remaining, estimated) { if (group.status === 'downloading' || (group.postprocess && !Status.status.PostPaused)) { var kind = 'progress-success'; } else if (group.status === 'paused' || (group.postprocess && Status.status.PostPaused)) { var kind = 'progress-warning'; } else { var kind = 'progress-none'; } var totalMB = group.FileSizeMB-group.PausedSizeMB; var remainingMB = group.RemainingSizeMB-group.PausedSizeMB; var percent = Math.round((totalMB - remainingMB) / totalMB * 100); var progress = ''; if (group.postprocess) { totalsize = ''; remaining = ''; percent = Math.round(group.post.StageProgress / 10); } if (!UISettings.miniTheme) { progress = '
    '+ '
    '+ '
    '+ '
    '+ '
    ' + totalsize + '
    '+ '
    ' + remaining + '
    '+ '
    '; } else { progress = '
    '+ '
    '+ '
    '+ '
    '+ '
    ' + (totalsize !== '' ? 'total ' : '') + totalsize + '
    '+ '
    ' + (estimated !== '' ? '[' + estimated + ']': '') + '
    '+ '
    ' + remaining + (remaining !== '' ? ' left' : '') + '
    '+ '
    '; } return progress; } this.buildEstimated = function(group) { if (group.postprocess) { if (group.post.StageProgress > 0) { return Util.formatTimeLeft(group.post.StageTimeSec / group.post.StageProgress * (1000 - group.post.StageProgress)); } } else if (!group.paused && Status.status.DownloadRate > 0) { return Util.formatTimeLeft((group.RemainingSizeMB-group.PausedSizeMB)*1024/(Status.status.DownloadRate/1024)); } return ''; } this.buildProgressLabel = function(group, maxWidth) { var text = ''; if (group.postprocess && !Status.status.PostPaused) { switch (group.post.Stage) { case "REPAIRING": break; case "LOADING_PARS": case "VERIFYING_SOURCES": case "VERIFYING_REPAIRED": case "UNPACKING": case "RENAMING": text = group.post.ProgressLabel; break; case "EXECUTING_SCRIPT": if (group.post.Log && group.post.Log.length > 0) { text = group.post.Log[group.post.Log.length-1].Text; // remove "for " from label text text = text.replace(' for ' + group.NZBName, ' '); } else { text = group.post.ProgressLabel; } break; } } return text !== '' ? ' ' + text + '' : ''; } this.buildPriorityText = function(priority) { switch (priority) { case 0: return ''; case 100: return 'very high priority'; case 50: return 'high priority'; case -50: return 'low priority'; case -100: return 'very low priority'; default: return 'priority: ' + priority; } } this.buildPriority = function(priority) { switch (priority) { case 0: return ''; case 100: return ' very high priority'; case 50: return ' high priority'; case -50: return ' low priority'; case -100: return ' very low priority'; } if (priority > 0) { return ' priority: ' + priority + ''; } else if (priority < 0) { return ' priority: ' + priority + ''; } } function formatDupeText(dupeKey, dupeScore, dupeMode) { dupeKey = dupeKey.replace('rageid=', ''); dupeKey = dupeKey.replace('imdb=', ''); dupeKey = dupeKey.replace('series=', ''); dupeKey = dupeKey.replace('nzb=', '#'); dupeKey = dupeKey.replace('=', ' '); dupeKey = dupeKey === '' ? 'title' : dupeKey; return dupeKey; } this.buildDupeText = function(dupeKey, dupeScore, dupeMode) { if (dupeCheck == null) { dupeCheck = Options.option('DupeCheck') === 'yes'; } if (dupeCheck && dupeKey != '' && UISettings.dupeBadges) { return formatDupeText(dupeKey, dupeScore, dupeMode); } else { return ''; } } this.buildDupe = function(dupeKey, dupeScore, dupeMode) { if (dupeCheck == null) { dupeCheck = Options.option('DupeCheck') === 'yes'; } if (dupeCheck && dupeKey != '' && UISettings.dupeBadges) { return ' ' + formatDupeText(dupeKey, dupeScore, dupeMode) + ' '; } else { return ''; } } this.resetCategoryColumnWidth = function() { categoryColumnWidth = null; } this.calcCategoryColumnWidth = function() { if (categoryColumnWidth === null) { var widthHelper = $('
    ').css({'position': 'absolute', 'float': 'left', 'white-space': 'nowrap', 'visibility': 'hidden'}).appendTo($('body')); // default (min) width categoryColumnWidth = 60; for (var i = 1; ; i++) { var opt = Options.option('Category' + i + '.Name'); if (!opt) { break; } widthHelper.text(opt); var catWidth = widthHelper.width(); categoryColumnWidth = Math.max(categoryColumnWidth, catWidth); } widthHelper.remove(); categoryColumnWidth += 'px'; } return categoryColumnWidth; } this.deleteConfirm = function(actionCallback, multi) { var dupeCheck = Options.option('DupeCheck') === 'yes'; var cleanupDisk = Options.option('DeleteCleanupDisk') === 'yes'; var history = Options.option('KeepHistory') !== '0'; var dialog = null; function init(_dialog) { dialog = _dialog; if (!multi) { var html = $('#ConfirmDialog_Text').html(); html = html.replace(/downloads/g, 'download'); $('#ConfirmDialog_Text').html(html); } $('#DownloadsDeleteConfirmDialog_Delete', dialog).prop('checked', true); $('#DownloadsDeleteConfirmDialog_Delete', dialog).prop('checked', true); $('#DownloadsDeleteConfirmDialog_DeleteDupe', dialog).prop('checked', false); $('#DownloadsDeleteConfirmDialog_DeleteFinal', dialog).prop('checked', false); Util.show($('#DownloadsDeleteConfirmDialog_Options', dialog), history); Util.show($('#DownloadsDeleteConfirmDialog_Simple', dialog), !history); Util.show($('#DownloadsDeleteConfirmDialog_DeleteDupe,#DownloadsDeleteConfirmDialog_DeleteDupeLabel', dialog), dupeCheck); Util.show($('#DownloadsDeleteConfirmDialog_Remain', dialog), !cleanupDisk); Util.show($('#DownloadsDeleteConfirmDialog_Cleanup', dialog), cleanupDisk); Util.show('#ConfirmDialog_Help', history && dupeCheck); }; function action() { var deleteNormal = $('#DownloadsDeleteConfirmDialog_Delete', dialog).is(':checked'); var deleteDupe = $('#DownloadsDeleteConfirmDialog_DeleteDupe', dialog).is(':checked'); var deleteFinal = $('#DownloadsDeleteConfirmDialog_DeleteFinal', dialog).is(':checked'); var command = deleteNormal ? 'GroupDelete' : (deleteDupe ? 'GroupDupeDelete' : 'GroupFinalDelete'); actionCallback(command); } ConfirmDialog.showModal('DownloadsDeleteConfirmDialog', action, init); } }(jQuery)); nzbget-12.0+dfsg/webui/edit.js000066400000000000000000001346471226450633000162620ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2012-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 925 $ * $Date: 2013-12-24 19:38:10 +0100 (Tue, 24 Dec 2013) $ * */ /* * In this module: * 1) Download edit dialog; * 2) Download multi edit dialog (edit multiple items); * 3) Download merge dialog; * 4) Download split dialog; * 5) History edit dialog. */ /*** DOWNLOAD EDIT DIALOG ************************************************************/ var DownloadsEditDialog = (new function($) { 'use strict'; // Controls var $DownloadsEditDialog; var $DownloadsLogTable; var $DownloadsFileTable; var $DownloadsEdit_ParamData; // State var curGroup; var notification = null; var postParams = []; var lastPage; var lastFullscreen; var logFilled; var files; this.init = function() { $DownloadsEditDialog = $('#DownloadsEditDialog'); $DownloadsEdit_ParamData = $('#DownloadsEdit_ParamData'); $('#DownloadsEdit_Save').click(saveChanges); $('#DownloadsEdit_Pause').click(itemPause); $('#DownloadsEdit_Resume').click(itemResume); $('#DownloadsEdit_Delete').click(itemDelete); $('#DownloadsEdit_CancelPP').click(itemCancelPP); $('#DownloadsEdit_Param, #DownloadsEdit_Log, #DownloadsEdit_File, #DownloadsEdit_Dupe').click(tabClick); $('#DownloadsEdit_Back').click(backClick); $DownloadsLogTable = $('#DownloadsEdit_LogTable'); $DownloadsLogTable.fasttable( { filterInput: '#DownloadsEdit_LogTable_filter', pagerContainer: '#DownloadsEdit_LogTable_pager', filterCaseSensitive: false, pageSize: 100, maxPages: 3, hasHeader: true, renderCellCallback: logTableRenderCellCallback }); $DownloadsFileTable = $('#DownloadsEdit_FileTable'); $DownloadsFileTable.fasttable( { filterInput: '#DownloadsEdit_FileTable_filter', pagerContainer: '#DownloadsEdit_FileTable_pager', filterCaseSensitive: false, headerCheck: '#DownloadsEdit_FileTable > thead > tr:first-child', pageSize: 10000, hasHeader: true, renderCellCallback: fileTableRenderCellCallback }); $DownloadsFileTable.on('click', 'tbody div.check', function(event) { $DownloadsFileTable.fasttable('itemCheckClick', this.parentNode.parentNode, event); }); $DownloadsFileTable.on('click', 'thead div.check', function() { $DownloadsFileTable.fasttable('titleCheckClick') }); $DownloadsFileTable.on('mousedown', Util.disableShiftMouseDown); $DownloadsEditDialog.on('hidden', function() { // cleanup $DownloadsLogTable.fasttable('update', []); $DownloadsFileTable.fasttable('update', []); $DownloadsEdit_ParamData.empty(); // resume updates Refresher.resume(); }); TabDialog.extend($DownloadsEditDialog); if (UISettings.setFocus) { $DownloadsEditDialog.on('shown', function() { if ($('#DownloadsEdit_NZBName').is(":visible")) { $('#DownloadsEdit_NZBName').focus(); } }); } } this.showModal = function(nzbid, allGroups) { var group = null; // find Group object for (var i=0; i 0 ? Util.formatTimeHMS((group.RemainingSizeMB-group.PausedSizeMB)*1024/(Status.status.DownloadRate/1024)) : ''); var table = ''; table += 'Age' + age + ''; table += 'Total' + size + ''; table += 'Paused' + unpausedSize + ''; table += 'Unpaused' + remaining + ''; //table += 'Active downloads' + group.ActiveDownloads + ''; //table += 'Estimated time' + estimated + ''; table += 'Health (current/critical)' + Math.floor(group.Health / 10) + '% / ' + Math.floor(group.CriticalHealth / 10) + '%'; table += 'Files (total/remaining/pars)' + group.FileCount + ' / ' + group.RemainingFileCount + ' / ' + group.RemainingParCount + ''; $('#DownloadsEdit_Statistics').html(table); $('#DownloadsEdit_Title').text(Util.formatNZBName(group.NZBName)); $('DownloadsEdit_Title').html($('#DownloadsEdit_Title').html() + ' ' + status); $('#DownloadsEdit_NZBName').attr('value', group.NZBName); $('#DownloadsEdit_NZBName').attr('readonly', group.postprocess); // Priority var v = $('#DownloadsEdit_Priority'); DownloadsUI.fillPriorityCombo(v); v.val(group.MaxPriority); if (v.val() != group.MaxPriority) { v.append(''); } v.attr('disabled', 'disabled'); // Category var v = $('#DownloadsEdit_Category'); DownloadsUI.fillCategoryCombo(v); v.val(group.Category); if (v.val() != group.Category) { v.append($('').text(group.Category)); } // duplicate settings $('#DownloadsEdit_DupeKey').val(group.DupeKey); $('#DownloadsEdit_DupeScore').val(group.DupeScore); $('#DownloadsEdit_DupeMode').val(group.DupeMode); $DownloadsLogTable.fasttable('update', []); $DownloadsFileTable.fasttable('update', []); var postParamConfig = ParamTab.createPostParamConfig(); Util.show('#DownloadsEdit_NZBNameReadonly', group.postprocess); Util.show('#DownloadsEdit_CancelPP', group.postprocess); Util.show('#DownloadsEdit_Delete', !group.postprocess); Util.show('#DownloadsEdit_Pause', !group.postprocess); Util.show('#DownloadsEdit_Resume', false); Util.show('#DownloadsEdit_Save', !group.postprocess); var dupeCheck = Options.option('DupeCheck') === 'yes'; Util.show('#DownloadsEdit_Dupe', dupeCheck); var postParam = postParamConfig[0].options.length > 0; var postLog = group.postprocess && group.post.Log.length > 0; Util.show('#DownloadsEdit_Param', postParam); Util.show('#DownloadsEdit_Log', postLog); if (group.postprocess) { $('#DownloadsEdit_NZBName').attr('disabled', 'disabled'); $('#DownloadsEdit_Priority').attr('disabled', 'disabled'); $('#DownloadsEdit_Category').attr('disabled', 'disabled'); $('#DownloadsEdit_Close').addClass('btn-primary'); $('#DownloadsEdit_Close').text('Close'); } else { $('#DownloadsEdit_NZBName').removeAttr('disabled'); $('#DownloadsEdit_Priority').removeAttr('disabled'); $('#DownloadsEdit_Category').removeAttr('disabled'); $('#DownloadsEdit_Close').removeClass('btn-primary'); $('#DownloadsEdit_Close').text('Cancel'); if (group.RemainingSizeHi == group.PausedSizeHi && group.RemainingSizeLo == group.PausedSizeLo) { $('#DownloadsEdit_Resume').show(); $('#DownloadsEdit_Pause').hide(); } } if (postParam) { postParams = ParamTab.buildPostParamTab($DownloadsEdit_ParamData, postParamConfig, curGroup.Parameters); } EditUI.buildDNZBLinks(curGroup.Parameters, 'DownloadsEdit_DNZB'); enableAllButtons(); $('#DownloadsEdit_GeneralTab').show(); $('#DownloadsEdit_ParamTab').hide(); $('#DownloadsEdit_LogTab').hide(); $('#DownloadsEdit_FileTab').hide(); $('#DownloadsEdit_DupeTab').hide(); $('#DownloadsEdit_Back').hide(); $('#DownloadsEdit_BackSpace').show(); $DownloadsEditDialog.restoreTab(); $('#DownloadsEdit_FileTable_filter').val(''); $('#DownloadsEdit_LogTable_filter').val(''); $('#DownloadsEdit_LogTable_pagerBlock').hide(); files = null; logFilled = false; notification = null; $DownloadsEditDialog.modal({backdrop: 'static'}); } function completed() { $DownloadsEditDialog.modal('hide'); Refresher.update(); if (notification) { Notification.show(notification); notification = null; } } function tabClick(e) { e.preventDefault(); $('#DownloadsEdit_Back').fadeIn(500); $('#DownloadsEdit_BackSpace').hide(); var tab = '#' + $(this).attr('data-tab'); lastPage = $(tab); lastFullscreen = ($(this).attr('data-fullscreen') === 'true') && !UISettings.miniTheme; $('#DownloadsEdit_FileBlock').removeClass('modal-inner-scroll'); $('#DownloadsEdit_FileBlock').css('top', ''); if (UISettings.miniTheme && files === null) { $('#DownloadsEdit_FileBlock').css('min-height', $DownloadsEditDialog.height()); } if (UISettings.miniTheme && !logFilled) { $('#DownloadsEdit_LogBlock').css('min-height', $DownloadsEditDialog.height()); } $DownloadsEditDialog.switchTab($('#DownloadsEdit_GeneralTab'), lastPage, e.shiftKey || !UISettings.slideAnimation ? 0 : 500, {fullscreen: lastFullscreen, mini: UISettings.miniTheme, complete: function() { if (!UISettings.miniTheme) { $('#DownloadsEdit_FileBlock').css('top', $('#DownloadsEdit_FileBlock').position().top); $('#DownloadsEdit_FileBlock').addClass('modal-inner-scroll'); } else { $('#DownloadsEdit_FileBlock').css('min-height', ''); $('#DownloadsEdit_LogBlock').css('min-height', ''); } }}); if (tab === '#DownloadsEdit_LogTab' && !logFilled && curGroup.post && curGroup.post.Log && curGroup.post.Log.length > 0) { fillLog(); } if (tab === '#DownloadsEdit_FileTab' && files === null) { fillFiles(); } } function backClick(e) { e.preventDefault(); $('#DownloadsEdit_Back').fadeOut(500, function() { $('#DownloadsEdit_BackSpace').show(); }); $('#DownloadsEdit_FileBlock').removeClass('modal-inner-scroll'); $('#DownloadsEdit_FileBlock').css('top', ''); $DownloadsEditDialog.switchTab(lastPage, $('#DownloadsEdit_GeneralTab'), e.shiftKey || !UISettings.slideAnimation ? 0 : 500, {fullscreen: lastFullscreen, mini: UISettings.miniTheme, back: true}); } function disableAllButtons() { $('#DownloadsEditDialog .modal-footer .btn').attr('disabled', 'disabled'); setTimeout(function() { $('#DownloadsEdit_Transmit').show(); }, 500); } function enableAllButtons() { $('#DownloadsEditDialog .modal-footer .btn').removeAttr('disabled'); $('#DownloadsEdit_Transmit').hide(); } function saveChanges(e) { e.preventDefault(); disableAllButtons(); notification = null; saveName(); } function saveName() { var name = $('#DownloadsEdit_NZBName').val(); name !== curGroup.NZBName && !curGroup.postprocess ? RPC.call('editqueue', ['GroupSetName', 0, name, [curGroup.LastID]], function() { notification = '#Notif_Downloads_Saved'; savePriority(); }) :savePriority(); } function savePriority() { var priority = parseInt($('#DownloadsEdit_Priority').val()); priority !== curGroup.MaxPriority && curGroup.LastID > 0 ? RPC.call('editqueue', ['GroupSetPriority', 0, ''+priority, [curGroup.LastID]], function() { notification = '#Notif_Downloads_Saved'; saveCategory(); }) : saveCategory(); } function saveCategory() { var category = $('#DownloadsEdit_Category').val(); category !== curGroup.Category && curGroup.LastID > 0 ? RPC.call('editqueue', ['GroupSetCategory', 0, category, [curGroup.LastID]], function() { notification = '#Notif_Downloads_Saved'; saveDupeKey(); }) : saveDupeKey(); } function itemPause(e) { e.preventDefault(); disableAllButtons(); notification = '#Notif_Downloads_Paused'; RPC.call('editqueue', ['GroupPause', 0, '', [curGroup.LastID]], completed); } function itemResume(e) { e.preventDefault(); disableAllButtons(); notification = '#Notif_Downloads_Resumed'; RPC.call('editqueue', ['GroupResume', 0, '', [curGroup.LastID]], function() { if (Options.option('ParCheck') === 'force') { completed(); } else { RPC.call('editqueue', ['GroupPauseExtraPars', 0, '', [curGroup.LastID]], completed); } }); } function itemDelete(e) { e.preventDefault(); DownloadsUI.deleteConfirm(doItemDelete, false); } function doItemDelete(command) { disableAllButtons(); notification = '#Notif_Downloads_Deleted'; RPC.call('editqueue', [command, 0, '', [curGroup.LastID]], completed); } function itemCancelPP(e) { e.preventDefault(); disableAllButtons(); notification = '#Notif_Downloads_PostCanceled'; var postDelete = function() { RPC.call('editqueue', ['PostDelete', 0, '', [curGroup.post.ID]], completed); }; if (curGroup.LastID > 0) { RPC.call('editqueue', ['GroupDelete', 0, '', [curGroup.LastID]], postDelete); } else { postDelete(); } } /*** TAB: POST-PROCESSING PARAMETERS **************************************************/ function saveParam() { var paramList = ParamTab.prepareParamRequest(postParams); saveNextParam(paramList); } function saveNextParam(paramList) { if (paramList.length > 0) { RPC.call('editqueue', ['GroupSetParameter', 0, paramList[0], [curGroup.LastID]], function() { notification = '#Notif_Downloads_Saved'; paramList.shift(); saveNextParam(paramList); }) } else { saveFiles(); } } /*** TAB: DUPLICATE SETTINGS **************************************************/ function saveDupeKey() { var value = $('#DownloadsEdit_DupeKey').val(); value !== curGroup.DupeKey ? RPC.call('editqueue', ['GroupSetDupeKey', 0, value, [curGroup.LastID]], function() { notification = '#Notif_Downloads_Saved'; saveDupeScore(); }) :saveDupeScore(); } function saveDupeScore() { var value = $('#DownloadsEdit_DupeScore').val(); value != curGroup.DupeScore ? RPC.call('editqueue', ['GroupSetDupeScore', 0, value, [curGroup.LastID]], function() { notification = '#Notif_Downloads_Saved'; saveDupeMode(); }) :saveDupeMode(); } function saveDupeMode() { var value = $('#DownloadsEdit_DupeMode').val(); value !== curGroup.DupeMode ? RPC.call('editqueue', ['GroupSetDupeMode', 0, value, [curGroup.LastID]], function() { notification = '#Notif_Downloads_Saved'; saveParam(); }) :saveParam(); } /*** TAB: LOG *************************************************************************/ function fillLog() { logFilled = true; var data = []; for (var i=0; i < curGroup.post.Log.length; i++) { var message = curGroup.post.Log[i]; var kind; switch (message.Kind) { case 'INFO': kind = 'info'; break; case 'DETAIL': kind = 'detail'; break; case 'WARNING': kind = 'warning'; break; case 'ERROR': kind = 'error'; break; case 'DEBUG': kind = 'debug'; break; } var text = Util.textToHtml(message.Text); var time = Util.formatDateTime(message.Time + UISettings.timeZoneCorrection*60*60); var fields; if (!UISettings.miniTheme) { fields = [kind, time, text]; } else { var info = kind + ' ' + time + ' ' + text; fields = [info]; } var item = { id: message, fields: fields, search: message.Kind + ' ' + time + ' ' + message.Text }; data.unshift(item); } $DownloadsLogTable.fasttable('update', data); $DownloadsLogTable.fasttable('setCurPage', 1); Util.show('#DownloadsEdit_LogTable_pagerBlock', data.length > 100); } function logTableRenderCellCallback(cell, index, item) { if (index === 0) { cell.width = '65px'; } } /*** TAB: FILES *************************************************************************/ function fillFiles() { $('.loading-block', $DownloadsEditDialog).show(); RPC.call('listfiles', [0, 0, curGroup.NZBID], filesLoaded); } function filesLoaded(fileArr) { $('.loading-block', $DownloadsEditDialog).hide(); files = fileArr; var data = []; for (var i=0; i < files.length; i++) { var file = files[i]; if (!file.status) { file.status = file.Paused ? (file.ActiveDownloads > 0 ? 'pausing' : 'paused') : (file.ActiveDownloads > 0 ? 'downloading' : 'queued'); } var age = Util.formatAge(file.PostTime + UISettings.timeZoneCorrection*60*60); var size = Util.formatSizeMB(0, file.FileSizeLo); if (file.FileSizeLo !== file.RemainingSizeLo) { size = '(' + Util.round0(file.RemainingSizeLo / file.FileSizeLo * 100) + '%) ' + size; } var status; switch (file.status) { case 'downloading': case 'pausing': status = '' + file.status + ''; break; case 'paused': status = 'paused'; break; case 'queued': status = 'queued'; break; case 'deleted': status = 'deleted'; break; default: status = 'internal error(' + file.status + ')'; } var priority = ''; if (file.Priority != curGroup.MaxPriority) { priority = DownloadsUI.buildPriority(file.Priority); } var name = Util.textToHtml(file.Filename); var fields; if (!UISettings.miniTheme) { var info = name + ' ' + priority; fields = ['
    ', status, info, age, size]; } else { var info = '
    ' + name + '' + ' ' + (file.status === 'queued' ? '' : status) + ' ' + priority; fields = [info]; } var item = { id: file.ID, file: file, fields: fields, search: file.status + ' ' + file.Filename + ' ' + priority + ' ' + age + ' ' + size }; data.push(item); } $DownloadsFileTable.fasttable('update', data); $DownloadsFileTable.fasttable('setCurPage', 1); } function fileTableRenderCellCallback(cell, index, item) { if (index > 2) { cell.className = 'text-right'; } } this.editActionClick = function(action) { if (files.length == 0) { return; } var checkedRows = $DownloadsFileTable.fasttable('checkedRows'); if (checkedRows.length == 0) { Notification.show('#Notif_Edit_Select'); return; } for (var i = 0; i < files.length; i++) { var file = files[i]; file.moved = false; } var editIDList = []; var splitError = false; for (var i = 0; i < files.length; i++) { var n = i; if (action === 'down' || action === 'top') { // iterate backwards in the file list n = files.length-1-i; } var file = files[n]; if (checkedRows.indexOf(file.ID) > -1) { editIDList.push(file.ID); switch (action) { case 'pause': file.status = 'paused'; file.editAction = action; break; case 'resume': file.status = 'queued'; file.editAction = action; break; case 'delete': file.status = 'deleted'; file.editAction = action; break; case 'top': if (!file.moved) { files.splice(n, 1); files.unshift(file); file.moved = true; file.editMoved = true; i--; } break; case 'up': if (!file.moved && i > 0) { files.splice(i, 1); files.splice(i-1, 0, file); file.moved = true; file.editMoved = true; } break; case 'down': if (!file.moved && i > 0) { files.splice(n, 1); files.splice(n+1, 0, file); file.moved = true; file.editMoved = true; } break; case 'bottom': if (!file.moved) { files.splice(i, 1); files.push(file); file.moved = true; file.editMoved = true; i--; } break; case 'split': if (file.ActiveDownloads > 0 || file.FileSizeLo !== file.RemainingSizeLo) { splitError = true; } break; } } } if (action === 'split') { if (splitError) { Notification.show('#Notif_Downloads_SplitNotPossible'); } else { DownloadsSplitDialog.showModal(curGroup, editIDList); } } filesLoaded(files); } function saveFilesActions(actions, commands) { if (actions.length === 0 || !files || files.length === 0) { saveFileOrder(); return; } var action = actions.shift(); var command = commands.shift(); var IDs = []; for (var i = 0; i < files.length; i++) { var file = files[i]; if (file.editAction === action) { IDs.push(file.ID); } } if (IDs.length > 0) { RPC.call('editqueue', [command, 0, '', IDs], function() { notification = '#Notif_Downloads_Saved'; saveFilesActions(actions, commands); }) } else { saveFilesActions(actions, commands); } } function saveFiles() { saveFilesActions(['pause', 'resume', 'delete'], ['FilePause', 'FileResume', 'FileDelete']); } function saveFileOrder() { if (!files || files.length === 0) { completed(); return; } var IDs = []; var hasMovedFiles = false; for (var i = 0; i < files.length; i++) { var file = files[i]; IDs.push(file.ID); hasMovedFiles |= file.editMoved; } if (hasMovedFiles) { RPC.call('editqueue', ['FileReorder', 0, '', IDs], function() { notification = '#Notif_Downloads_Saved'; completed(); }) } else { completed(); } } }(jQuery)); /*** COMMON FUNCTIONS FOR EDIT DIALOGS ************************************************************/ var EditUI = (new function($) { 'use strict' this.buildDNZBLinks = function(parameters, prefix) { $('.' + prefix).hide(); var hasItems = false; for (var i=0; i < parameters.length; i++) { var param = parameters[i]; if (param.Name.substr(0, 6) === '*DNZB:') { var linkName = param.Name.substr(6, 100); var $paramLink = $('#' + prefix + '_' + linkName); if($paramLink.length > 0) { $paramLink.attr('href', param.Value); $paramLink.show(); hasItems = true; } } } Util.show('#' + prefix + '_Section', hasItems); } }(jQuery)); /*** PARAM TAB FOR EDIT DIALOGS ************************************************************/ var ParamTab = (new function($) { 'use strict' this.buildPostParamTab = function(configData, postParamConfig, parameters) { var postParams = $.extend(true, [], postParamConfig); Options.mergeValues(postParams, parameters); var content = Config.buildOptionsContent(postParams[0]); configData.empty(); configData.append(content); configData.addClass('retain-margin'); var lastClass = ''; var lastDiv = null; for (var i=0; i < configData.children().length; i++) { var div = $(configData.children()[i]); var divClass = div.attr('class'); if (divClass != lastClass && lastClass != '') { lastDiv.addClass('wants-divider'); } lastDiv = div; lastClass = divClass; } return postParams; } this.createPostParamConfig = function() { var postParamConfig = Options.postParamConfig; defineBuiltinParams(postParamConfig); return postParamConfig; } function defineBuiltinParams(postParamConfig) { if (postParamConfig.length == 0) { postParamConfig.push({category: 'P', postparam: true, options: []}); } if (!Options.findOption(postParamConfig[0].options, '*Unpack:')) { postParamConfig[0].options.unshift({name: '*Unpack:Password', value: '', defvalue: '', select: [], caption: 'Password', sectionId: '_Unpack_', description: 'Unpack-password for encrypted archives.'}); postParamConfig[0].options.unshift({name: '*Unpack:', value: '', defvalue: 'yes', select: ['yes', 'no'], caption: 'Unpack', sectionId: '_Unpack_', description: 'Unpack rar and 7-zip archives.'}); } } this.prepareParamRequest = function(postParams) { var request = []; for (var i=0; i < postParams.length; i++) { var section = postParams[i]; for (var j=0; j < section.options.length; j++) { var option = section.options[j]; if (!option.template && !section.hidden) { var oldValue = option.value; var newValue = Config.getOptionValue(option); if (oldValue != newValue && !((oldValue === null || oldValue === '') && newValue === option.defvalue)) { var opt = option.name + '=' + newValue; request.push(opt); } } } } return request; } }(jQuery)); /*** DOWNLOAD MULTI EDIT DIALOG ************************************************************/ var DownloadsMultiDialog = (new function($) { 'use strict' // Controls var $DownloadsMultiDialog; // State var multiIDList; var notification = null; var oldPriority; var oldCategory; this.init = function() { $DownloadsMultiDialog = $('#DownloadsMultiDialog'); $('#DownloadsMulti_Save').click(saveChanges); $DownloadsMultiDialog.on('hidden', function () { Refresher.resume(); }); if (UISettings.setFocus) { $DownloadsMultiDialog.on('shown', function () { if ($('#DownloadsMulti_Priority').is(":visible")) { $('#DownloadsMulti_Priority').focus(); } }); } } this.showModal = function(nzbIdList, allGroups) { var groups = []; multiIDList = []; for (var i=0; i -1) { groups.push(gr); multiIDList.push(gr.LastID); } } if (groups.length == 0) { return; } Refresher.pause(); var FileSizeMB = 0, FileSizeLo = 0; var RemainingSizeMB = 0, RemainingSizeLo = 0; var PausedSizeMB = 0, PausedSizeLo = 0; var FileCount = 0, RemainingFileCount = 0, RemainingParCount = 0; var paused = true; var Priority = groups[0].MaxPriority; var PriorityDiff = false; var Category = groups[0].Category; var CategoryDiff = false; for (var i=0; i 0 ? Util.formatTimeHMS((RemainingSizeMB-PausedSizeMB)*1024/(Status.status.DownloadRate/1024)) : ''); var table = ''; table += 'Total' + size + ''; table += 'Paused' + unpausedSize + ''; table += 'Unpaused' + remaining + ''; table += 'Estimated time' + estimated + ''; table += 'Files (total/remaining/pars)' + FileCount + ' / ' + RemainingFileCount + ' / ' + RemainingParCount + ''; $('#DownloadsMulti_Statistics').html(table); $('#DownloadsMulti_Title').text('Multiple records (' + groups.length + ')'); // Priority var v = $('#DownloadsMulti_Priority'); DownloadsUI.fillPriorityCombo(v); v.val(Priority); if (v.val() != Priority) { v.append(''); v.val(Priority); } if (PriorityDiff) { v.append(''); } oldPriority = v.val(); $('#DownloadsMulti_Priority').removeAttr('disabled'); // Category var v = $('#DownloadsMulti_Category'); DownloadsUI.fillCategoryCombo(v); v.val(Category); if (v.val() != Category) { v.append($('').text(Category)); v.val(Category); } if (CategoryDiff) { v.append(''); } oldCategory = v.val(); enableAllButtons(); $('#DownloadsMulti_GeneralTabLink').tab('show'); notification = null; $DownloadsMultiDialog.modal({backdrop: 'static'}); } function enableAllButtons() { $('#DownloadsMulti .modal-footer .btn').removeAttr('disabled'); $('#DownloadsMulti_Transmit').hide(); } function disableAllButtons() { $('#DownloadsMulti .modal-footer .btn').attr('disabled', 'disabled'); setTimeout(function() { $('#DownloadsMulti_Transmit').show(); }, 500); } function saveChanges(e) { e.preventDefault(); disableAllButtons(); savePriority(); } function savePriority() { var priority = $('#DownloadsMulti_Priority').val(); (priority !== oldPriority && priority !== '') ? RPC.call('editqueue', ['GroupSetPriority', 0, priority, multiIDList], function() { notification = '#Notif_Downloads_Saved'; saveCategory(); }) : saveCategory(); } function saveCategory() { var category = $('#DownloadsMulti_Category').val(); (category !== oldCategory && category !== '') ? RPC.call('editqueue', ['GroupSetCategory', 0, category, multiIDList], function() { notification = '#Notif_Downloads_Saved'; completed(); }) : completed(); } function completed() { $DownloadsMultiDialog.modal('hide'); Refresher.update(); if (notification) { Notification.show(notification); } } }(jQuery)); /*** DOWNLOAD MERGE DIALOG ************************************************************/ var DownloadsMergeDialog = (new function($) { 'use strict' // Controls var $DownloadsMergeDialog; // State var mergeEditIDList; this.init = function() { $DownloadsMergeDialog = $('#DownloadsMergeDialog'); $('#DownloadsMerge_Merge').click(merge); $DownloadsMergeDialog.on('hidden', function () { Refresher.resume(); }); if (UISettings.setFocus) { $DownloadsMergeDialog.on('shown', function () { $('#DownloadsMerge_Merge').focus(); }); } } this.showModal = function(nzbIdList, allGroups) { Refresher.pause(); mergeEditIDList = []; $('#DownloadsMerge_Files').empty(); for (var i = 0; i < allGroups.length; i++) { var group = allGroups[i]; if (nzbIdList.indexOf(group.NZBID) > -1) { mergeEditIDList.push(group.LastID); var html = '
    ' + Util.formatNZBName(group.NZBName) + '
    '; $('#DownloadsMerge_Files').append(html); } } $DownloadsMergeDialog.modal({backdrop: 'static'}); } function merge() { RPC.call('editqueue', ['GroupMerge', 0, '', mergeEditIDList], completed); } function completed() { $DownloadsMergeDialog.modal('hide'); Refresher.update(); Notification.show('#Notif_Downloads_Merged'); } }(jQuery)); /*** DOWNLOAD SPLIT DIALOG ************************************************************/ var DownloadsSplitDialog = (new function($) { 'use strict' // Controls var $DownloadsSplitDialog; // State var splitEditIDList; this.init = function() { $DownloadsSplitDialog = $('#DownloadsSplitDialog'); $('#DownloadsSplit_Split').click(split); $DownloadsSplitDialog.on('hidden', function () { Refresher.resume(); }); if (UISettings.setFocus) { $DownloadsSplitDialog.on('shown', function () { $('#DownloadsSplit_Merge').focus(); }); } } this.showModal = function(group, editIDList) { Refresher.pause(); splitEditIDList = editIDList; var groupName = group.NZBName + ' (' + editIDList[0] + (editIDList.length > 1 ? '-' + editIDList[editIDList.length-1] : '') + ')'; $('#DownloadsSplit_NZBName').attr('value', groupName); $DownloadsSplitDialog.modal({backdrop: 'static'}); } function split() { var groupName = $('#DownloadsSplit_NZBName').val(); RPC.call('editqueue', ['FileSplit', 0, groupName, splitEditIDList], completed); } function completed(result) { $('#DownloadsEditDialog').modal('hide'); $DownloadsSplitDialog.modal('hide'); Refresher.update(); Notification.show(result ? '#Notif_Downloads_Splitted' : '#Notif_Downloads_SplitError'); } }(jQuery)); /*** EDIT HISTORY DIALOG *************************************************************************/ var HistoryEditDialog = (new function() { 'use strict' // Controls var $HistoryEditDialog; var $HistoryEdit_ParamData; var $ServStatsTable; // State var curHist; var notification = null; var postParams = []; var lastPage; var lastFullscreen; var saveCompleted; this.init = function() { $HistoryEditDialog = $('#HistoryEditDialog'); $HistoryEdit_ParamData = $('#HistoryEdit_ParamData'); $('#HistoryEdit_Save').click(saveChanges); $('#HistoryEdit_Delete').click(itemDelete); $('#HistoryEdit_Return, #HistoryEdit_ReturnURL').click(itemReturn); $('#HistoryEdit_Reprocess').click(itemReprocess); $('#HistoryEdit_Redownload').click(itemRedownload); $('#HistoryEdit_Param, #HistoryEdit_Dupe').click(tabClick); $('#HistoryEdit_Back').click(backClick); $('#HistoryEdit_MarkGood').click(itemGood); $('#HistoryEdit_MarkBad').click(itemBad); $ServStatsTable = $('#HistoryEdit_ServStatsTable'); $ServStatsTable.fasttable( { filterInput: '#HistoryEdit_ServStatsTable_filter', pagerContainer: '#HistoryEdit_ServStatsTable_pager', pageSize: 100, maxPages: 3, hasHeader: true, renderCellCallback: servStatsTableRenderCellCallback }); $HistoryEditDialog.on('hidden', function () { $HistoryEdit_ParamData.empty(); // resume updates Refresher.resume(); }); TabDialog.extend($HistoryEditDialog); } this.showModal = function(hist) { Refresher.pause(); curHist = hist; var status; if (hist.Kind === 'NZB') { status = 'health: ' + Math.floor(hist.Health / 10) + '%'; if (hist.MarkStatus !== 'NONE') { status += ' ' + HistoryUI.buildStatus(hist.MarkStatus, 'Mark: '); } if (hist.DeleteStatus === 'NONE') { status += ' ' + HistoryUI.buildStatus(hist.ParStatus, 'Par: ') + ' ' + (Options.option('Unpack') == 'yes' || hist.UnpackStatus != 'NONE' ? HistoryUI.buildStatus(hist.UnpackStatus, 'Unpack: ') : '') + ' ' + (hist.MoveStatus === "FAILURE" ? HistoryUI.buildStatus(hist.MoveStatus, 'Move: ') : ''); } else { status += ' ' + HistoryUI.buildStatus('edit-deleted-' + hist.DeleteStatus, 'Delete: '); } for (var i=0; i' + (hist.Kind === 'DUP' ? 'hidden' : hist.Kind) + ''); } if (hist.Kind !== 'DUP') { $('#HistoryEdit_Category').text(hist.Category); } if (hist.Kind === 'NZB') { $('#HistoryEdit_Path').text(hist.FinalDir !== '' ? hist.FinalDir : hist.DestDir); var size = Util.formatSizeMB(hist.FileSizeMB, hist.FileSizeLo); var completion = hist.SuccessArticles + hist.FailedArticles > 0 ? Util.round0(hist.SuccessArticles * 100.0 / (hist.SuccessArticles + hist.FailedArticles)) + '%' : '--'; var table = ''; table += 'Total' + size + ''; table += 'Files (total/parked)' + hist.FileCount + ' / ' + hist.RemainingFileCount + ''; table += 'Articles (total/completion)' + (hist.ServerStats.length > 0 ? '' : '') + hist.TotalArticles + ' / ' + completion + (hist.ServerStats.length > 0 ? ' ' : '') + ''; $('#HistoryEdit_Statistics').html(table); $('#HistoryEdit_ServStat').click(tabClick); fillServStats(); } if (hist.Kind !== 'URL') { $('#HistoryEdit_DupeKey').val(hist.DupeKey); $('#HistoryEdit_DupeScore').val(hist.DupeScore); $('#HistoryEdit_DupeMode').val(hist.DupeMode); $('#HistoryEdit_DupeBackup').prop('checked', hist.DeleteStatus === 'DUPE'); $('#HistoryEdit_DupeBackup').prop('disabled', !(hist.DeleteStatus === 'DUPE' || hist.DeleteStatus === 'MANUAL')); } Util.show($('#HistoryEdit_DupeBackup').closest('.control-group'), hist.Kind === 'NZB'); $('#HistoryEdit_DupeMode').closest('.control-group').toggleClass('last-group', hist.Kind !== 'NZB'); Util.show('#HistoryEdit_Return', hist.RemainingFileCount > 0); Util.show('#HistoryEdit_ReturnURL', hist.Kind === 'URL'); Util.show('#HistoryEdit_Redownload', hist.Kind === 'NZB'); Util.show('#HistoryEdit_PathGroup, #HistoryEdit_StatisticsGroup, #HistoryEdit_Reprocess', hist.Kind === 'NZB'); Util.show('#HistoryEdit_CategoryGroup', hist.Kind !== 'DUP'); Util.show('#HistoryEdit_DupGroup', hist.Kind === 'DUP'); var dupeCheck = Options.option('DupeCheck') === 'yes'; Util.show('#HistoryEdit_MarkGood', dupeCheck && ((hist.Kind === 'NZB' && hist.MarkStatus !== 'GOOD') || (hist.Kind === 'DUP' && hist.DupStatus !== 'GOOD'))); Util.show('#HistoryEdit_MarkBad', dupeCheck && hist.Kind !== 'URL'); Util.show('#HistoryEdit_Dupe', dupeCheck && hist.Kind !== 'URL'); $('#HistoryEdit_CategoryGroup').toggleClass('control-group-last', hist.Kind === 'URL'); var postParamConfig = ParamTab.createPostParamConfig(); var postParam = hist.Kind === 'NZB' && postParamConfig[0].options.length > 0; Util.show('#HistoryEdit_Param', postParam); if (postParam) { postParams = ParamTab.buildPostParamTab($HistoryEdit_ParamData, postParamConfig, curHist.Parameters); } EditUI.buildDNZBLinks(curHist.Parameters ? curHist.Parameters : [], 'HistoryEdit_DNZB'); enableAllButtons(); $('#HistoryEdit_GeneralTab').show(); $('#HistoryEdit_ParamTab').hide(); $('#HistoryEdit_ServStatsTab').hide(); $('#HistoryEdit_DupeTab').hide(); $('#HistoryEdit_Back').hide(); $('#HistoryEdit_BackSpace').show(); $HistoryEditDialog.restoreTab(); notification = null; $HistoryEditDialog.modal({backdrop: 'static'}); } function tabClick(e) { e.preventDefault(); $('#HistoryEdit_Back').fadeIn(500); $('#HistoryEdit_BackSpace').hide(); var tab = '#' + $(this).attr('data-tab'); lastPage = $(tab); lastFullscreen = ($(this).attr('data-fullscreen') === 'true') && !UISettings.miniTheme; $HistoryEditDialog.switchTab($('#HistoryEdit_GeneralTab'), lastPage, e.shiftKey || !UISettings.slideAnimation ? 0 : 500, {fullscreen: lastFullscreen, mini: UISettings.miniTheme}); } function backClick(e) { e.preventDefault(); $('#HistoryEdit_Back').fadeOut(500, function() { $('#HistoryEdit_BackSpace').show(); }); $HistoryEditDialog.switchTab(lastPage, $('#HistoryEdit_GeneralTab'), e.shiftKey || !UISettings.slideAnimation ? 0 : 500, {fullscreen: lastFullscreen, mini: UISettings.miniTheme, back: true}); } function disableAllButtons() { $('#HistoryEditDialog .modal-footer .btn').attr('disabled', 'disabled'); setTimeout(function() { $('#HistoryEdit_Transmit').show(); }, 500); } function enableAllButtons() { $('#HistoryEditDialog .modal-footer .btn').removeAttr('disabled'); $('#HistoryEdit_Transmit').hide(); } function itemDelete(e) { e.preventDefault(); HistoryUI.deleteConfirm(doItemDelete, curHist.Kind === 'NZB', curHist.Kind === 'DUP', curHist.ParStatus === 'FAILURE' || curHist.UnpackStatus === 'FAILURE', false); } function doItemDelete(command) { disableAllButtons(); notification = '#Notif_History_Deleted'; RPC.call('editqueue', [command, 0, '', [curHist.ID]], completed); } function itemReturn(e) { e.preventDefault(); disableAllButtons(); notification = '#Notif_History_Returned'; RPC.call('editqueue', ['HistoryReturn', 0, '', [curHist.ID]], completed); } function itemRedownload(e) { e.preventDefault(); if (curHist.SuccessArticles > 0) { ConfirmDialog.showModal('HistoryEditRedownloadConfirmDialog', doItemRedownload); } else { doItemRedownload(); } } function doItemRedownload() { disableAllButtons(); notification = '#Notif_History_Returned'; RPC.call('editqueue', ['HistoryRedownload', 0, '', [curHist.ID]], completed); } function itemReprocess(e) { e.preventDefault(); disableAllButtons(); saveCompleted = reprocess; saveDupeKey(); } function reprocess() { notification = '#Notif_History_Reproces'; RPC.call('editqueue', ['HistoryProcess', 0, '', [curHist.ID]], completed); } function completed() { $HistoryEditDialog.modal('hide'); Refresher.update(); if (notification) { Notification.show(notification); notification = null; } } function saveChanges(e) { e.preventDefault(); disableAllButtons(); notification = null; saveCompleted = completed; saveDupeKey(); } function itemGood(e) { e.preventDefault(); ConfirmDialog.showModal('HistoryEditGoodConfirmDialog', doItemGood); } function doItemGood() { disableAllButtons(); notification = '#Notif_History_Marked'; RPC.call('editqueue', ['HistoryMarkGood', 0, '', [curHist.ID]], completed); } function itemBad(e) { e.preventDefault(); ConfirmDialog.showModal('HistoryEditBadConfirmDialog', doItemBad); } function doItemBad() { disableAllButtons(); notification = '#Notif_History_Marked'; RPC.call('editqueue', ['HistoryMarkBad', 0, '', [curHist.ID]], completed); } /*** TAB: POST-PROCESSING PARAMETERS **************************************************/ function saveParam() { if (curHist.Kind === 'DUP') { saveCompleted(); return; } var paramList = ParamTab.prepareParamRequest(postParams); saveNextParam(paramList); } function saveNextParam(paramList) { if (paramList.length > 0) { RPC.call('editqueue', ['HistorySetParameter', 0, paramList[0], [curHist.ID]], function() { notification = '#Notif_History_Saved'; paramList.shift(); saveNextParam(paramList); }) } else { saveCompleted(); } } /*** TAB: DUPLICATE SETTINGS **************************************************/ function saveDupeKey() { var value = $('#HistoryEdit_DupeKey').val(); value !== curHist.DupeKey ? RPC.call('editqueue', ['HistorySetDupeKey', 0, value, [curHist.ID]], function() { notification = '#Notif_History_Saved'; saveDupeScore(); }) :saveDupeScore(); } function saveDupeScore() { var value = $('#HistoryEdit_DupeScore').val(); value != curHist.DupeScore ? RPC.call('editqueue', ['HistorySetDupeScore', 0, value, [curHist.ID]], function() { notification = '#Notif_History_Saved'; saveDupeMode(); }) :saveDupeMode(); } function saveDupeMode() { var value = $('#HistoryEdit_DupeMode').val(); value !== curHist.DupeMode ? RPC.call('editqueue', ['HistorySetDupeMode', 0, value, [curHist.ID]], function() { notification = '#Notif_History_Saved'; saveDupeBackup(); }) :saveDupeBackup(); } function saveDupeBackup() { var canChange = curHist.DeleteStatus === 'DUPE' || curHist.DeleteStatus === 'MANUAL'; var oldValue = curHist.DeleteStatus === 'DUPE'; var value = $('#HistoryEdit_DupeBackup').is(':checked'); canChange && value !== oldValue ? RPC.call('editqueue', ['HistorySetDupeBackup', 0, value ? "YES" : "NO", [curHist.ID]], function() { notification = '#Notif_History_Saved'; saveParam(); }) :saveParam(); } /*** TAB: SERVER STATISTICS **************************************************/ function fillServStats() { var data = []; for (var i=0; i < Status.status.NewsServers.length; i++) { var server = Status.status.NewsServers[i]; var name = Options.option('Server' + server.ID + '.Name'); if (name === null || name === '') { var host = Options.option('Server' + server.ID + '.Host'); var port = Options.option('Server' + server.ID + '.Port'); name = (host === null ? '' : host) + ':' + (port === null ? '119' : port); } var articles = '--'; var artquota = '--'; var success = '--'; var failures = '--'; for (var j=0; j < curHist.ServerStats.length; j++) { var stat = curHist.ServerStats[j]; if (stat.ServerID === server.ID && stat.SuccessArticles + stat.FailedArticles > 0) { articles = stat.SuccessArticles + stat.FailedArticles; artquota = Util.round0(articles * 100.0 / (curHist.SuccessArticles + curHist.FailedArticles)) + '%'; success = Util.round0(stat.SuccessArticles * 100.0 / articles) + '%'; failures = Util.round0(stat.FailedArticles * 100.0 / articles) + '%'; break; } } var fields = [server.ID + '. ' + name, articles, artquota, success, failures]; var item = { id: server.ID, fields: fields, search: '' }; data.push(item); } $ServStatsTable.fasttable('update', data); $ServStatsTable.fasttable('setCurPage', 1); } function servStatsTableRenderCellCallback(cell, index, item) { if (index > 0) { cell.className = 'text-right'; } } }(jQuery)); nzbget-12.0+dfsg/webui/fasttable.js000066400000000000000000000441571226450633000172760ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2012-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 778 $ * $Date: 2013-08-07 22:09:43 +0200 (Wed, 07 Aug 2013) $ * */ /* * Some code was borrowed from: * 1. Greg Weber's uiTableFilter jQuery plugin (http://gregweber.info/projects/uitablefilter) * 2. Denny Ferrassoli & Charles Christolini's TypeWatch jQuery plugin (http://github.com/dennyferra/TypeWatch) * 3. Justin Britten's tablesorterFilter jQuery plugin (http://www.justinbritten.com/work/2008/08/tablesorter-filter-results-based-on-search-string/) * 4. Allan Jardine's Bootstrap Pagination jQuery plugin for DataTables (http://datatables.net/) */ /* * In this module: * HTML tables with: * 1) very fast content updates; * 2) automatic pagination; * 3) search/filtering. * * What makes it unique and fast? * The tables are designed to be updated very often (up to 10 times per second). This has two challenges: * 1) updating of whole content is slow because the DOM updates are slow. * 2) if the DOM is updated during user interaction the user input is not processed correctly. * For example if the table is updated after the user pressed mouse key but before he/she released * the key, the click is not processed because the element, on which the click was performed, * doesn't exist after the update of DOM anymore. * * How Fasttable solves these problems? The solutions is to update only rows and cells, * which were changed by keeping the unchanged DOM-elements. * * Important: the UI of table must be designed in a way, that the cells which are frequently changed * (like remaining download size) should not be clickable, whereas the cells which are rarely changed * (e. g. Download name) can be clickable. */ (function($) { 'use strict'; $.fn.fasttable = function(method) { if (methods[method]) { return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 )); } else if ( typeof method === 'object' || ! method ) { return methods.init.apply( this, arguments ); } else { $.error( 'Method ' + method + ' does not exist on jQuery.fasttable' ); } }; var methods = { init : function(options) { return this.each(function() { var $this = $(this); var data = $this.data('fasttable'); // If the plugin hasn't been initialized yet if (!data) { /* Do more setup stuff here */ var config = {}; config = $.extend(config, defaults, options); config.filterInput = $(config.filterInput); config.filterClearButton = $(config.filterClearButton); config.pagerContainer = $(config.pagerContainer); config.infoContainer = $(config.infoContainer); config.headerCheck = $(config.headerCheck); // Create a timer which gets reset upon every keyup event. // Perform filter only when the timer's wait is reached (user finished typing or paused long enough to elapse the timer). // Do not perform the filter is the query has not changed. // Immediately perform the filter if the ENTER key is pressed. var timer; config.filterInput.keyup(function() { var timerWait = 500; var overrideBool = false; var inputBox = this; // Was ENTER pushed? if (inputBox.keyCode == 13) { timerWait = 1; overrideBool = true; } var timerCallback = function() { var value = inputBox.value; var data = $this.data('fasttable'); if ((value != data.lastFilter) || (overrideBool)) { data.lastFilter = value; if (data.content) { data.curPage = 1; refresh(data); } if (data.config.filterInputCallback) { data.config.filterInputCallback(value); } } }; // Reset the timer clearTimeout(timer); timer = setTimeout(timerCallback, timerWait); return false; }); config.filterClearButton.click(function() { var data = $this.data('fasttable'); data.lastFilter = ''; data.config.filterInput.val(''); if (data.content) { refresh(data); } if (data.config.filterClearCallback) { data.config.filterClearCallback(); } }); config.pagerContainer.on('click', 'li', function (e) { e.preventDefault(); var data = $this.data('fasttable'); var pageNum = $(this).text(); if (pageNum.indexOf('Prev') > -1) { data.curPage--; } else if (pageNum.indexOf('Next') > -1) { data.curPage++; } else if (isNaN(parseInt(pageNum))) { return; } else { data.curPage = parseInt(pageNum); } refresh(data); }); $this.data('fasttable', { target : $this, config : config, pageSize : parseInt(config.pageSize), maxPages : parseInt(config.maxPages), pageDots : Util.parseBool(config.pageDots), curPage : 1, checkedRows: [], lastClickedRowID: null }); } }); }, destroy : function() { return this.each(function() { var $this = $(this); var data = $this.data('fasttable'); // Namespacing FTW $(window).unbind('.fasttable'); $this.removeData('fasttable'); }); }, update : updateContent, setPageSize : setPageSize, setCurPage : setCurPage, filteredContent : function() { return $(this).data('fasttable').filteredContent; }, availableContent : function() { return $(this).data('fasttable').availableContent; }, checkedRows : function() { return $(this).data('fasttable').checkedRows; }, checkRow : function(id, checked) { checkRow($(this).data('fasttable'), id, checked); }, itemCheckClick : itemCheckClick, titleCheckClick : titleCheckClick }; function has_words(str, words, caseSensitive) { var text = caseSensitive ? str : str.toLowerCase(); for (var i = 0; i < words.length; i++) { if (text.indexOf(words[i]) === -1) { return false; } } return true; } function updateContent(content) { var data = $(this).data('fasttable'); if (content) { data.content = content; } refresh(data); } function refresh(data) { refilter(data); validateChecks(data); updatePager(data); updateInfo(data); updateTable(data); } function refilter(data) { var filterInput = data.config.filterInput; var phrase = filterInput.length > 0 ? filterInput.val() : ''; var caseSensitive = data.config.filterCaseSensitive; var words = caseSensitive ? phrase.split(' ') : phrase.toLowerCase().split(' '); var hasFilter = !(words.length === 1 && words[0] === ''); data.availableContent = []; data.filteredContent = []; for (var i = 0; i < data.content.length; i++) { var item = data.content[i]; if (hasFilter && item.search === undefined && data.config.fillSearchCallback) { data.config.fillSearchCallback(item); } if (!hasFilter || has_words(item.search, words, caseSensitive)) { data.availableContent.push(item); if (!data.config.filterCallback || data.config.filterCallback(item)) { data.filteredContent.push(item); } } } } function updateTable(data) { var oldTable = data.target[0]; var newTable = buildTBody(data); updateTBody(data, oldTable, newTable); } function buildTBody(data) { var table = $('
    ')[0]; for (var i=0; i < data.pageContent.length; i++) { var item = data.pageContent[i]; var row = table.insertRow(table.rows.length); row.fasttableID = item.id; if (data.checkedRows.indexOf(item.id) > -1) { row.className = 'checked'; } if (data.config.renderRowCallback) { data.config.renderRowCallback(row, item); } if (!item.fields) { if (data.config.fillFieldsCallback) { data.config.fillFieldsCallback(item); } else { item.fields = []; } } for (var j=0; j < item.fields.length; j++) { var cell = row.insertCell(row.cells.length); cell.innerHTML = item.fields[j]; if (data.config.renderCellCallback) { data.config.renderCellCallback(cell, j, item); } } } titleCheckRedraw(data); if (data.config.renderTableCallback) { data.config.renderTableCallback(table); } return table; } function updateTBody(data, oldTable, newTable) { var oldTRs = oldTable.rows; var newTRs = newTable.rows; var oldTBody = $('tbody', oldTable)[0]; var oldTRsLength = oldTRs.length - (data.config.hasHeader ? 1 : 0); // evlt. skip header row var newTRsLength = newTRs.length; for (var i=0; i < newTRs.length; ) { var newTR = newTRs[i]; if (i < oldTRsLength) { // update existing row var oldTR = oldTRs[i + (data.config.hasHeader ? 1 : 0)]; // evlt. skip header row var oldTDs = oldTR.cells; var newTDs = newTR.cells; oldTR.className = newTR.className; oldTR.fasttableID = newTR.fasttableID; for (var j=0, n = 0; j < oldTDs.length; j++, n++) { var oldTD = oldTDs[j]; var newTD = newTDs[n]; var oldHtml = oldTD.outerHTML; var newHtml = newTD.outerHTML; if (oldHtml !== newHtml) { oldTR.replaceChild(newTD, oldTD); n--; } } i++; } else { // add new row oldTBody.appendChild(newTR); } } var maxTRs = newTRsLength + (data.config.hasHeader ? 1 : 0); // evlt. skip header row; while (oldTRs.length > maxTRs) { oldTable.deleteRow(oldTRs.length - 1); } } function updatePager(data) { data.pageCount = Math.ceil(data.filteredContent.length / data.pageSize); if (data.curPage < 1) { data.curPage = 1; } if (data.curPage > data.pageCount) { data.curPage = data.pageCount; } var startIndex = (data.curPage - 1) * data.pageSize; data.pageContent = data.filteredContent.slice(startIndex, startIndex + data.pageSize); var pagerObj = data.config.pagerContainer; var pagerHtml = buildPagerHtml(data); var oldPager = pagerObj[0]; var newPager = $(pagerHtml)[0]; updatePagerContent(data, oldPager, newPager); } function buildPagerHtml(data) { var iListLength = data.maxPages; var iStart, iEnd, iHalf = Math.floor(iListLength/2); if (data.pageCount < iListLength) { iStart = 1; iEnd = data.pageCount; } else if (data.curPage -1 <= iHalf) { iStart = 1; iEnd = iListLength; } else if (data.curPage - 1 >= (data.pageCount-iHalf)) { iStart = data.pageCount - iListLength + 1; iEnd = data.pageCount; } else { iStart = data.curPage - 1 - iHalf + 1; iEnd = iStart + iListLength - 1; } var pager = '
      '; pager += '← Prev'; if (iStart > 1) { pager += '
    • 1
    • '; if (iStart > 2 && data.pageDots) { pager += '
    • '; } } for (var j=iStart; j<=iEnd; j++) { pager += '' + j + ''; } if (iEnd != data.pageCount) { if (iEnd < data.pageCount - 1 && data.pageDots) { pager += '
    • '; } pager += '
    • ' + data.pageCount + '
    • '; } pager += 'Next →'; pager += '
    '; return pager; } function updatePagerContent(data, oldPager, newPager) { var oldLIs = oldPager.getElementsByTagName('li'); var newLIs = newPager.getElementsByTagName('li'); var oldLIsLength = oldLIs.length; var newLIsLength = newLIs.length; for (var i=0, n=0; i < newLIs.length; i++, n++) { var newLI = newLIs[i]; if (n < oldLIsLength) { // update existing LI var oldLI = oldLIs[n]; var oldHtml = oldLI.outerHTML; var newHtml = newLI.outerHTML; if (oldHtml !== newHtml) { oldPager.replaceChild(newLI, oldLI); i--; } } else { // add new LI oldPager.appendChild(newLI); i--; } } while (oldLIs.length > newLIsLength) { oldPager.removeChild(oldPager.lastChild); } } function updateInfo(data) { if (data.content.length === 0) { var infoText = data.config.infoEmpty; } else if (data.curPage === 0) { var infoText = 'No matching records found (total ' + data.content.length + ')'; } else { var firstRecord = (data.curPage - 1) * data.pageSize + 1; var lastRecord = firstRecord + data.pageContent.length - 1; var infoText = 'Showing records ' + firstRecord + '-' + lastRecord + ' from ' + data.filteredContent.length; if (data.filteredContent.length != data.content.length) { infoText += ' filtered (total ' + data.content.length + ')'; } } data.config.infoContainer.html(infoText); if (data.config.updateInfoCallback) { data.config.updateInfoCallback({ total: data.content.length, available: data.availableContent.length, filtered: data.filteredContent.length, firstRecord: firstRecord, lastRecord: lastRecord }); } } function setPageSize(pageSize, maxPages, pageDots) { var data = $(this).data('fasttable'); data.pageSize = parseInt(pageSize); data.curPage = 1; if (maxPages !== undefined) { data.maxPages = maxPages; } if (pageDots !== undefined) { data.pageDots = pageDots; } refresh(data); } function setCurPage(page) { var data = $(this).data('fasttable'); data.curPage = parseInt(page); refresh(data); } function titleCheckRedraw(data) { var filteredContent = data.filteredContent; var checkedRows = data.checkedRows; var hasSelectedItems = false; var hasUnselectedItems = false; for (var i = 0; i < filteredContent.length; i++) { if (checkedRows.indexOf(filteredContent[i].id) === -1) { hasUnselectedItems = true; } else { hasSelectedItems = true; } } if (hasSelectedItems && hasUnselectedItems) { data.config.headerCheck.removeClass('checked').addClass('checkremove'); } else if (hasSelectedItems) { data.config.headerCheck.removeClass('checkremove').addClass('checked'); } else { data.config.headerCheck.removeClass('checked').removeClass('checkremove'); } } function itemCheckClick(row, event) { var data = $(this).data('fasttable'); var checkedRows = data.checkedRows; var id = row.fasttableID; var doToggle = true; if (event.shiftKey && data.lastClickedRowID != null) { var checked = checkedRows.indexOf(id) > -1; doToggle = !checkRange(data, id, data.lastClickedRowID, !checked); } if (doToggle) { toggleCheck(data, id); } data.lastClickedRowID = id; refresh(data); } function titleCheckClick() { var data = $(this).data('fasttable'); var filteredContent = data.filteredContent; var checkedRows = data.checkedRows; var hasSelectedItems = false; for (var i = 0; i < filteredContent.length; i++) { if (checkedRows.indexOf(filteredContent[i].id) > -1) { hasSelectedItems = true; break; } } data.lastClickedRowID = null; checkAll(data, !hasSelectedItems); } function toggleCheck(data, id) { var checkedRows = data.checkedRows; var index = checkedRows.indexOf(id); if (index > -1) { checkedRows.splice(index, 1); } else { checkedRows.push(id); } } function checkAll(data, checked) { var filteredContent = data.filteredContent; for (var i = 0; i < filteredContent.length; i++) { checkRow(data, filteredContent[i].id, checked); } refresh(data); } function checkRange(data, from, to, checked) { var filteredContent = data.filteredContent; var indexFrom = indexOfID(filteredContent, from); var indexTo = indexOfID(filteredContent, to); if (indexFrom === -1 || indexTo === -1) { return false; } if (indexTo < indexFrom) { var tmp = indexTo; indexTo = indexFrom; indexFrom = tmp; } for (var i = indexFrom; i <= indexTo; i++) { checkRow(data, filteredContent[i].id, checked); } return true; } function checkRow(data, id, checked) { if (checked) { if (data.checkedRows.indexOf(id) === -1) { data.checkedRows.push(id); } } else { var index = data.checkedRows.indexOf(id); if (index > -1) { data.checkedRows.splice(index, 1); } } } function indexOfID(content, id) { for (var i = 0; i < content.length; i++) { if (id === content[i].id) { return i; } } return -1; } function validateChecks(data) { var filteredContent = data.filteredContent; var checkedRows = data.checkedRows; var ids = []; for (var i = 0; i < data.content.length; i++) { ids.push(data.content[i].id); } for (var i = 0; i < checkedRows.length; i++) { if (ids.indexOf(checkedRows[i]) === -1) { checkedRows.splice(i, 1); i--; } } } var defaults = { filterInput: '#table-filter', filterClearButton: '#table-clear', filterCaseSensitive: false, pagerContainer: '#table-pager', infoContainer: '#table-info', pageSize: 10, maxPages: 5, pageDots: true, hasHeader: true, infoEmpty: 'No records', renderRowCallback: undefined, renderCellCallback: undefined, renderTableCallback: undefined, fillFieldsCallback: undefined, updateInfoCallback: undefined, filterInputCallback: undefined, filterClearCallback: undefined, fillSearchCallback: undefined, filterCallback: undefined, headerCheck: '#table-header-check' }; })(jQuery);nzbget-12.0+dfsg/webui/feed.js000066400000000000000000000573431226450633000162350ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 920 $ * $Date: 2013-12-16 22:15:49 +0100 (Mon, 16 Dec 2013) $ * */ /* * In this module: * 1) Feeds menu; * 2) Feed view/preview dialog; * 3) Feed filter dialog. */ /*** FEEDS **********************************************/ var Feeds = (new function($) { 'use strict'; this.init = function() { } this.redraw = function() { var menu = $('#RssMenu'); var menuItemTemplate = $('.feed-menu-template', menu); menuItemTemplate.removeClass('feed-menu-template').removeClass('hide').addClass('feed-menu'); var insertPos = $('#RssMenu_Divider', menu); $('.feed-menu', menu).remove(); for (var i=1; ;i++) { var url = Options.option('Feed' + i + '.URL'); if (url === null) { break; } if (url.trim() !== '') { var item = menuItemTemplate.clone(); var name = Options.option('Feed' + i + '.Name'); var a = $('a', item); a.text(name !== '' ? name : 'Feed' + i); a.attr('data-id', i); a.click(viewFeed); insertPos.before(item); } } Util.show('#RssMenuBlock', $('.feed-menu', menu).length > 0); } function viewFeed() { var id = parseInt($(this).attr('data-id')); FeedDialog.showModal(id); } this.fetchAll = function() { RPC.call('fetchfeed', [0], function() { Notification.show('#Notif_Feeds_FetchAll'); }); } }(jQuery)); /*** FEEDS VIEW / PREVIEW DIALOG **********************************************/ var FeedDialog = (new function($) { 'use strict'; // Controls var $FeedDialog; var $ItemTable; // State var items = null; var pageSize = 100; var curFilter = 'ALL'; var filenameMode = false; var tableInitialized = false; this.init = function() { $FeedDialog = $('#FeedDialog'); $ItemTable = $('#FeedDialog_ItemTable'); $ItemTable.fasttable( { filterInput: '#FeedDialog_ItemTable_filter', pagerContainer: '#FeedDialog_ItemTable_pager', filterCaseSensitive: false, headerCheck: '#FeedDialog_ItemTable > thead > tr:first-child', pageSize: pageSize, hasHeader: true, renderCellCallback: itemsTableRenderCellCallback }); $ItemTable.on('click', 'tbody div.check', function(event) { $ItemTable.fasttable('itemCheckClick', this.parentNode.parentNode, event); }); $ItemTable.on('click', 'thead div.check', function() { $ItemTable.fasttable('titleCheckClick') }); $ItemTable.on('mousedown', Util.disableShiftMouseDown); $FeedDialog.on('hidden', function() { // cleanup $ItemTable.fasttable('update', []); // resume updates Refresher.resume(); }); TabDialog.extend($FeedDialog); if (UISettings.setFocus) { $FeedDialog.on('shown', function() { //$('#FeedDialog_Name').focus(); }); } } this.showModal = function(id, name, url, filter, pauseNzb, category, priority) { Refresher.pause(); $ItemTable.fasttable('update', []); enableAllButtons(); $FeedDialog.restoreTab(); $('#FeedDialog_ItemTable_filter').val(''); $('#FeedDialog_ItemTable_pagerBlock').hide(); items = null; curFilter = 'ALL'; filenameMode = false; tableInitialized = false; $('#FeedDialog_Toolbar .badge').text('?'); updateFilterButtons(undefined, undefined, undefined, false); tableInitialized = false; $FeedDialog.modal({backdrop: 'static'}); $FeedDialog.maximize({mini: UISettings.miniTheme}); $('.loading-block', $FeedDialog).show(); if (id > 0) { var name = Options.option('Feed' + id + '.Name'); $('#FeedDialog_Title').text(name !== '' ? name : 'Feed'); RPC.call('viewfeed', [id, false], itemsLoaded, feedFailure); } else { $('#FeedDialog_Title').text(name !== '' ? name : 'Feed Preview'); var feedPauseNzb = pauseNzb === 'yes'; var feedCategory = category; var feedPriority = parseInt(priority); RPC.call('previewfeed', [name, url, filter, feedPauseNzb, feedCategory, feedPriority, false, 0, ''], itemsLoaded, feedFailure); } } function feedFailure(res) { $FeedDialog.modal('hide'); AlertDialog.showModal('Error', res); } function disableAllButtons() { $('#FeedDialog .modal-footer .btn').attr('disabled', 'disabled'); setTimeout(function() { $('#FeedDialog_Transmit').show(); }, 500); } function enableAllButtons() { $('#FeedDialog .modal-footer .btn').removeAttr('disabled'); $('#FeedDialog_Transmit').hide(); } function itemsLoaded(itemsArr) { $('.loading-block', $FeedDialog).hide(); items = itemsArr; updateTable(); $('.modal-inner-scroll', $FeedDialog).scrollTop(100).scrollTop(0); } function updateTable() { var countNew = 0; var countFetched = 0; var countBacklog = 0; var differentFilenames = false; var data = []; for (var i=0; i < items.length; i++) { var item = items[i]; var age = Util.formatAge(item.Time + UISettings.timeZoneCorrection*60*60); var size = (item.SizeMB > 0 || item.SizeLo > 0 || item.SizeHi > 0) ? Util.formatSizeMB(item.SizeMB, item.SizeLo) : ''; var status; switch (item.Status) { case 'UNKNOWN': status = 'UNKNOWN'; break; case 'BACKLOG': status = 'BACKLOG'; countBacklog +=1; break; case 'FETCHED': status = 'FETCHED'; countFetched +=1; break; case 'NEW': status = 'NEW'; countNew +=1; break; default: status = 'internal error(' + item.Status + ')'; } if (!(curFilter === item.Status || curFilter === 'ALL')) { continue; } differentFilenames = differentFilenames || (item.Filename !== item.Title); var itemName = filenameMode ? item.Filename : item.Title; var name = Util.textToHtml(itemName); name = name.replace(/\./g, '.').replace(/_/g, '_'); var fields; if (!UISettings.miniTheme) { fields = ['
    ', status, name, item.Category, age, size]; } else { var info = '
    ' + name + '' + ' ' + status; fields = [info]; } var item = { id: item.URL, item: item, fields: fields, search: item.Status + ' ' + itemName + ' ' + item.Category + ' ' + age + ' ' + size }; data.push(item); } $ItemTable.fasttable('update', data); $ItemTable.fasttable('setCurPage', 1); Util.show('#FeedDialog_ItemTable_pagerBlock', data.length > pageSize); updateFilterButtons(countNew, countFetched, countBacklog, differentFilenames); } function itemsTableRenderCellCallback(cell, index, item) { if (index > 3) { cell.className = 'text-right'; } } function updateFilterButtons(countNew, countFetched, countBacklog, differentFilenames) { if (countNew != undefined) { $('#FeedDialog_Badge_ALL,#FeedDialog_Badge_ALL2').text(countNew + countFetched + countBacklog); $('#FeedDialog_Badge_NEW,#FeedDialog_Badge_NEW2').text(countNew); $('#FeedDialog_Badge_FETCHED,#FeedDialog_Badge_FETCHED2').text(countFetched); $('#FeedDialog_Badge_BACKLOG,#FeedDialog_Badge_BACKLOG2').text(countBacklog); } $('#FeedDialog_Toolbar .btn').removeClass('btn-inverse'); $('#FeedDialog_Badge_' + curFilter + ',#FeedDialog_Badge_' + curFilter + '2').closest('.btn').addClass('btn-inverse'); $('#FeedDialog_Toolbar .badge').removeClass('badge-active'); $('#FeedDialog_Badge_' + curFilter + ',#FeedDialog_Badge_' + curFilter + '2').addClass('badge-active'); if (differentFilenames != undefined && !tableInitialized) { Util.show('#FeedDialog .FeedDialog-names', differentFilenames); tableInitialized = true; } $('#FeedDialog_Titles,#FeedDialog_Titles2').toggleClass('btn-inverse', !filenameMode); $('#FeedDialog_Filenames,#FeedDialog_Filenames2').toggleClass('btn-inverse', filenameMode); $('#FeedDialog_ItemTable_Name').text(filenameMode ? 'Filename' : 'Title'); } this.fetch = function() { var checkedRows = $ItemTable.fasttable('checkedRows'); if (checkedRows.length == 0) { Notification.show('#Notif_FeedDialog_Select'); return; } disableAllButtons(); var fetchItems = []; for (var i = 0; i < items.length; i++) { var item = items[i]; if (checkedRows.indexOf(item.URL) > -1) { fetchItems.push(item); } } fetchNextItem(fetchItems); } function fetchNextItem(fetchItems) { if (fetchItems.length > 0) { var name = fetchItems[0].Filename; if (name.substr(name.length-4, 4).toLowerCase() !== '.nzb') { name += '.nzb'; } RPC.call('appendurl', [name, fetchItems[0].AddCategory, fetchItems[0].Priority, false, fetchItems[0].URL, false, fetchItems[0].DupeKey, fetchItems[0].DupeScore, fetchItems[0].DupeMode], function() { fetchItems.shift(); fetchNextItem(fetchItems); }) } else { $FeedDialog.modal('hide'); Notification.show('#Notif_FeedDialog_Fetched'); } } this.filter = function(type) { curFilter = type; updateTable(); } this.setFilenameMode = function(mode) { filenameMode = mode; updateTable(); } }(jQuery)); /*** FEED FILTER DIALOG **********************************************/ var FeedFilterDialog = (new function($) { 'use strict'; // Controls var $FeedFilterDialog; var $ItemTable; var $Splitter; var $FilterInput; var $FilterBlock; var $FilterLines; var $FilterNumbers; var $PreviewBlock; var $ModalBody; var $LoadingBlock; var $CHAutoRematch; var $RematchIcon; // State var items = null; var pageSize = 100; var curFilter = 'ALL'; var filenameMode = false; var tableInitialized = false; var saveCallback; var splitStartPos; var feedName; var feedUrl; var feedFilter; var feedPauseNzb; var feedCategory; var feedPriority; var cacheTimeSec; var cacheId; var updating; var updateTimerIntitialized = false; var autoUpdate = false; var splitRatio; var firstUpdate; var lineNo; var showLines; this.init = function() { $FeedFilterDialog = $('#FeedFilterDialog'); $Splitter = $('#FeedFilterDialog_Splitter'); $Splitter.mousedown(splitterMouseDown); $('#FeedFilterDialog_Save').click(save); $FilterInput = $('#FeedFilterDialog_FilterInput'); $FilterBlock = $('#FeedFilterDialog_FilterBlock'); $FilterLines = $('#FeedFilterDialog_FilterLines'); $FilterNumbers = $('#FeedFilterDialog_FilterNumbers'); $PreviewBlock = $('#FeedFilterDialog_PreviewBlock'); $ModalBody = $('.modal-body', $FeedFilterDialog); $LoadingBlock = $('.loading-block', $FeedFilterDialog); $CHAutoRematch = $('#FeedFilterDialog_CHAutoRematch'); $RematchIcon = $('#FeedFilterDialog_RematchIcon'); autoUpdate = UISettings.read('$FeedFilterDialog_AutoRematch', '1') == '1'; updateRematchState(); initLines(); $ItemTable = $('#FeedFilterDialog_ItemTable'); $ItemTable.fasttable( { filterInput: '', pagerContainer: '#FeedFilterDialog_ItemTable_pager', filterCaseSensitive: false, headerCheck: '', pageSize: pageSize, hasHeader: true, renderCellCallback: itemsTableRenderCellCallback }); $ItemTable.on('mousedown', Util.disableShiftMouseDown); $FilterInput.keypress(filterKeyPress); $FeedFilterDialog.on('hidden', function() { // cleanup $ItemTable.fasttable('update', []); $(window).off('resize', windowResized); // resume updates Refresher.resume(); }); TabDialog.extend($FeedFilterDialog); if (UISettings.setFocus) { $FeedFilterDialog.on('shown', function() { $FilterInput.focus(); }); } } this.showModal = function(name, url, filter, pauseNzb, category, priority, _saveCallback) { saveCallback = _saveCallback; Refresher.pause(); $ItemTable.fasttable('update', []); $FeedFilterDialog.restoreTab(); $(window).on('resize', windowResized); splitterRestore(); $('#FeedFilterDialog_ItemTable_pagerBlock').hide(); $FilterInput.val(filter.replace(/\s*%\s*/g, '\n')); items = null; firstUpdate = true; curFilter = 'ALL'; filenameMode = false; tableInitialized = false; $('#FeedFilterDialog_Toolbar .badge').text('?'); updateFilterButtons(undefined, undefined, undefined, false); tableInitialized = false; $FeedFilterDialog.modal({backdrop: 'static'}); $FeedFilterDialog.maximize({mini: UISettings.miniTheme}); updateLines(); $LoadingBlock.show(); $('#FeedFilterDialog_Title').text(name !== '' ? name : 'Feed Preview'); feedName = name; feedUrl = url; feedFilter = filter; feedPauseNzb = pauseNzb === 'yes'; feedCategory = category; feedPriority = parseInt(priority); cacheId = '' + Math.random()*10000000; cacheTimeSec = 60*10; // 10 minutes if (url !== '') { RPC.call('previewfeed', [name, url, filter, feedPauseNzb, feedCategory, feedPriority, true, cacheTimeSec, cacheId], itemsLoaded, feedFailure); } else { $LoadingBlock.hide(); } } this.rematch = function() { updateFilter(); } function updateFilter() { if (feedUrl == '') { return; } tableInitialized = false; updating = true; var filter = $FilterInput.val().replace(/\n/g, '%'); RPC.call('previewfeed', [feedName, feedUrl, filter, feedPauseNzb, feedCategory, feedPriority, true, cacheTimeSec, cacheId], itemsLoaded, feedFailure); setTimeout(function() { if (updating) { $LoadingBlock.show(); } }, 500); } function feedFailure(msg, result) { updating = false; var filter = $FilterInput.val().replace(/\n/g, ' % '); if (firstUpdate && filter === feedFilter) { $FeedFilterDialog.modal('hide'); } $LoadingBlock.hide(); AlertDialog.showModal('Error', result ? result.error.message : msg); } function itemsLoaded(itemsArr) { updating = false; $LoadingBlock.hide(); items = itemsArr; updateTable(); if (firstUpdate) { $('.modal-inner-scroll', $FeedFilterDialog).scrollTop(100).scrollTop(0); } firstUpdate = false; if (!updateTimerIntitialized) { setupUpdateTimer(); updateTimerIntitialized = true; } } function updateTable() { var countAccepted = 0; var countRejected = 0; var countIgnored = 0; var differentFilenames = false; var filter = $FilterInput.val().split('\n'); var data = []; for (var i=0; i < items.length; i++) { var item = items[i]; var age = Util.formatAge(item.Time + UISettings.timeZoneCorrection*60*60); var size = (item.SizeMB > 0 || item.SizeLo > 0 || item.SizeHi > 0) ? Util.formatSizeMB(item.SizeMB, item.SizeLo) : ''; var status; switch (item.Match) { case 'ACCEPTED': var addInfo = [item.AddCategory !== feedCategory ? 'category: ' + item.AddCategory : null, item.Priority !== feedPriority ? DownloadsUI.buildPriorityText(item.Priority) : null, item.PauseNzb !== feedPauseNzb ? (item.PauseNzb ? 'paused' : 'unpaused') : null, item.DupeScore != 0 ? 'dupe-score: ' + item.DupeScore : null, item.DupeKey !== '' ? 'dupe-key: ' + item.DupeKey : null, item.DupeMode !== 'SCORE' ? 'dupe-mode: ' + item.DupeMode.toLowerCase() : null]. filter(function(e){return e}).join('; '); status = 'ACCEPTED'; countAccepted += 1; break; case 'REJECTED': status = 'REJECTED'; countRejected += 1; break; case 'IGNORED': status = 'IGNORED'; countIgnored += 1; break; default: status = 'internal error(' + item.Match + ')'; break; } if (!(curFilter === item.Match || curFilter === 'ALL')) { continue; } differentFilenames = differentFilenames || (item.Filename !== item.Title); var itemName = filenameMode ? item.Filename : item.Title; var name = Util.textToHtml(itemName); name = name.replace(/\./g, '.').replace(/_/g, '_'); var rule = ''; if (item.Rule > 0) { rule = ' ' + item.Rule + ' '; } var fields; if (!UISettings.miniTheme) { fields = [status, rule, name, item.Category, age, size]; } else { var info = '' + name + '' + ' ' + status; fields = [info]; } var dataItem = { id: item.URL, item: item, fields: fields, search: item.Match + ' ' + itemName + ' ' + item.Category + ' ' + age + ' ' + size }; data.push(dataItem); } $ItemTable.fasttable('update', data); Util.show('#FeedFilterDialog_ItemTable_pagerBlock', data.length > pageSize); updateFilterButtons(countAccepted, countRejected, countIgnored, differentFilenames); } function itemsTableRenderCellCallback(cell, index, item) { if (index > 3) { cell.className = 'text-right'; } } function updateFilterButtons(countAccepted, countRejected, countIgnored, differentFilenames) { if (countAccepted != undefined) { $('#FeedFilterDialog_Badge_ALL,#FeedFilterDialog_Badge_ALL2').text(countAccepted + countRejected + countIgnored); $('#FeedFilterDialog_Badge_ACCEPTED,#FeedFilterDialog_Badge_ACCEPTED2').text(countAccepted); $('#FeedFilterDialog_Badge_REJECTED,#FeedFilterDialog_Badge_REJECTED2').text(countRejected); $('#FeedFilterDialog_Badge_IGNORED,#FeedFilterDialog_Badge_IGNORED2').text(countIgnored); } $('#FeedFilterDialog_Toolbar .FeedFilterDialog-filter .btn').removeClass('btn-inverse'); $('#FeedFilterDialog_Badge_' + curFilter + ',#FeedFilterDialog_Badge_' + curFilter + '2').closest('.btn').addClass('btn-inverse'); $('#FeedFilterDialog_Toolbar .badge').removeClass('badge-active'); $('#FeedFilterDialog_Badge_' + curFilter + ',#FeedFilterDialog_Badge_' + curFilter + '2').addClass('badge-active'); if (differentFilenames != undefined && !tableInitialized) { Util.show('#FeedFilterDialog .FeedFilterDialog-names', differentFilenames); tableInitialized = true; } $('#FeedFilterDialog_Titles,#FeedFilterDialog_Titles2').toggleClass('btn-inverse', !filenameMode); $('#FeedFilterDialog_Filenames,#FeedFilterDialog_Filenames2').toggleClass('btn-inverse', filenameMode); $('#FeedFilterDialog_ItemTable_Name').text(filenameMode ? 'Filename' : 'Title'); } this.filter = function(type) { curFilter = type; updateTable(); } this.setFilenameMode = function(mode) { filenameMode = mode; updateTable(); } function save(e) { e.preventDefault(); $FeedFilterDialog.modal('hide'); var filter = $FilterInput.val().replace(/\n/g, ' % '); saveCallback(filter); } function setupUpdateTimer() { // Create a timer which gets reset upon every keyup event. // Perform filter only when the timer's wait is reached (user finished typing or paused long enough to elapse the timer). // Do not perform the filter if the query has not changed. var timer; var lastFilter = $FilterInput.val(); $FilterInput.keyup(function() { var timerCallback = function() { var value = $FilterInput.val(); if (value != lastFilter) { lastFilter = value; if (autoUpdate) { updateFilter(); } } }; // Reset the timer clearTimeout(timer); timer = setTimeout(timerCallback, 500); return false; }); } this.autoRematch = function() { autoUpdate = !autoUpdate; UISettings.write('$FeedFilterDialog_AutoRematch', autoUpdate ? '1' : '0'); updateRematchState(); if (autoUpdate) { updateFilter(); } } function updateRematchState() { Util.show($CHAutoRematch, autoUpdate); $RematchIcon.toggleClass('icon-process', !autoUpdate); $RematchIcon.toggleClass('icon-process-auto', autoUpdate); } function filterKeyPress(event) { if (event.which == 37) { event.preventDefault(); alert('Percent character (%) cannot be part of a filter because it is used\nas line separator when saving filter into configuration file.'); } } /*** SPLITTER ***/ function splitterMouseDown(e) { e.stopPropagation(); e.preventDefault(); splitStartPos = e.pageX; $(document).bind("mousemove", splitterMouseMove).bind("mouseup", splitterMouseUp); $ModalBody.css('cursor', 'col-resize'); $FilterInput.css('cursor', 'col-resize'); } function splitterMouseMove(e) { var newPos = e.pageX; var right = $PreviewBlock.position().left + $PreviewBlock.width(); newPos = newPos < 150 ? 150 : newPos; newPos = newPos > right - 150 ? right - 150 : newPos; splitterMove(newPos - splitStartPos); splitStartPos = newPos; } function splitterMouseUp(e) { $ModalBody.css('cursor', ''); $FilterInput.css('cursor', ''); $(document).unbind("mousemove", splitterMouseMove).unbind("mouseup", splitterMouseUp); splitterSave(); } function splitterMove(delta) { $FilterBlock.css('width', parseInt($FilterBlock.css('width')) + delta); $PreviewBlock.css('left', parseInt($PreviewBlock.css('left')) + delta); $Splitter.css('left', parseInt($Splitter.css('left')) + delta); } function splitterSave() { if (!UISettings.miniTheme) { splitRatio = parseInt($FilterBlock.css('width')) / $(window).width(); UISettings.write('$FeedFilterDialog_SplitRatio', splitRatio); } } function splitterRestore() { if (!UISettings.miniTheme) { var oldSplitRatio = parseInt($FilterBlock.css('width')) / $(window).width(); splitRatio = UISettings.read('$FeedFilterDialog_SplitRatio', oldSplitRatio); windowResized(); } } function windowResized() { if (!UISettings.miniTheme) { var oldWidth = parseInt($FilterBlock.css('width')); var winWidth = $(window).width(); var newWidth = Math.round(winWidth * splitRatio); var right = winWidth - 30; newWidth = newWidth > right - 150 ? right - 150 : newWidth; newWidth = newWidth < 150 ? 150 : newWidth; splitterMove(newWidth - oldWidth); } } /*** LINE SELECTION ***/ this.selectRule = function(rule) { selectTextareaLine($FilterInput[0], rule); } function selectTextareaLine(tarea, lineNum) { lineNum--; // array starts at 0 var lines = tarea.value.split("\n"); // calculate start/end var startPos = 0, endPos = tarea.value.length; for (var x = 0; x < lines.length; x++) { if (x == lineNum) { break; } startPos += (lines[x].length+1); } var endPos = lines[lineNum].length+startPos; if (typeof(tarea.selectionStart) != "undefined") { tarea.focus(); tarea.selectionStart = startPos; tarea.selectionEnd = endPos; } } /*** LINE NUMBERS ***/ // Idea and portions of code from LinedTextArea plugin by Alan Williamson // http://files.aw20.net/jquery-linedtextarea/jquery-linedtextarea.html function initLines() { showLines = !UISettings.miniTheme; if (showLines) { lineNo = 1; $FilterInput.scroll(updateLines); } } function updateLines() { if (!UISettings.miniTheme && showLines) { var domTextArea = $FilterInput[0]; var scrollTop = domTextArea.scrollTop; var clientHeight = domTextArea.clientHeight; $FilterNumbers.css('margin-top', (-1*scrollTop) + "px"); lineNo = fillOutLines(scrollTop + clientHeight, lineNo); } } function fillOutLines(h, lineNo) { while ($FilterNumbers.height() - h <= 0) { $FilterNumbers.append("
    " + lineNo + "
    "); lineNo++; } return lineNo; } }(jQuery)); nzbget-12.0+dfsg/webui/history.js000066400000000000000000000370421226450633000170250ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2012-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 907 $ * $Date: 2013-11-14 21:17:45 +0100 (Thu, 14 Nov 2013) $ * */ /* * In this module: * 1) History tab; * 2) Functions for html generation for history, also used from other modules (edit dialog). */ /*** HISTORY TAB AND EDIT HISTORY DIALOG **********************************************/ var History = (new function($) { 'use strict'; // Controls var $HistoryTable; var $HistoryTabBadge; var $HistoryTabBadgeEmpty; var $HistoryRecordsPerPage; // State var history; var notification = null; var updateTabInfo; var curFilter = 'ALL'; var activeTab = false; var showDup = false; this.init = function(options) { updateTabInfo = options.updateTabInfo; $HistoryTable = $('#HistoryTable'); $HistoryTabBadge = $('#HistoryTabBadge'); $HistoryTabBadgeEmpty = $('#HistoryTabBadgeEmpty'); $HistoryRecordsPerPage = $('#HistoryRecordsPerPage'); var recordsPerPage = UISettings.read('HistoryRecordsPerPage', 10); $HistoryRecordsPerPage.val(recordsPerPage); $HistoryTable.fasttable( { filterInput: $('#HistoryTable_filter'), filterClearButton: $("#HistoryTable_clearfilter"), pagerContainer: $('#HistoryTable_pager'), infoContainer: $('#HistoryTable_info'), headerCheck: $('#HistoryTable > thead > tr:first-child'), filterCaseSensitive: false, pageSize: recordsPerPage, maxPages: UISettings.miniTheme ? 1 : 5, pageDots: !UISettings.miniTheme, fillFieldsCallback: fillFieldsCallback, filterCallback: filterCallback, renderCellCallback: renderCellCallback, updateInfoCallback: updateInfo }); $HistoryTable.on('click', 'a', editClick); $HistoryTable.on('click', 'tbody div.check', function(event) { $HistoryTable.fasttable('itemCheckClick', this.parentNode.parentNode, event); }); $HistoryTable.on('click', 'thead div.check', function() { $HistoryTable.fasttable('titleCheckClick') }); $HistoryTable.on('mousedown', Util.disableShiftMouseDown); } this.applyTheme = function() { $HistoryTable.fasttable('setPageSize', UISettings.read('HistoryRecordsPerPage', 10), UISettings.miniTheme ? 1 : 5, !UISettings.miniTheme); } this.show = function() { activeTab = true; this.redraw(); } this.hide = function() { activeTab = false; } this.update = function() { if (!history) { $('#HistoryTable_Category').css('width', DownloadsUI.calcCategoryColumnWidth()); initFilterButtons(); } RPC.call('history', [showDup], loaded); } function loaded(curHistory) { history = curHistory; prepare(); RPC.next(); } function prepare() { for (var j=0, jl=history.length; j < jl; j++) { detectStatus(history[j]); } } function detectStatus(hist) { if (hist.Kind === 'NZB') { if (hist.MarkStatus === 'BAD') { hist.status = 'failure'; hist.FilterKind = 'FAILURE'; } else if (hist.DeleteStatus !== 'NONE') { switch (hist.DeleteStatus) { case 'HEALTH': hist.status = 'deleted-health'; hist.FilterKind = 'FAILURE'; break; case 'MANUAL': hist.status = 'deleted-manual'; hist.FilterKind = 'DELETED'; break; case 'DUPE': hist.status = 'deleted-dupe'; hist.FilterKind = 'DUPE'; break; } } else if (hist.ParStatus == 'FAILURE' || hist.UnpackStatus == 'FAILURE' || hist.MoveStatus == 'FAILURE') { hist.status = 'failure'; hist.FilterKind = 'FAILURE'; } else if (hist.ParStatus == 'MANUAL') { hist.status = 'damaged'; hist.FilterKind = 'FAILURE'; } else if (hist.ParStatus == 'REPAIR_POSSIBLE') { hist.status = 'repairable'; hist.FilterKind = 'FAILURE'; } else if (hist.ParStatus == 'NONE' && hist.UnpackStatus == 'NONE' && (hist.ScriptStatus !== 'FAILURE' || hist.Health < 1000)) { hist.status = hist.Health === 1000 ? 'success' : hist.Health >= hist.CriticalHealth ? 'damaged' : 'failure'; hist.FilterKind = hist.status === 'success' ? 'SUCCESS' : 'FAILURE'; } else { switch (hist.UnpackStatus) { case 'SPACE': hist.status = 'space'; hist.FilterKind = 'FAILURE'; break; case 'PASSWORD': hist.status = 'password'; hist.FilterKind = 'FAILURE'; break; case 'SUCCESS': case 'NONE': switch (hist.ScriptStatus) { case 'SUCCESS': hist.status = 'success'; hist.FilterKind = 'SUCCESS'; break; case 'FAILURE': hist.status = 'pp-failure'; hist.FilterKind = 'FAILURE'; break; case 'UNKNOWN': hist.status = 'unknown'; hist.FilterKind = 'FAILURE'; break; case 'NONE': hist.status = 'success'; hist.FilterKind = 'SUCCESS'; break; } } } } else if (hist.Kind === 'URL') { switch (hist.UrlStatus) { case 'SUCCESS': hist.status = 'success'; hist.FilterKind = 'SUCCESS'; break; case 'FAILURE': hist.status = 'failure'; hist.FilterKind = 'FAILURE'; break; case 'UNKNOWN': hist.status = 'unknown'; hist.FilterKind = 'FAILURE'; break; case 'SCAN_FAILURE': hist.status = 'failure'; hist.FilterKind = 'FAILURE'; break; case 'SCAN_SKIPPED': hist.status = 'skipped'; hist.FilterKind = 'FAILURE'; break; } } else if (hist.Kind === 'DUP') { switch (hist.DupStatus) { case 'SUCCESS': hist.status = 'success'; hist.FilterKind = 'SUCCESS'; break; case 'FAILURE': hist.status = 'failure'; hist.FilterKind = 'FAILURE'; break; case 'DELETED': hist.status = 'deleted-manual'; hist.FilterKind = 'DELETED'; break; case 'DUPE': hist.status = 'deleted-dupe'; hist.FilterKind = 'DUPE'; break; case 'GOOD': hist.status = 'GOOD'; hist.FilterKind = 'SUCCESS'; break; case 'BAD': hist.status = 'failure'; hist.FilterKind = 'FAILURE'; break; case 'UNKNOWN': hist.status = 'unknown'; hist.FilterKind = 'FAILURE'; break; } } } this.redraw = function() { var data = []; for (var i=0; i < history.length; i++) { var hist = history[i]; var kind = hist.Kind; var statustext = hist.status === 'none' ? 'unknown' : hist.status; var size = kind === 'URL' ? '' : Util.formatSizeMB(hist.FileSizeMB); var time = Util.formatDateTime(hist.HistoryTime + UISettings.timeZoneCorrection*60*60); var dupe = DownloadsUI.buildDupeText(hist.DupeKey, hist.DupeScore, hist.DupeMode); var category = ''; var textname = hist.Name; if (kind === 'URL') { textname += ' URL'; } else if (kind === 'DUP') { textname += ' hidden'; } if (kind !== 'DUP') { category = hist.Category; } var item = { id: hist.ID, hist: hist, data: {time: time, size: size}, search: statustext + ' ' + time + ' ' + textname + ' ' + dupe + ' ' + category + ' ' + size }; data.push(item); } $HistoryTable.fasttable('update', data); Util.show($HistoryTabBadge, history.length > 0); Util.show($HistoryTabBadgeEmpty, history.length === 0 && UISettings.miniTheme); } function fillFieldsCallback(item) { var hist = item.hist; var status = HistoryUI.buildStatus(hist.status, ''); var name = '' + Util.textToHtml(Util.formatNZBName(hist.Name)) + ''; var dupe = DownloadsUI.buildDupe(hist.DupeKey, hist.DupeScore, hist.DupeMode); var category = ''; if (hist.Kind !== 'DUP') { var category = Util.textToHtml(hist.Category); } if (hist.Kind === 'URL') { name += ' URL'; } else if (hist.Kind === 'DUP') { name += ' hidden'; } if (!UISettings.miniTheme) { item.fields = ['
    ', status, item.data.time, name + dupe, category, item.data.size]; } else { var info = '
    ' + name + '' + dupe + ' ' + status + ' ' + item.data.time + ''; if (category) { info += ' ' + category + ''; } if (hist.Kind === 'NZB') { info += ' ' + item.data.size + ''; } item.fields = [info]; } } function renderCellCallback(cell, index, item) { if (index === 2) { cell.className = 'text-center'; } else if (index === 5) { cell.className = 'text-right'; } } this.recordsPerPageChange = function() { var val = $HistoryRecordsPerPage.val(); UISettings.write('HistoryRecordsPerPage', val); $HistoryTable.fasttable('setPageSize', val); } function updateInfo(stat) { updateTabInfo($HistoryTabBadge, stat); if (activeTab) { updateFilterButtons(); } } this.deleteClick = function() { var checkedRows = $HistoryTable.fasttable('checkedRows'); if (checkedRows.length == 0) { Notification.show('#Notif_History_Select'); return; } var hasNzb = false; var hasDup = false; var hasFailed = false; for (var i = 0; i < history.length; i++) { var hist = history[i]; if (checkedRows.indexOf(hist.ID) > -1) { hasNzb |= hist.Kind === 'NZB'; hasDup |= hist.Kind === 'DUP'; hasFailed |= hist.ParStatus === 'FAILURE' || hist.UnpackStatus === 'FAILURE'; } } HistoryUI.deleteConfirm(historyDelete, hasNzb, hasDup, hasFailed, true); } function historyDelete(command) { Refresher.pause(); var IDs = $HistoryTable.fasttable('checkedRows'); RPC.call('editqueue', [command, 0, '', [IDs]], function() { notification = '#Notif_History_Deleted'; editCompleted(); }); } function editCompleted() { Refresher.update(); if (notification) { Notification.show(notification); notification = null; } } function editClick(e) { e.preventDefault(); var histid = $(this).attr('histid'); $(this).blur(); var hist = null; // find history object for (var i=0; i' + prefix + status + ''; case 'failure': case 'FAILURE': case 'deleted-health': return '' + prefix + 'failure'; case 'BAD': return '' + prefix + status + ''; case 'unknown': case 'UNKNOWN': return '' + prefix + 'unknown'; case 'repairable': case 'REPAIR_POSSIBLE': return '' + prefix + 'repairable'; case 'manual': case 'MANUAL': case 'damaged': case 'pp-failure': case 'space': case 'password': case 'SPACE': case 'PASSWORD': return '' + prefix + status + ''; case 'deleted-manual': return '' + prefix + 'deleted'; case 'deleted-dupe': case 'edit-deleted-DUPE': return '' + prefix + 'dupe'; case 'edit-deleted-MANUAL': return '' + prefix + 'manual'; case 'edit-deleted-HEALTH': return '' + prefix + 'health'; case 'SCAN_SKIPPED': return '' + prefix + 'skipped'; case 'none': case 'NONE': return '' + prefix + 'none'; default: return '' + prefix + status + ''; } } this.deleteConfirm = function(actionCallback, hasNzb, hasDup, hasFailed, multi) { var dupeCheck = Options.option('DupeCheck') === 'yes'; var cleanupDisk = Options.option('DeleteCleanupDisk') === 'yes'; var dialog = null; function init(_dialog) { dialog = _dialog; if (!multi) { var html = $('#ConfirmDialog_Text').html(); html = html.replace(/records/g, 'record'); $('#ConfirmDialog_Text').html(html); } $('#HistoryDeleteConfirmDialog_Hide', dialog).prop('checked', true); Util.show($('#HistoryDeleteConfirmDialog_Options', dialog), hasNzb && dupeCheck); Util.show($('#HistoryDeleteConfirmDialog_Simple', dialog), !(hasNzb && dupeCheck)); Util.show($('#HistoryDeleteConfirmDialog_DeleteWillCleanup', dialog), hasNzb && hasFailed && cleanupDisk); Util.show($('#HistoryDeleteConfirmDialog_DeleteCanCleanup', dialog), hasNzb && hasFailed && !cleanupDisk); Util.show($('#HistoryDeleteConfirmDialog_DeleteNoCleanup', dialog), !(hasNzb && hasFailed)); Util.show($('#HistoryDeleteConfirmDialog_DupAlert', dialog), !hasNzb && dupeCheck && hasDup); Util.show('#ConfirmDialog_Help', hasNzb && dupeCheck); }; function action() { var hide = $('#HistoryDeleteConfirmDialog_Hide', dialog).is(':checked'); var command = hasNzb && hide ? 'HistoryDelete' : 'HistoryFinalDelete'; actionCallback(command); } ConfirmDialog.showModal('HistoryDeleteConfirmDialog', action, init); } }(jQuery)); nzbget-12.0+dfsg/webui/img/000077500000000000000000000000001226450633000155345ustar00rootroot00000000000000nzbget-12.0+dfsg/webui/img/download-anim-green-2x.png000066400000000000000000000332661226450633000224320ustar00rootroot00000000000000‰PNG  IHDRŒŒ®ÀA>sRGB®ÎébKGDÿÿÿ ½§“ pHYs%%IR$ðtIMEÜ ;+µÅä IDATxÚí½i]×}à÷_Î9÷-Ý › )H¢È’%{<‰5²]ÕX3Iy‰=žÉæÉTÍ—äcò!“T*_’©I*•©©rùCÊ[äESq•c[Š<å‰#ÙÑ´)EwŠI½¼~÷Þ³üóáÿåéÛï5ºF£!÷­ºõ^¿ÞÞò»ÿ}8>Žããø8>Žããø8>Žããø8>Žããø8>ãÀï¥3ñ¿^Ùí›ÓéT޹;`Á|äV~æn‡¿‡ ØçÏŸ§þ4M3÷÷«ªZø!_ºt)í(¹Û!ÂïQ ‚.\Àüà<0B7ýú1²¨áp(/^” ÈÝ Þ¥à¢ÇJÉQJ …£„$¥4÷ïÇ·=ÎÌs?L"’>@‹@*$‘ÜÍ’ïrX¶AÒ¤‡‚ "¸²²‚»A£—P̃e}}]Q¬>D Â3%K¹¡Á»–__¸pUÕ4Mƒ} 2”RwöÁ‘=½ G ‰ž‹êÃS€sC•u” Á»–Î.™N§XÚ!!„‚#Š.--‘¡§B!"XU• Ì‘4ØÿðJHЦi’ÞGD)áØÜÜLˆ(óàQ©³@]Ihðn‘(}µS‚RBÒ—""‚ ˆˆèãØ—,Î9Z$e 8¤„£€HˆHQ ><¥ôQxúàìU]ÝIxðne(ËËË$!ZHŒ‘DŒRÚ”`ÝHͻճmÛÄÌ©¥><©Žª«ž«.G ¼[ÔÏ"PB ª k-+ )%*à ý?úµ1¼/’aí¤‡>žï«DI%<(©1&-gŽwud Á£Ë@) Á”9çXï—dpPD(¥DÆdfUA,}©3Oº„»1Ƙ1…R#•QòÞÇâkiÛ6fÕÔÁ³8G ¥¤ß…Ô Ì ‰™9©cŒIƘ´µµû¶Í"is»¡Á;KÒ/aQ[eŽTá#cL†¥”(.ƒR!¢‡ˆ-"Dt`À2³M)Dä ‡€5˜òÍOsÎA¥øüu ^D¼ˆ´"â  !xñDRJ>«2ODAUU!Q²ÖƦi¢B3OEÝ ƒï,ó¼ ï=Çc. [rÎq)UbŒœ?t.@Q@*"€Í÷«|¿†ˆ¬ˆ0pæ^9Í÷Êi^S8J'` –¸’± eˆ £k¨ýL¡Á-Ø‚ ™àzZÃëí[p-¾oKƒ‚“O_Ó¦”Øv›RÉãEÄÇEfŽ!„À̉™£ÅûPQ·¿õ8?ß‚· ‘º8›”R­àˆHBh3T!¥ÔQ‘V¥M©ªÔ¦1Ƥ½@sWÓsq7›e,Ž1"²ÖZ—퓪%C2$¢Qe@DÃê“þîÃéûñAúNF²Ó ö»õ×é+í³ü\†D%Ì4Æ8EÄ:ß61ÆhÛ¶m™9¤”ÚBEÅÒ®QhÄkn4x°Ìõ†ÁBD6ÆÈD¤^Ï ¥d¬µCpÌ<€ ÇÌ´ò³þ?–síY`9P §aÓ´°Å‰ØÄx»üH¤ûÓêðÃð1Ø µð&ldhˆºÀ!AŽ4Ënдm+ªÊDÎ;—/_>Põdng¼Eí–”.//SÎuné6ç˜J•aQ'Yˆh Ëã¿ã‚¿¯ýD¼YP¦œ`‹¬a+®ÃD&¸®ÂÕø½í_Ã+i êÂXD Ëî!YåûÓ}æ œá8I˲ K0’e ¶’Üã‡ÃŸŸsiŸZÿ=þD$aͪQWA˜á€c TUÅMÓÀx<æÉd5ËŸkž¼~äúÎxI»Å[&“ µmK!FB k­É[B0DdÑ1󪬂JX–q(§–~ÚÿC|¸}`ß/pÍ6áexµyŠŸnŸ£W ŠˆFf5¼¯% ¥Kª)Ê’˜ˆH“•†W`ì‹çíÃéaZ…{ñLß²þн¼ñ›æóq"×`=ƸcÜ‘‰ˆLRJSD¬½÷ˆ4š»*½( îYk“zNéjãÁ27ÞR×5õ="5ns2P ܈ ˜yÄÌãlÔ®Ѳ9 ÷/ÿ\óŸÇÓ~e_/lbc|Ÿ[ÿ]óņ×Ð}L)DŒ9Û¬™fæ«?Wå¹'ΑcSÜZ0öœ<¸ôÙø\½EpÞ´ë¿n~5¬ÉÙH)­ghœiAc:­Bc­ vÎÅ­­­Xö hn) Ì˼x ---±÷ž Èdh¬ˆXkmEDªŠF̬°¬˜“pÿøï7ÿ8òKû%ħìÓ_à5½HOÀ$¥4ÉñŽIJi ¶RJÓ”ÒVþzcœæ¸HÝ;[i5¨Væ… äïÅxÖš'ÌEŠ(æÝ ƒtsê~œªê½øÁö›ümñÔ·.9&"ÈÞ¤”’ª&çÄwxNÎ99{öì{æfl™›fŽWTæ‰Ô&fÞK6v-3[cLŒ1c"Zb戸Ìc<=þ…æÃaÁ)Çø´yfýóæ·ëoâÓ©‘õ|U*(zn¥”ŽiJIa© ‰1Ö)%Íûlƒ%çºÇó}ŸŽcó"¼Z?Î͇æ4ž»£k/иê<| }Šž…ˆ!—Th]ŽˆˆÖwФ”:hƒv9tžÓ<¯éNÓÝž>}Õ+J)áh42s$Kg·ˆˆ3ÆŒ˜y”r+D´ÂŽN®üBódÕß³'Ãñ’}uã·ío×ßà'%ÀºˆlªT‰1nfŠˆuJ©VpbŒ*Þé¤H™ ,Kòc>'}¶‰¼ˆh‰C”$±}Ž^j¿I—Ü)¼O§å}Û ã4pïÁ÷5é[ ³Bs-æ‚Y)(¨íU”&é¤Ìh4‚2¨7Ïk:`æÙ.}UB c …9„À`bŒlUU3qÄÌgYAÄ•åÏù_w·g÷bжÿûÒÆ—ùÒ®¥”6SJ“¬ó·JP2$µêDl½÷MN¶!„6¥Ôæ:Í{D YõxDì—$h™B ¢˜.æäaJShê§ènð¦}>UÚ×û+iX=çê‹ô­ ‰ÚX1WF"’|$fÆ£Xk!«wÀÀÌÛ!eö«–øaé€9}ú4ÆQ½"µ]œs&ç„:)cŒdé2vÎ-iä—‡ŸñŸå ÍGöØ{có7Üo4/à 0ÉêgSÕNa’¡™¦”jDlBµJñÌìED«ä<"n«–ƒY1T4ÆDD스ÔhN))L!¥ä@«ñBþP$ÿ:¼Ý>Eß¶âÃt2í+Ï…§Ò²»OµÏò *UdfÀhzÉQ_±ÖJJI¬µRJƒÁ½÷¢öL¾)[†Àvé‡ÿKCW¥‹AD&"-v¨*"¢%D\!¢ƒÆ¹O7?¸»÷ÿÒþÕÆïØ/¤VÖDd6cŒª‚&ÙF©E¤ƒD“yˆècŒÝ×ÞûõšJï)—b-ÅL)…Üt–bŒ!Û!ÿlÊÞW˜Õ–‹–rJjÄ×OÒ³î$¤w¥3ûyÏéþt/Šþe|#«¦Í™˜á‰ÌÜÙ71FÉ] BDÒ4 ¨-㜓[±eø l—2üß3t9ƨ®´ëœÑPí–./tîý‡þˆ‹»zí×Ý““?°_€MØ,ÕP¾æìocl¬µÞ{ߊH‹ˆ>„ €D‰¹¦V‹¯ûg$¢”ÝíX–ü3Úr¢Ð¥¬š$K€Î@mž¥—!ÇçÒ»öa}<ì_ÁWÓnd; –ëŒEûŸTÊ8ç „ Ãáp›|+¶Ì­³-üŸRÂ¥¥% ÿ3"vq—lèVˆXYk‡,+°<þ™ö?{w/rŠOT·¾hÿPDÖcŒ)¥ìmÆÕÛÑ –Ï€¨á äúÙ¨åÅ™´|@O§¼¯’£()¥­µ E[JN8ó/Ðv@#~(Ý·÷7^°z˜õOš§%Î`Qh²ŒÆ‰³0°Xk%ƨm1BH:çæVl™[Fm—¾tш."v^‘1¦Ê…Oc"k2qü·âÑ ì–ð´{vò{ö÷RJ°)"qBØ€:ÆXQã½o²ÑÚ@WöX‚fïNÌ*$’¡<£^Áú}kmÙ[ººŠˆ1ÆB%u]“ùþlDÉsøzu†Vé¾tÏžßüQrîœiŸáçÔ¸V5ID RVMj‹sî@m¾Iƒ·ïJw†î<é³l—K)G¹ði™™Wø$Ü?ø ÿ3àÒÂxE|Þ¾6ù÷[ K–(›9à¦q•&×ȶ9&² ”™ÍÔxƘÄÌÉ{K ú§~Ï9'D4{ Í8±Öv}ÕADýÐ$7¼•))qØïßs¹Æ89" ÿ½¢½Q¥”IólDcŒô ­n/0êJkÿóh4â#UUÅw‰1šÙ¯ÚYöŒ˜y–GÏÿžŒ “Ší¹/ùïð¥lܪG´¥’EaiÛ¶Q[¥'QbÈ$”Òc2™Ä”RŠ1&ff–ªªºûzZk™%„¼÷Ò¶­ŒF£nU NŒQŒ1 " @ æd¦Ö¬ PºNS3‚ñ~Œ`sšVÛ'ø¢ÄYN C–2’1f®-SFç©¥s³õ0Ý|¹ÆµqžrÃ;c8·²ºÜ\æ±2çâ¹ÝÊä[î;õ_˜'5TºÍ}XÔó)+ìKPˆHÊÑaÖÚmC|ôèOmšËŸÑ2Ó­­-ɽߒïÄ{/Î9Qh4<c„<‚€:HÞéÞ´`׿HÿßéGÌ»ñþ°·¬üRp£Oão|‰ÿ0Çœ*© EDŸ£Öœ qƒåì¿ÛZqWJ˜~ .·†pJ‰ÔØM)qŽ» ˆhc/K̼,"KãÏ„ÏÁé8×C ‰m7³úµØ¤õ"zÛ¥ö5ZÛ4MÛoü²ÖjmH²Ö&f–ÍÍÍ®ÅÔZ+Î9‡bŒ=_xá…tõêU)ϵµ5Y]]Eýç\Ëа{Ó42;”R’¬žfo23Î4¢`¶]ôÔÒL–·èzu!}hoe'¼L§êËOäæ»4­siRŽË$5~½÷:5k‡·t»$LwÅ,--QÛ¶XUyï)פ²µÖä9,œFziËp6œ[œ3'²¦å9Ô_‘Ú+Ú»ã- š×%XJ”ÞH°Öƒ\ºt)ö#Ûý.ˆ,qÒx<æ¶mÁZÛ©§,eˆ0G˜‘™‰ˆ(O–°)%CD®y^<˯Ú†‡öôœ Õàðã[_£¯­Âšuo«ª2!¯S.ªª¢#®¬¬àÖÖ†0KÕ=Wãí7õ¾c²v9Ú´<1Ý("rÆ'"NDÜàýÁ0ΗnoÛ­¯òŸærƒZUPε)¥¶ßRÚ‡Å9µêÌ9—ƒAÇéäÉ“i^!Ñ~**@.]º”Nž<™Æãq É9—¬µi2™tž3§c0Æm¾'"OD^Õ©&Dµ´BDê­/óÿ -íù¹U‘ ˆXÁ¬ëÓå Ó䬓·t:W_--—PÀ@!ººZ]H©såt“sÎä²F†Ùô„™ñûHúð¢?îÿ’¿–¯’iŒq‹ˆ4'Ô@×FŠˆ±,M,%ˆñÜòyñâE‡RU•ê.M§ÓP@£ó_¢B““ž¡¨©Ñr‹©ˆ4þm¹Ÿ£W÷üž g̽roöD» Æ—mÖa%4¥†˜·wá–€ég¨ûT3p»'¦S t. 9±|BNÂ}ó[XqÍÖÓ¯™ÇµÿÛB“+ܼˆø¶mÛÒÀÕA<¥RXJ©rñâÅ[‘,s%ÍÅ‹S)mŒ1ÂÌ¢m¬mÛvžZ‘³ EMM«58e-ÎæŸáãû ¨þ@ú„ˆ8•0:‡™¹ª*SHÔé]s&†â^Jv÷mÃdý½Í;ÊCÈ{ˆˆÖZʆœvôé¤[}<|?àüxCx޾©¢Y¯º,º"† HÌÑÔ˜Cá)Ç@vÀr;F|O§û/^¼.\€¦i4‡ÙøÅ,i™)¥D!„HD¡(§ðY% òE2ð/áëéŠÙ¤Õ°§jÃêÝðÈ£M˜Ù…™”’Ñ‘m¥¹s~T×u*옃‘0 &Gí¼ä¶?!Ê}Òš°`˜ÙЃr~~ü¥þ3ûÕ Lƒˆ]u›ž1Æ °xï£J—=À"p@#0z¿»ME©zÒqªš~ÐD‘D²šõ9yØ•@ŸÇïìõ9¥{ý’]…3Dä28jHUIyÞݪh½¥? ´–·ÅdmíäÓ™rFD,I«sŸÈeûV¼×òƒ6CÔMÔÁ‚e)B©Š˜¹³%æÀË.£3†uçuÌj‘õŽ!„˜R ÖÚ”/„Š™2"â7Ç‹û’|•Cœ„ˆ–™­N Í^ê¶ùÅ7¹)/I ^]ø TU…ÅÐdLi4cMgÃÂA>þ;ô­¢N‹›Ú\þTºè<8˜¬F®Ž½¨ªê¶Âr#hr|Gþ,”2ˆ˜tÞ]¾0–Yõ-¸.WÌdÏÌéa(F°Á;£ØXgs‰·m}išu·Ôm•0jmgµ…ÅDmÊ·ÛæÉUïMï[ô·š¿0QžÚ Hœ¥wÂŽâ¦|ÕJNnó†àXÍû›óTS)etøsœe2C©rUÂ䤢O—ñòžS«p*W5ª“Á:¢4hÕÞ\`ø<0ó<¤r[ˆµ¶›ÈM³ eâÓòà¢ØKÜÀ5D DäÀ‘ÖÊjãYWU¶HºèvPîÍÚ4}Õ¤YïyRÆ£%—]mq¶cBû*ìÙ½†¥`ùþt&‡0ºYÅÚŒ§ŸI ‰jˆ~lí¶I˜Ý _"b&³qÄ'dn* ¾‰oäB¦6—>Æ\±ŸÊSgØêävï`鵄ÞvXnô?Tʨ-Ó4M*¦€ëPhˆZÈÕ],àÛoÓ ûyÕ{äÝÙfìæÍ23õj±çFì÷âVï ÕsêR÷AÑûÌLš¾×Q¦À±’¥ù5/üJjŬǃÎÀUÉ¢ @êºîŠ ‡‡6èx4-SÔ¿hiD*ÕR6~£Žc-û›1Æk¸Ž»ç{®¹©m 8ô³¹£^Ò"=Ø3²Ô†AÌúã_Âr!PW ¯@:Ï¿ÜA³K¨ûºÈ(ú(õE’¦´eT-és.ÔRÊ¥Ûês‹¶•6¡Þ³„_Ž'³tÑ\23.òn­/<4•¤K!º?>+uAt`çæZ’ð ¿‘k:"j%\Ô.?ÈÕlå¹@í)¡xÇ6I—ke0)8ªfsÝJЦ¹<^>ŠHLS™îYÂŒqœëm¨,=ÈF·ëÓM!™t¢qn»(6¬ƒ‘SQ:™rb3…R)Öõ _´¼ó¨úü˵8ª–´ó KXUɳ¨v½ 3€ªlÞÏÒEú›YŽ$0ý«G0œû 6ªrŠ&²ä½¥dÙö¤‹+vŽýrdŽyjiήҎˆØ( BÄfÏÿÐ%«u.»Iç[‡C<#¢-(Ö ÐÂÎa>м•›ÏÊ+¶_5w¾·T——Æ}É;†Á¸ç7Ú%RX2œi^jD/º›‘ÒthïÍ·-¤…P ob±ýLw'Ê^¥Ù:džZªëZ÷B–!ƒn…ŽB>íG° ºè‹Ásˆ"–NCÓ4©g?ÝSŠ:½¯ãBS#íù“JÉQ~'{ÛÞl¸Ë5ÆõCQµTñâìÐm(^DZ"òˆØ’`Úç¿ÔVà2†%ÙV*÷SÊÆÆF:&Ï+Ñ}Ï&ÍÏ‹`·7º¼"eˆGýèÅc¶ÙX¥{]ï]»mžás´{–W›à•=˜Ûõ5m±.–Qòy¿»(¥ràÀÌ“ €˜í’ÙŒ5ÞXð t'céîH’ÝMÇœtôsKåHýP­µ f#Fšcˆuýæ«èyO’ ½JÏQ 5"¶¹†8”}áýxÖm•0š§)Å«BÓ_ëë3Hó¢–èÀ•QÈy윣W#Zä-eõcŒ!„Ð"b‹ˆm.MÝJWérzÆ>qÃ?±íôÜo3ûUqu]‡ya‰2i[äàW%if9‹ØXØ"Ú91+U4u#Ó!·¿” ”µ¨å×Eä®9´=W3ØYUDf¹Ð} ³ÁØœü¾ý?åe÷â®ïû¿µ_×á ˜ÍÊé Éú]9ki¿ÜŒ—¹o`J}\ºgzÕ@ž¹¦Òf{$™àúŽ?8H•ÖiäüiÐIwIçM÷X‚²²²Ò²ŸzŽ£à^—jI“‘UUI]×áB˜"â$¥´! Ö&¿>ø_âGÿ½P]Â){h)òw«ËðáŸ4ŸÿOÓ¯™?˜Í÷݌Ҋˆ¯ªªsã5q«Ÿß¾_í^)ª³1ÛÜâÞBÍ™«¸NoÀönÇad<™Vð®ëfWf¦c¹Wº[Ü9Ñ{eåûÝ‹éEÁ»‹­išdŒIÓéÔ33äåbMîcÒ”P€Ð<é¾Vÿ¥<±ôãþ3“/Û ³tŠ1NdB˜dɲ­ µmë­µ©mÛh­ÕÆ>˜3Ã÷ö¨¤2?RHhÛ6F° i\!Ä+ôÒ\©u>>¢iùÜ=Iš4ë/—I³2c^ÔsàQ—2ÄÓ«>÷S‰¶¢dé0ÍíÁ:àùšˆ¬ál8ȵãµãUY !\‘Ͷm7SJÓº®ë<É"–»#$mÇ­.=¤ÒÖIÈqàŸ±OÏ}«éœVæå~l-ü¡²¬°öDÍ«ç˜3¼ñŽ¿jø–±øfC4Õuí«ª’¦iÚlÔyXq¤º*IDATÒVú¸!"×q(c¼×C×cŒkˆ8 !Lˆ¨nšfª+sÓ_WK´¹¹™æT(Þ6`džZ*½¢\’ ³á¢îqN)ù´†ktÝíˆÇàjz0÷b›ÜˆÅ¹ÜJXrÏÎKÏ÷*ñB³M”Eâ¹@<QšN§~8Fñ!„ÖÓŠÈ4ƸcÜ\þ1yL|62@›ÞûÂVJiÚ4Í4¥ä1A%Œ–³–sozв×hù¾%L¹,uq™UÃW‹ŠÎ^ÞÄe‡'â)4³™ýSl«çèjRU-õ:ø°:’Д tõpœ-cÐu³hÜ#éG·.ÂïÇ':}+¥4 !lÅkcŒ·Öz]ZQvVô‹åwÛy}ÓÀì¹,UQÎOˆÚ1¹È¹Û笻œÃ ö;ÃŒ‘ÝGü‡´€ 3Û™ýËœ‹Êw´³”ÒF¡)¼%<ÊÐô#¿9_¦L7´Që!7¾¥JîÝ|:¼µU"¢&ÏÈiëºnSJÞZu„ÞŠ“pK6Lßð-Äk,ÂÝ1¥Ôæ.¿¶}Ò<9/€gß›¾O·„ä©Uœï—Í;zk–––H¥LÓ4Xì‚;%i­3œwèûW¤ º÷2ƘŒ11¼Ë=ŸåsÄkUUEfÖîÉVUPnþmÛÆ¦iv,Hï÷šß6`æ•;*¥¥**ï‡b©Šò€›[¼B;‚Pø@:y$3W`1ÚŒÅ: B{·U-õß^LæÐÕÓ¢ÿ±(N¤ö_Ï""qε˜Õ“2Æ_l^Ãß !tÍoZp•×ï}·ßz·Åèûµ]nEÂttöëUûjIk=bŒ0)¥º~Ú|eÇ{W}Ò\{°ó­ADc­µ¹å³›@cÔ^ånlšÎkéI™Cƒf·¿½[‡ªõÁ`°íópÎsŒ1èîã*ýù拃¯ ƒn~°µ6câd2ñ¥TYËp8¼)Ûå–¼$­$ë‹Õ¾ZªëZ'V†¬–ZDlÃ_ÙoÒu·¹ãÉ<æf»©Ìì˜ÙiŸ0ç#÷>q–6,"B ¾”éÀ·š!o¨›ãÉÍg0`ÑAŠº~xé³Íá¶ÜŸk]Ó/!„¨ª¨ö¨#Oö#YöR8K6LßÚï«¥B´àcŒ5Ìf¿Ôñyþój©½ŸÏ†`¶Ci§†;f¶*e˜ÙsNHÇWè•ÐàÅÿB[*Ñ[kUÂl“*[[[qÞ|œÝ$Ë^»,nYênÛ6@×J¡›?²¾mšúOÜ—qb}ÿü;þ³0k*0ó ƒcòâL“C眥L§šÔ!èžÉš|…Ïg¿ç?»C΃E{Ó‹¿I::ÅZKDÄUUi#-ÿ|ýŸ‘çËáñêÅ"f£ ÌÄÌ¢ ¨‰PÂr’ʬð^ŽþØÕ«W¯Âêê*Æ1ƈu]ƒsNÇ•ažÛ‹9×C1F4ƨÛl!"™3é^¼/loŸ=™NÊkæ9Yã5È«e´“€™»ê<ÑÕu û²u]oÛá,"WØíúz¬µ¸—s‘$é²mžB¢ ”1¤”F#²Ö²NëšÙø†‘«¶òŸºú/íóKÿÔ?g^Ò~lçœzBñÊ•+Q¥}m¥dyæ™g–›¦{SNŸ>y˜BAï=2³ÞR1P[8 "rx™_©.¤W,žB@söß°[;txq‚ÙÚ™ns^õ¢Ð@ºm[\]]ųgÏbÎ ÝÞH“ðœ?žNŸ>§OŸÞXTX²ÁÞÍѹÿþû‰ˆŒ1†‡Ã¡É« ­ˆf¦êg×ÿG ¤uêŸéû0RšBÓ4±®ë”§wR…ˆºXÙAÂr«ÀÀÙ³g±®ë.IXH™®û‘™©m[4Æ äž_˜õZˆˆ¼>èÝöÏ–ã%jÒ«æU˜õ(iÖ[OMAt+õ9ö¡Q)“Ÿ#ˆœ>}Z·ÆÏ“ {=·ýŽ‚Ò4M'u³ä¥y ÓñxÌ'Oždç‘Æ› "Ú|²ûáæ‚tý¿æo¬ü“ð¿cÔPE\[[ MÓ¤r¤¬†:n‡dÙ70!„Ð\¾|VWW;)“¯tÊRFçÓª¥ß {ÖÌ4"šø¢yÕ} }FqÛ‚>ì“Pc#Æ1ͬÞn¦…ŽªšúÐ8ç°ª*œN§ Wv¾ºáĉ´ººŠsàÙóyáÂ\YYÙJi£”Ƹz=ËËËd­¥ápH<½Ý°%;3`"²¢“¿síón}ðõö÷–~•ˆb^7777}Žþ¦ÜÛËí,7-Šû#Ì.\¸@ׯ_§–Kͽ÷œ7È23Û#ƒ! sKD´dŒ9AD'élx¯û©ÍÿÜöˆòº{½þµñÿ&"!„5؈1n†¶Ú¶íæßéxÓ<±²œÓ%Û4&¡1}ƒKÃp¿GßM.Z€Yï¹B¤nJ Çã1ò,"g˜™gOÃXfžíÀ¶–Ýß¿ö¿6+õðÎüH¼b6±iš¦Aݱ±Ñ4M÷0-ô@aÙ—„Y¤šúRFm•21FUQ ±”<;€e¦vŒ<àÙFór\¶Ë8öÏ™oçB&WÌsé:mæµ»4YòtntáfwEäy2”“²´N8ƒ³mŠÖn’EÁ(¾DaŒ1v)ŒSafî`¼ŠYD4fù#Í#á“×~‰{9üúé}ðˆ¼÷Þ9677÷"]æÆËö»Ÿú¶3GÊÀt:…Á`ÐAÃÌØ¶­@®ÑÍ£Ít~Élè@@‘Ëæyûhúä6WÛ Ùsr!½`žN[Øêî!…E·Âk½m[`f2Æ@³T"5ˆûÕ{ÖZêIž…fii‰ªªÂªªp Rƒe;Î ›R2Î9g­­²¡[‘©ÎÇâ\ûUùò‰ŸOoÒz^ísƒ[ÜMºÌw²¯=H· ˜yÞ@)eT59ç:ŸGÅ>‘Žé~žˆ6y ›ô]zOø°¼ó}­y·|4}Ë=)é¢*À™lïÀDDôÞë4¬NªT+ìœ7:V%OÿÌó¾4¢"âŒÅ"ÎífyyÙ"¢uÎÙÁ`PåÌ•sÎ!¢5çâjúÌÕ_Çöë÷ü“øTõ|^pÀ3ó m—¼‘Dn§:º)`æ@ÓÝž={¶‹uÄq:‚ÀªšDƒ6M#åt$fµmämsÍZ‡‡Â‡Š&¶a¬ìùôqxÉ~jò*´"OÁÈ‘NÊ•'zb¡¦:p §4ViÞ¹@êtuÇÙ¤ÑhÄUU™Á``s² ÎÓÁÒ•rœ ÷ÊO\û•T…“Õ·Nþ·íŸ ÿ4OnhcŒ-ï½oÛ6xïãõë×ïˆírKÀ,‚æòåË [fçÀ˜Ú¶•Á`@êå "dÛt Nü®y“…¦ø°lÛŠ¿a˜óñ“p•_J×h³0œ1‡LuújO½!f¦ªª¸m[)‹Ëc ZkÑ{¿£è|ÑYU‡Cãœck­©ªJgåšÑvÎ9ÝPcœçøcÍûáG¯ÿrªÂ‰êÅ•Qÿþèÿ¯S½÷-3ÇõõuŸ*ʲ]n˜EÐhŽ©o«mc­EÁ¦i ª*u%CÓID„øšyÍ·pÖ`4U²ô^ÿ S!„—Ì+98Û¡˜{›TòÄ1L'!ï½(8MÓlwŸí+Ô¤Ÿ‚Ô?‘‡Ã!çiçF7ÏcØc«ªrÌ\^PÅÌ.—oTƒŸØú™ø}ëÿ½˜8¨^Zþ¥éïŽ%*k›¦iSJm®éõZ^9Oº–ír Àì–cÊÑWT¸È3µs¥æešºÊYo}á_1¯Ràu| |°´i€…ଟ}<&¯™KPSÈ­*ê®kÞŠõC <›Èš å\ÁÇUUq~>åPA.ïë9 f³‡MþÃÌÎ9çŒ1Îã êÖý0WÙrü®xÚ~n㟵n~Sõì‰ÿ¹ýÃåßÑ¥a0ëzl1´më½÷)¥”Ö××¥_“{#Ûån®^½ š˜TÕÔ4 ,%SaˆJÛ¶b­Õy2ÂÌÚÌñ5~¯šç̓ð©âöIœËá¤ù@øwÍ2¸ð‚yY?ÔAe3›œÌ9 Ϻ -g„»š¶ÈK5:@¼÷šÎÐQ솈ŒµÖVUe‰Èåš§ÒÄ3 ¢1f€ˆD¬DÄþýÉš~pýŸÆqû.jìuø³ÿUøêð«ðÎîJ¯l"뺎š/Ê)Ø«ír»€9ʳ^ÈŽô~™6К•c·ßZã!; *˜-ˆ"âÀ³„ˆc"Z¡¬Œþný‹ñ=ÓÇæt]uך¯»ß OU!oEÉ·mí5'yjg^&µ;g~Óœb15¬)‡: ¤Ýš V¾F²‡?Öüh|ßÖ/¦¡¿€¯Œ¾–þï•ÿN6išB#"MÛ¶M.–Ÿ5þy/_¾Ë^¢2•q#ut;FÏX©â¼Eèeµ™fmoMŒÑ¨î·Ö*4c"À‡îãí'ø“ÍϦ•vî h|cðªÿšû­ðmó"¶ºø!o[í>cÐQayã½}¥þÜV­Á@pž_¯6Þ¡J,D¤êS[ŸŠï­ÿa\nî ©ÝàoþyóÇÓG¬61ƺ‹Ð5X˜EÐèz8ÝB»WhˆÈä´•1fCcÌG0 ‹ãêǧŸƒóÍß”*Î*ðÂð›ÍF¿ÐívÖ XÿŒ:@@K( 7F-ljPÁPÕ•aÑQ%("8úÛõgã¹æ?ˆËÍ™™^ èÞ}ÉÿÁÒÿ.5må&³6„ЄêcÛ—,“ÉDæõíÅнk€9hÈcˆH%1ÆhÜb`ŒQhPÀÀž‚SîG¦?›n¿OlÜQrŠ {xÛ¾Šo˜§ýÕWÂuXƒw†ê  ó›{C‘Ê÷‰sÐP=³.„P}ªþ”œo>Wêû»xã•Ñ7Ú?ýóôªùnV èt¨VaiÛ¶ !ļlt[výqo{°î°€¹%hòl²Çcsdt½Ž¡ˆ8fåªß+gªžþT|¨ùh¿Lb[ó¦›BM×q‚Wd^–ËöRø–y6ÖÐh$5€ Ðö‹qÅ÷òá<­¤si”Þƒt& ²Øw&›·‡Ï¦'†ÿÒÿ•}67Æ·!„š<¢Ã×uÝXkcÓ4>ïÜNo¼ñ†¶È.ªHÕWßNX˜}A£‘Sç\×PªªlÛXDXku‹jEDqˆXÑ 9Q}ºþ©ô¾­Ú×Ñpk[3‰5­ã¯B€ `q©’“ì`9YYJ. wxiý¿3±æ¹Ñ/7<ü×ÅæØVgéÖú¶m}¶£¢÷>ZkÓ›o¾rÓÚ\XæÔºšírÛ€¹hbŒ8¹¬'Qi£98frÌCƒ`¶'7¿UˆhÍû£ÕçәðÁtOsê¶½žmº7h¾%¯»¯ú'ª?mR8tƒlKDmJ©ÝÚÚj­µ±®koŒI!„X×uÚÚÚŠó`)K.çì‚:ôU?‡Ù¸šÒåÖb£¢“±kTËLD&KΰXk­Õ¬/":GD,äñ!<–!?¡ÕpŽ–åA¥3<‚SÞÆeûjOoTmÛšµ0Å7i‚ß•5þNxÍ>¿m^ÈÔu/u›w ù|Û¦”|¡Õ…a!„BˆÆ˜´–²ð¨Àr[Ù/4!ôÞ—ŒŠªªJ“d­åìI1ç·,u,Ì6غ¼]Õ ¢ÓšYuws‚³K¢¦‡â}n5Ý/Ki•Ær T`+­ÓëéM~)¾h^Lœöl]¤uQV¡r¼Æz´øIÝåBdæT×u2ƤÍÍÍ´›d¹“.ô¡s#hÊ8MÙŠQª(•6ª¦r©„n{ÓȬADãœ30ëËfc̶õǺJ0×Þ”÷1§#4à(:½¼„CoÓì–›WbJÉ+4 IÛ¶!¥™9N§ÓR %(³±ew,‡ÌÍH…¦/mrmnYj@Î95Œ5o¤ÅÕ¤Ë2ÕëÒпÛtâà.ïƒ1𤀔ÛWò ¿PŒ™ y PšN§A%J±(´›¤YvŒîbàX ˜ýHšœÿØN¿,²/q [‡ôVÒð}.Üê&ujfZŸKVWý±õÛVóÄ3§ó$NUU]®'ƒSÖº”%¡;Æ€”å¢áÍÿ¿ ”©í¡*Ú¶MÞûT®µÉýßÛöíEªì²‚ðÈÀrG€Ù 4šéîÛ6„®ziiI›•¶IU/ÃápÛc¹Õ­µÛàèï2è¯ñ)—9èî&• ý Rz_o÷)UŽ4,w ˜Ð,´mJpTꔽʥº*À€>@ùCÚ–>8ú!©ä(éßê}ÝC´¨Ór?^ÐQ‚厳GhJ§/uÊmï‹n÷Ë<8æRJ…¤ü~jå~l•£Ëfð̵oœš¾ÔQ[g7`özÌf}}}Û:äþÏíC¢ÀÝË‘f¯jJ(%Ž>Ö·uôñr'J–rÊnG Fa Ë<˜úìA¢ÜU°9`ö¡¦v¨*=æIžòû})Ó‡i‹àèÝŸq·K”#Ì>¡ÙñX Тq7zl»=VBÒ“$‹À¸+a9²ÀìžTÚ<‹ ÚïÑ— ½¨ì¾¹[ ¹«€Ù47z %ÐÍs$Èn@ì ÃÝË]ÌMB´Ûë»Ù×-7ù½»’ï `öÍa¼Î¿°ÜõÀLr|/Àð×˜Ã„æ¯ ,ÇÇñq|ÇÇñq|ÇÇñq|ÇÇñq|ÇÇñq|ÇÇ÷ÂñÿK‹‚ÕIEND®B`‚nzbget-12.0+dfsg/webui/img/download-anim-orange-2x.png000066400000000000000000000353741226450633000226070ustar00rootroot00000000000000‰PNG  IHDRŒŒ®ÀA>sRGB®ÎébKGDÿÿÿ ½§“ pHYs%%IR$ðtIMEÜ 9 !_ IDATxÚí½y]éuØwÎù–{ßÒºé™Á fÁlr8$gHQ„hI,Z±X)Ë‘,Ë*“±*²U)ˉ*v˜D²B¹J¶EŠ¢8aôG,Ê–"KNìÐIK ) \<$Ep’³ƒÁ2Øzy˽÷[NþèïÜùúõk ;(|U·ÞëFãõë÷~ïì À­sëÜ:·Î­sëÜ:·Î­sëÜ:·Î­sëÜ:·Î­s-~;ý1ý~¯óßËúÇÁ`À·€¹9`Á+øðåüÌÍ ~A±-0üqšüªª¦þÿ²,7}“:·ßìá·©A€`zCpMÓ\òßo­åÍ€ê÷û pðàAÞ¾Y¡Á›Üì{¹äÈ%†Àáœk¿B˜úø“ßWJM}3óïcøB e’ˆofɃ79,ë ™d!ƈóóó¸h.ËÙ³g™ˆX¾7 ‘$ðôû}N’‡oFhð&eÃ×@Q5UUᤙˆ|/ƸAÊ0ó–^ DäIˆˆ–Íš„'ç¢*ëF‚o"XZ»d0`n‡8çZ8ˆ¹¹9Ê¿! @cÄn·Kr?¿mß±ô³ˆ€1y4E¹ˆ¬”b`ii)æ@åðˆÔÙD]ÝÐàÍ"Q&ÕNJIˆ¼ñHŒcŒ(ßËÁ(Š‚6“2MUU,÷å–ˆ™ˆ˜ˆXš„'—>òõ$8[UW×¼Y`Ù ”;w’ÜwÎÑf€„ˆ™QÀÈ¥€s!µ4 ùZ®º®£R*N¤”Š“ðœ?>N‚#êjÂUç ¼YÔÏf 8çHB$ Xk•ÀB  ŽV¢ÈׯÜäuádE#‡D¢Ä"u'á1ÆÄÍÀ™â]Ý0ÐàËÅ@išFÀÀ•e©”’Æ)ÆHÆÔZ 0˜¾9@›IçQû&zï#"Fçœs€ˆ(6M²¯¹ªª@D¬µŽDÄÖÚx!pn$hðFƒEl•••§iš&QʲT"M¬µ*ÆH9$!¥µV€J)ÅÌ”n1Ýoa’ûévƒÑBˆÀ!FĈˆì½o‰1ú$]âh4 Dˆ("b+iž$eZx.Îììlœb_7hðFƒešT™U“ ˆD1Æè ¥µV1Fbf*ŠÂ$ÕCD¤•RcTJ)jš†¬µšˆ0ûe2mÀÌ,`D¥0s!D" 1Æö~!QH0…‚÷Þ¥T@ĘKGkG£QœÇZsi3Å(æk ^'XpZLE`©ªŠ&Õ÷žÊ²TÞ{òÞ« ‰¢”RZ ‰1ªd“"Ò1F…ˆ53"ê$Mäk•#’EÜnFDŽ1ÆôýcŒˆ™90³g怈™½sÎçàÄ}ŒÑQÇ^)åEêh­CNUU!·qVVV‚RŠË²Œ€¦çZ@ƒ×–iRÅ9Gsss$R¥Óé(ï=9电V…TJŒQ (ÆKD:„ µÖ 3+¥”ADþPˆH 0ˆ¨2u$Ï“cŒ,R%‡cÁÇ3{¥”!8föˆèÀ;ç3»£ü»Ï¤Ž×Z‘8Zë µŽUUDdcL´Ö¶àlEÚ\mhð:ÁÒ†ôsXÄV™”*Î9%êÇ{¯r‰b­µ ” X"²ˆhQ‘F)ebŒU‚£½Ÿ€!À Ît’€’ßfö1FÏÌ\úºI÷]Œ±afsÎÅRÊÇ]X;N)åEU9ç¼R*ZkÃx<Í4u= b¼°L󂚦¡ÙÙY•¶-,"U¼÷*½é*Å"¢%¢‚ˆJ0é~‘î·À‘afeˆí=ýfþîY?¿» »æ¬ß1×ý>ÅÞL'tz†K¥”æÂ`LÕjÄñj£F+cZ=_«•×Gzéµ±:ÿʲ>;ª€€ñà˜cc¬ ½ !ÔÌìäŠ1º'¢ µ1&ˆQ¼ uU¡Ák K±&Yš¦Q“¶Š÷^…´RJ‹ôPÀc f.´Öf.”R%XD,“±»‹0ûþ»V{dg}ßÝ tGG³¹Ü¿yyÅ ^Yѧž[²¯|öxÿ¯i9I—œ÷¾BÄ:ÆXÇkf'pfn¼÷µ@ã½÷ZkODÁãýÚ'$6Mã/Í…$ÍMÌv%‹s޼÷d­Õ!äœÓZkí½7EQØ‚.˲‚ˆJ"*•RH D,öï¨nû¾½Ãw<º§¾¡¯ç®…ú=3ðKÏœ*^üÄÑ™/]Ñg˜¹a抙«cÅÌuº'ÉS9稓Ü(¥BŒ±É¥MÓ4"uâêêj0ÆÄ­@sS3á:ã…l–i°h­uRAšˆLQ6Ù'E²QJ"*µÖè(¥º ”’ˆ:?°oé‰ïÞ7zÇí³jþzF²_:Oüëçû¿pª÷B¡bæš™ÇÌ<!Œ±òÞ±öÞ×P@S×u“윭uÈíf“xÍUƒ¯,S½¡Í`QJ±UBÊSÆuQ° Ž‚ˆzJ©.u4¾oéÝï¿wø3¥êÞHøç¹ÿ÷•þÁßµ÷ã(I›Q‚gÄÌ•snB¨4537IU­ƒÆêºö“T¯×‹×Â{R× (wAXš¦QEQhçœÒZï½""BÐÖÚ"ZcL;Zë¾1¦¯”šQJíPJÍ"âÌ÷ß=xâï?yæG¿=>Th2pƒ™už¸Ý?ôÞ;V9Ø!i"jcCD¤”R¤”‚}FD”l;+¥ÐÞ{°ÖbÓ4 Ðív1eÑ™!Æ'OžäIaþÏÌfÜÐÊÊ m&Yq™‰ÈzïM·Ûí0³5Æô¬µ=­uO)5CD3ˆ8CD3ož¯îûÈ“§ô½÷„·†,Üà§_Pç]wúGžÜ5|àÈÀœ=;VM (Š›ˆHDDÌ D„/Œ123Ã4hÆã1034{÷îÅÍ:-r% ¹bÀXk§ª¢]»vÑh4¢ÍÔÀÒ4IêÈt»]1d»J©õ3Xf s?õÖ3é/¿©úósêÃMvvö°ÿÝw7o½¯W-|õlï5$€(ÐÄA)ˆ(Ñf!l€¦Óéà&ÐL}Ÿ.š«Ì»¥ª*º,Zkˆ¶,Ë’ˆ:DÔM׌1f†ˆf‰hæ;o¯ýé'Îþ§÷.À^Â+kƒÕCåbS;ï#k…xuIBÀ½s°çýw¯¾íÜX-YÕ«)€ˆˆ)Ç%µ7"Í|!hêºæL•ÁÃ? G½¢êI_Íx‹Ø-’ªªj›»Í)¦R$X:’¥3ÿÅ£g¾ÿ=÷„'èßÅ&@¬WÆ4:3¦á¹Z/ŸÒ¹£C{öKÅëËV’ffÔêÑ]õžûgêÛîš »;an¾ 3ý¾îv \[i¦TŸ|çøŸx¹zæ—¾¶ó÷’:R’U;0„€R¯ã½Ï¡áN§£Æã1ÌÎΪ••• YþTó,k„­5á]]/éBñ–••ªªŠœs433£œs”¸Ú9§SŽÇj­K(’­Ò¢”š™Õ~×÷®ÓÚ7‡wl÷ù ¨¿u’ýÇû‡Ÿz½ûjŠÊðYx?æiù;ÒFIµÉËÝeè¸}ôÀ[æë»ïÙåwïèšÞå¾–¯.…Sÿà »k©‚ó°â½_ ! bŒCfŠ+Þ4MÍÌõZxÊ»”ÄôÖÚ Á=kmÏéJºÚx…`™o‡4éYkµ÷^i­ "Š[2s©µîj­{ˆ8£”šUJÍ,vüâGŸ<ùŸÍÎèÙí<·±Çpø8¼ðO-|Jr< ’jWBÊ6‡”Hl3ÒYþˆ’M¡&òO:»5 ßº»¾óï¼méûvö.œ¥‘_ýÈŸìþø©¾ÎÌ«!„•Í€™‡1ƱsNb:@c­õ.Ë2¬¬¬„<°—AsYQ`ua™o¡¹¹9Õ4Ê<"<"ÃÌÆZ[¤ÜO‡ˆºZkev±ãî=§|¶«úÛÅñˆ:üóOÍý«?|­÷LúdS¼c˜â£ã8Æ8Š1Ž`B§ïUr1sM ó×r?…ø›`áäP-âå™§•w¼o‡Ûm5]’º/ î¼éuŸ«FDÄdʬ½Àkõ lÇ£¨¦²,Á9·Ás*Š‚÷ïß¿Áž¹[æ’™âµy"ñм÷¤”R)묛¦ÑÉØ5J)c­-±PJu“*êk­w âÌŽ‚çþ='~|¶Ü,uÀð¥£øŸýâ®ùÇÇËÃc+éS9Š1CrbŒcfÇÇ!¥€Ú{_IÞGr>KÊÿ´à¤0¿K‰F‡ˆáé3öØ'^ê=½ ›Î;`AÐ%@cܾúðg_í}«‰R]W[À•¤DÛÞ2 ÷ž{½žt9€6ÍkºÀ´·‹‹‹(^QgfftÊ:ë¦iD²h¥”!"›â,]­u7äf‰h¶44÷óï<þcó3zçVžËs'ã±øÔ®ùûÇú‡jÏ+Ì<ˆ1cŒ#±’4€HQaì½—üN-0È•J(šB#0³KªÍee!D_:Ý9ò¹WÍó÷vÇ·ížÁ™í¾¾]«Êw/üý£½oF^+4絟´¥¢3ƒ÷žfff êMóš® 0Ól—IUäœ#c 9ç"*ï½BDí½7`ʲ,´Ö¢†úJ©Dœ%¢Ùÿþí§þÚ=»qïŞǰáúã_ë}úcÏÎýÁÀÑyÃ{ßJ%ÆX…*›¦iZiáœkBMÁÉå½wˆèч":fv©HÊ…$±‰<" ÌWÖŸy­÷ü`/¸»ŒÂm½Þý‚:oÝ1Ø÷ÇzßLˆ%6WXsª˜™9j­Ñ{ÏÖÚV5Õu-p"n2ÛUKê2aiY\\Dï=Šë¼cÇ•J´÷^å.tÊYcL¯(оDnqæo¿åìžÜß²…ÄÞÉŸþÜžñÌ9ó2 cŒƒt cŒ#ç\«~bŒ"ÖιJ¤3;¥”‹1zgˆÖjZÚ+„Œ1E‚¸£€ …SIº¸¤6bú¿!½Áñ¹%söŽvž{lat÷Îm+Ï5ß§™{»ã]Ÿ?Õ{!oI!zL‰ l­åE!„Øív±®kN‘ã< |I¶Œº¶Ëdø?7tEºh"RDdÓUZk»)yØ'¢Y¥ÔŽ÷ß5|Û_z¨~?^Ä{ûÂ+øõŸûÒžß{XNvÊÀ{?dæ‘÷~è½§’‚ ¢óÞ‹ ⚦i!ɽ¦äz‹%¥•!Æè­µÌÌѯD"xD ɾÌìÓ'_J9yäÀýû#½oÝÝ©æîÚ ÛyÍÝä}8|®8™T“gæ˜JC‚ÖºUM©³½÷LD<!ÁEQðåØ2êJØ.yø?7t;ŽÊòD†™MQ¥TG)Õ5Æô‰h†ˆfnï†Å¿ûäÊ-ñ½‹Ï¼¢ýÊÓóŸ€3D …!„ç\彯­µ®iš†™DtÎ9$@PJµ-!S® ”Š)>2BÁ3sLÿŸSŠ@E]€”s2"ÂNuÎbcXàÛ·óº?0ï~î,;5Ò« šZ\¤(›¦ ©ë€C\–%4MÃý~|9¶Ìå³.üBÀ¹¹9 ÿ+Dl“ŠÉÐ- °Öv2XfqægÞuò¯/tá‚ENŸ{E=ý«ÏÌ’™%¨µšÜæA2`ÇbÀj­[i’J&=øT?”RAk߯T„Ý^Rg› ˆ©Ï¨•̉(†‚µ¶5BÅ‹á5r‚ôZ§Øõlç伪»÷ÏÃm[¶ð{F÷úèÌa×Ô]’‚‘ˆ¼÷^¤LÈUSŒ1;ç¢ ¸[æ²ÛeRºE¡ÅЕh®1¦ÐZ—ÍM¶Ëì8ÿ¾'ï¼°Ýò•ãø­_<´ð‰ã*3˜¹5ns#f®¼÷•Rªnš¦N% x­µŸÃ{ï3µ“Kü ÌCí}kmÞcŒâêÆB`fvÎ+Ƙ÷d·Ÿê/½^°_í¹cîÜêk_eï팞ì½@ ˆi£µæÔâÒJ™+i˨K4x×¹ÒIQ.]ˆ¨•.Ìl´ÖËséJ4w±ìñÕ6ˆW¼p6¾öѧnûm%Æ8ðÞ`ì½¥8J-ÞN2@×bŒñéôÌÜV×u’;â”#°t:&"Nßc'y/œÀáÔ¬Æ!Nµ-,*ƒ%³˜^»?9Ñ}å;wîßÑÛº!|û˜?¾ §̹º/öSŒ1H¸H™+i˨Ë%WG)©HιÖvADBÐÆ«”jÝhcŒd g~ê-¯ÿÕÛgyÓRÊsC¿ü‘Ïßþë.ð*3¯ÆιáRé£KFlBp oŒñ"M’a›¦‘ô€Ø0mc|jãh¯¼ÿ9®¹S1Æ»Ý.#b+ò'ÁI% Q)…!„¨”‘4IÚP‡ž:Õ9ú½wÞTl#2üÈ®ê®O¼4ótrùCòÈœHLc §žo!ÄI[&ípæÌ™ 53W ˜Íu"]Rö™Ò„ú›µRÊ(¥ ©·UJµ…PÏü‹×ß³™W4öè~ö ?WÑÙ”Wxï!„aŠÒV)°Ö¤¸ˆ×Z{cŒOÅ@°Ö†¦i|Œ1c¢Ö:Zk£µ6.//Gï}tε·rÕuÍιØ4 ÏÎζó^¼÷ì½Þ{q_Y¢­I¢°ÖœslŒÁBΗ¢¨T(¥Æùèž{Ï]á¡­–k”†lkúê™òU$á~BÊÄi¶ "‚µ–' ­®.0âJ‡Ð{©§HÚYEé<_$]­õ Ìü—o{ý¯Î•¸iRñ7Ÿé|úOÏtŸOÆ­xD£L²4ÌÜÔu]‹ 2Æ´E@ !´€Hlzã֚¥mCkÍùe­eD­5 @u]óÌÌ (¥!Ç{ÏÆ)•ä03k­LÉLÑLtrlÇóºîí_غ|×\Üó©—ºO»Mrë}’2¢1fª-“G§©¥‹s©õ0í|¹¼iÞ{ODD©ˆ‡Œ1 TÖÂZÀZ{HñخѾ»çhÓr…ÃÇá¥ß{uöPJ½÷ƒãÐ97N‰Á¥Ôº&0I÷'u•Rœ³Ö®âÓ~r'ƧVU…ùÏH™éÊÊ §NwÜ4 —eÉIª´áyï=¤$cD êy 3›ÿíðŽ?yû§ïÙ=c¶”•ï´zhé»þ÷¯Ï}+f.˜¹€%¡’aŒÙxÜläu«s 3¨KêH%`”1F‡TJHïPGRÐÿÛo:ûƒ }˜ê!Œ<5ÿÃS»cìx%‹ÞcŒ’ûi ©ªª™lü²Öçœ7ÆÄ¢(¢1†———£Öš1\w:îõz¬µ¹>Ož<ÉùuæÌÞ»w/ÊÏEÁDÔŽUUqUU<33#@±µsIƒˆ ”BçÇQ¤Œ”b¦’ bfõêªYúsûüøEÕ´Ø÷»þõ ý¯$÷Ío…<"Æ—‰ÖZ±ÁX)Åu]Ë„¬ ÞÒÕ’0 Ò`nnŽªªÂn·Ku]SªIUÖZc$­µ†ÔóLDv‡ 3÷.ð¾Í÷ß¿`?¿TÁ²”#„FÎ¹ŠˆšL²4Zk'°$H´.Á\¢LŒ»h=È¡C‡Âdd{² "Iœ8;;«ªª‚\=%)ƒJ)ôÞ7ιv6Mš,a@‘=tÚ¾zøÔðØc‹t×V^ÿ^¡Š¸gåíÿ÷KýÏ!b‘ê +¨sM§ÓÑ©Ÿ›dB—÷çççqee›¦Á$U·\·ÝÔû†ÉÚ“Ãe<˜1†ÒÔ'¥”²vÍ4·Ìlÿ“{–ÞmÕté¶2ö«¿õÂŽÏK· ÷>Ï5IÔzQCÓ`‘"km,Ë2öz½8;;â´B¢m>tèP\XXˆ³³³RÕÖÚFÉU÷Zëè½÷ƘvªƒRÊ)¥r®­½µ^¤êc_ÛñG>ný¹½ïÞñc©Ó³›>˜šˆTŒqÝ¥iji³qùW ÈD׺Ys©.£Pc¤²,µÌg£µ.±xÛâøÍ›=ø§^êªTÔ4"¢*ÆX§vÒ¶4·Y&%ˆñ\öuðàAî÷û\–e.Áâp8ôMz^0ô!„vzƒ@“l™¹>¶Jçž=mõ͸c‡^Ø7ãvQ‘¤Œµ~s›€Q2 RFºÉû%bÚÞ…Ëf2C=Ie>­²( ¡™’:jÇn0³ÙSú¹…¾žß¤þ¶úW/ïø3×!„ ç\-bŒ®ªªF‚qÒw<9á@`É¥ÊÁƒ/G²L•4Œ¹´1ưRŠ¥µªª`Œi ñ”’ðù©Á‘‚-h~÷Åî—¶ód~àÞÁÌlEÂdspT§ÓÑÌÜJ™Þ59—8/±½¢6LÒßë¼£‚Ø/HDh­¥dÈIGŸ•±¸{ùÓÇ›~í¸z6M:¨RýŠ4¬{"j§;¥hjH5 1„0–«1â+ûð´ºÿàÁƒpàÀ¨ªŠSö¬µcÄ$ùP)E!rÎ"òˆèÑ‘‹1VDT¦Iùô™òø¹ÁÊ`Wßn©Úð­»Ý½F› ¢1ŠZÒÙ‡soinnކÃaÌì˜+#a6™µáäö‹Åƒ…á À˜ø¿ëT”¨'§*AÂa–qd’!÷Î9)¹ð!„:¯òûòñò¥­>§¹YÝ¿gÖ/ȇ’ˆL¡5ä=)Š‚.W´^Öä#Ù''l—e©ÒH0J’L3³ÙÝÃ=Óëõ¥xæÄP!ÔˆØÄk"j –d° ”"äªHb-b³L®,xŒÖ.Ë’E5Iì'›]ˆ(8çBÁ[kcrƒ¥ ´\õotŸÞÎsúówÞ ip"­µafÒZ‹—й¤¹ÔxÌ%yIbðÎÏÏc®˲l%LjÑ FHDæÑÃMù|õL盵´®®ëFfÃyï½L¡”º™—"£JÅÀ½š°\ š~¿/Ï#Ê„…iR&•EøTÞ)ÝNÚGŽ®è¥óC7Üêóyh¡¾²l2¿/Mm§Ÿçj1|«ªBÙ-uU%ŒÚï÷1{BdŒ¡D÷ºyrO.ŒÜì±~ïèìŸf#¼ácôùÐä\ºÈ<[‘.â Á5Z`5í1§©¦\ÊÈì^ï}HÀ´ãËD¤Šî¥³úÔ–³Ø³jW‚Dœ %!à¦<˜«ÕUÒ¤‡$Ë ²Qí"aP)EùXÓ;{îÎÍb/gj½Œˆ>Å)ù£Oƒ½ G¾t‘7ìJ¸—jÓLª&¥çRFªù1cXjƒ½÷í EðÏž³[v¯;Ísõ3+Dlgˈü\µõÂi%ÐdlíªI˜ ¾f&ö’8ý©ˆˆvwÂÔTÀ‘Us2UÝ7RØ”*öÛOd>Q[kÝV‰tIQÙ×aJöf¿C¤ŒØ2Ù®¶’/Æèý|ç¤Hi’ObºõvDÊäÕSÔÑ–Š×à¬ó˜d¹ÀÜÒßÒ‘‘XÒýÀÌa¹ÆñVéNÍ=xcŠÕ–ÕÌuf]ÝD²Î‰ˆ‘n+ê©í¢uÀà*xÉž²¼Ñ9'3þó…U¼ÙòÎõÈ­lR«–ÒzÙ[ÐŽžWJ…á·,alGYÍ0$éÂÓÖÞpÀL~Úwt¸3í¼÷µØ)RkcŒu]‡|ÛÙä‹/ŸØ)öË s¦©%_¶·å=PI-Ip20sðLõVŸQhdºC¾økR:ç»+o4`Ú­e Ym"aØ8Ì'oËX÷8òÇN±_nØ“{K™í%«þb6©¨Û‰j ŒaËÀ“À’~OÌUú„¸$)M×L$ãtÛ¢ à§|¢l?“ï¥ÄÞ–¤Ù txšZF-,YÈ@`‘NNÞÎkŒˆ(.ºHëBùn4Åœí|èèJK“Éû!„5cÏÁÔÚ¿o¬¼›|±Ó'rn²#Ƹ碖òTD}³…RÊ!bÃã6¥KÐż[Sšëò¦çÏŸ7„„IMá"y©ÖÃéÉ)–}Óí'ršÍr³2YgcåîµxK¹§”¦B8¥”¤ šsµ~}«¿{ä©–Q&Rd&¥ yïõ´ÿ»YJåŠ3ùæÊ×b¼Ê~¡“U¹:Õ›Z³mP&`g&¼¦ë M ÎdnI옴@4Q(Š"J©ƒ÷¾"¢ê“¯Î|Î3mI9Ã/QÅÌ"6iJ…—ü[nø^ª—¹-`$O“‹W‘“kxiAeæ8-jÙÑló(ä4H'ñçpIDAT¶˜$»ájcD›yKI=…‚oš¦AÄ›Tš:zmTœ:ô}åb¿hì±ù?ž]øílf_Œg7ü´°Dž´Írp×V%IfY ®Ì >ÄjŠ+X¤v Ùˆ«ôÚ°vŽD+³<ÈMs¤=W2ØIU­µO…îcŇ0ø¥§wÿæ«ËüÊ…ó“/t~÷d¥O¦†¿QÚ˜R˾I)gÍí—Kñ2·?°/ÓÇyˆ>‹À¶}½ /}2+5¬lp95¢‚R²ŒRze—43cY–mz>ƈóóó-(Û©ç¸Üky³Äø%¢Øívy4yI@:çÆˆ8 !¬F€å¿ÿù;ÿ§þtÿÿ|ñ4>_t>B8½Ì§ž:fÿøüÒÜ?þÝ—ç>+i²… ylbŒ®Ûí¶;³%q{©*I_î'Å9ƒÁ@ÚJÛ·äFR×3s8=0§ïœõ뺭u{ÏÍè•TÖIJ)rΑä¤bŒm&¼ßïcÓ4W¾ß ±˜É×MnG£Q´ÖÆÁ`à´Öc$ï}ú˜H)…’cúÔ‰¹/~òøŽ¯üøƒg¿ïcÏÍÿaò†ÜZí3sCf{ïGö.1³¯ëÚYkcUUÁZËKKKÑ¢Ž¦eù¯¨JÊó#"]$›œÔŠ É´'ÿʪ=2íñÞ¹0ºOT’@£µnmš¼RLTQò¬034¯JîäJ±ÿ¤ä!ûÔDz,Y.ÂZGÁ8MÕ’Ïç™yy¶··ÎÅ—sK1ÆA]׃ãx4U©*h­ÛÝ‘›$m¯[»hyÖ9ÓâïHDQ¦p|}öð´Ç¹o¦Ú'YmcŒÊ (÷˜òÚá¼'jZ=Ç”á×ÕøÃ7WãRX%u2ÃáÐu»]Ç÷¾!TιŒ’ªYeæ¥T‚gÉ9·BXFÄ¡snHDÕx<Ëb®´GRÒm´y¢BñªÃÓÄk„ j)M¨ô1Fwr¤—Wܹ{®¹35»éTb¸¡¼0Æ(=O$Ø1“Ó M+åuËDySƒë÷û!Æèœs1¦5ãœüÍGVõJ‚g5I•UçÜ(Æ8Çã‚3Æøt¤œUæßL©Pä­FË·-aòÈåÅ _)’Îà^;Êç:´Ëªµ™ýføKY§lõȯn·K¹ZšèàÉ€Þˆj)—2bˤ2Îàœkçï9çÄÍ®±~|wó½ðjùï’ª’±²ã¦iFÞûÊZ늢p9,yGh.].´óú’¹Pä27à$?"vŒ÷>ÈÆø$aDt_=ÛûÚ†Oõ¾Û—•fDÔZk“¤ŒJEåëìQK“îuæ-á Mù•šD1!ë_ŠJ©iް)ÕîÏ¡×ÒÞÈq¡"¢º^`× ‡Ã&„ଵa³ŽÐËqèrÿàÜð•P·Ö:dáîBhÒ°›æSÇæM à½sÏèqÙ’&nÊ@ż ¹-f–knnŽDÊTU…ÙŽ ¸^’f³u†ÓÎÙ³góмu•…Þû˜à‰ÖZþ;OVoŽÎw: µö ¢&Æèd܉H•ñx¼aAúd¯ùUfZ¹£P*b5OjI²Kr#àÒŒþ†›ãçaCêîyØ—6Ü[­ukÍûÒŒ¥dš4•‹Zš4~'b2×\=mö;6‹M–kÈkØét ( 0Æ DÑß¼ÛøÅü¿œs2œZ’•^kŠ¢ðù~ë -Fß®ír9¦¥s²^uR-%Ã×{ï]ŒQFuTŸ99spZŠà/Þ}þíi3›ô kDÔEQ˜‚’¦rfFï½ô*·à¤½’“RæšAs¡Ç¾P‡¨õn·KAÒšûç±è*ÿÀÿzø¶ƒÝn7&(BZª–——].UòÅ9,ý~ÿ’l—Ëò’¤’,«ÓÔÒh4òbÇ$µÔ bóG'v<»Úà`çðΕw!¢UJ•J)«”²ˆhBZk­´Ö*ƈÖÚvž3£sŽDʈjš0€¯:4›<&BÚP7Å“›*mºÝ.v:Våj­ÑZ ?ñ¦sÿù°)¾œ&z¥T !äm·A¤Šµ¶y²ɲ•Âù˲a$€·™Z’V‘4`Ùyï+¨˜¹:tÂ~yòñghñ‘Ã; PJ•DTEaµÖF¤ŒÖZ‹”h’:j¥¨¦ œ\—Çä5 ÈÖ ,›å¾fgg±Óéµ–¬µdŒ¡N§#{aW·ùËOè_ !ÄT嬵¡(Š ReÒÀÝŠdÙj—Å¥Û…º«ªÊk/¤ü0ÀÚš˜:m¯þù‹ ¿?öè&_è¹ïÜ`­©¼ÔZ—DTfóNt +k­Š1¶ªI `ç¥-¶-4é>œíJœ‹üìU8 –ɾ®^¯GÞ{ ! µ–ˆHu:Y|N}âäßlXŸú7¯Ì¿’Ål$µÖ, äYè+)YÚ÷zÒdÝ qòäIØ»w/zïÑ{£ÑʲĤ20 JÄ”ë!çš5 N‘qéþîh÷³q]ûì\ç^^Ò/¼^ËY'ÀÚXô(K¦˜Y¶AQí~ ^¯£ÑhÝç#¤v$€ü=ÖZÜʵ™$™eÝ,<Å9·!µB ™™*ŠB>*­Rˆ¨öÌèâ?ÞwêŸ>w¶ó3Oé‘ʼ¢(ÄmÇŽ 2>¶( VJA.Yžzê©+Ë¥Ó¾(‹‹‹˜6€aŒ;23VU…ÆlšåS’†Ê€!ˆê™¥Î«ï»kô]†Þ(G¼vt÷§Î~)¥ÖUÖËDç‡ -ÿ–™³íȺ®kÜ»w/îß¿3p.êö^Dšl€çñǧÅÅE\\\”•ÁíðDiüËniß¾}¤×Žê÷û:­64̬•Rôß>öê?$å?:|ï/Èë`Œ‰!?Âx<ŽÞûuãN”Rm¬ìJÂr¹ÀÀþýûq4µe"e˜¹í~ÔZS]×h­•é‘2ŠB{V¸Ç4Žsþþubßbß²¯Ÿ]î“нdÅTŒ%q °Ö¶3qóå H™”È„#,..ÊÖøi’a«×ºÿ# TUÕJ]ï=‡Cš–0U ª,KEDZ)¥B¤GþÚƒg{hnõ¿ùâé?ñôÙÎÉ õΜ9ãÇãq\^^ŽÆ˜us‡¯†dÙ60MÓl€æèÑ£°wïÞVʤO:13Öu ¢’RCx;ìY2ÓD¤ëûž;Î?Q\· á®þÞ¯œî8UÉ^€cÔZ¯Û˜T]»ü;‡¦,Kìv»8!Ù;è½Gf†……Ú»w/NgË×p×®]ë@õ“¹úä½o#Õ;wî$k-õz=2Æ("Ò2àÙZk’g¨f4Û=tê·Vëâ©_>|ûÇ•RÁ{ïŒ1aeeÅÉȶÜ[ËÕ,—,Š'G˜8p€Îœ9#Ff»Ô\vT§¥æÆ{¯z½^‡ˆ:eYö‰¨oŒÙ¡”š{tçpÿõ¶3?©i=À'Wãñ¿÷Å{þçãª÷~Vsçܨ®ë1¬mz•§Ïö´Õl’lË Â¼Ì 7 ·{&ÝäܨÍl·?„€³³³˜>8Zk­@cŒÖÚ€¶Öª>þÊ/ÍÕ;ékw}Ï+ãîëñx\#¢?þ|]UUØÂ´Ð+ ˶$ÌfªiRʈ-“êcÀƒÞ{ÐZSŒ$–’Ƨ#¨Ó•ï1M±oÎß·Îgî,ƽÿpº÷œ¬âMÒ&*¥À{/[Æ8×ë$s˜RÂ;‡Ãv¦ 3CÚó„¹½³•k4Q>²ª*Š1¢s®•&…¶Ö’Ä‹z½E¡²É\ÚcŠ¢(’:Òyó‘.v‡?úÍsý|úÄ®o$éê ¬®®:QÏ“ÒEk=)YàJÂrE€¹˜-“lLO!õþbêWk­×Hz¶wô»Wž-Öoe»c6îuο°Úy=©!ÙÃ,-,«§IhŒ1”Wèu:,Š‚1LeIX.¦]I浤rP¢ÏbÌæ ¤Øê÷û´&H´Ê’¬6ÍØ5ÖZý¡ûN~Ç›æW?z¦ê}úžÝ÷kÒÜVU•CD?Ýx<ŽçΓ;ˆ‘Ûï÷yŠAÅ`¹ÀljËt»];*¶ ôz=B€4™ª­ÙEDRJ©ÿpº÷ì{o_yW¡±È½¦v5[Õ/œ™ÁZµ·ÐHïyZ)™!Üvh­É9·aˆc§ÓÁN§ƒƒÁ uws[gÚ•¥%Öy;HQT–å:xRLE)¥Ä]Ö`:NV)eµÖæ}wœ½ï{ï8÷±šÍ©Ÿ}æž«ëàÑ×u튢ð+++[‘.SãeÛÝO}U™”2Ì ÃáºÝ®~`ŒÁ´vCíÛd #"Bˆ_^-^|ÇžêÉÜÕÖôØîæ±gΔ‡—Õ4i=M»fÆu]ƒÖšŒ1­=!S˜š¦É©Ÿ"qäVšvíܹ“´Í‘ÇvΩn·KÉŽÓ½^Ïĵ];EZšZ‘þŽ]ƒ;~èþÓøÏïþÑ#ËÅJ/ëdú…¤Ë”q'ÛÚƒtÕ€™æ-äRFT“Ø2ÌK/¢t€Öº4§Mt®)Æ+c8ñØîêm„oD¢-±yrÏð­_85{¨òè‰%8‡ˆ÷PÀHC•1_C#?;Y_#÷»Ý.–e¹áJsœ”.q–‘ù²Q7ÅŸôÎ; "š¢(L·Û-ˆÈcв,-"š·Î÷|è¡ã¿aˆ{Ÿ}m×OüÁ‰]/¦ ¸RꢶKÚHÂWS]0S io÷ïß©ÂR QMÌ ½^G£§7˜Òº|uÔ9Ï ¯<´Ð¼9ßRVh(Þ³¸òö§O÷žíÒüĵWÓ"+PJA*ºâ¤šHŒï|x£1µÖbãH½ zïiÚµ‰Ôã½÷ªÛíR¿ßWÝnWw»]£µVÌlºÝ®5ƈ(喈죳ÃÝ~äįèçž>3ûÓ¿ñòâçS·bã½oˆÈ×uíªªòMÓ„3gÎ\Ûå²€Ù š£G‚l™4€“EQ@UUÜëõ(Mg€dhB2b‘™áùA÷´e?~`—{$_ñWh(ß}ûðÉSCuäøÈÒ´qLã¹ÈCé1Ð##`Á9‡Zkêv»ª®kžØ¼‚I…¡µëºÆÉ²ÐÍ®N§£z½ž.ËRE¡Ë²”!âÙ²,e@aŒ±ˆhÑü…»Î>ô#ûOÿZ~ÇË˽_ýåoÜùÿ¤!‰Þ9×4MÓ(¥Â¹sçÜx<ŽJ)¾^¶Ëe³4’cš4€Å¶)Š™Çã1t:pÎIvÒ®D™–Ï.w_ëF×Ü¿Ë?œCc˜wì?±S9øÚ¹Þ«©c’ˆHê€I$O‹¢ ™.Y×5 8ãñxÝö¹/© iò""Õï÷UY–Jâ&éµT©ØËt:«µ.´ÖV)%+ m2r‹Ÿ|äµ~÷âÊÏ å+ËÝý£gîúõ´ƒ ÇM¡QJ…¦iÜêêj°ÖÆiÒåZÙ.W˜ å˜´Ö ñ QM¶Ö&!ÀhŒiÁu¦”Z å2ó×WúÇ(„•ý;›7å6 !нsîÁwïY~ä›ç»Ï‚ñ©¬ed½Ôk­ U·Ûm»¼÷h­U©Au»]¨µÎs_Jr`ÒÉ@Dò³Š™epµÖZÛ¢(¬1Æj­-3²îGk]H}ÏþÙjþ¿~üµ_¸«7üÅÃgfþɯ<·ïwbŒM]×ÍZÜÏ5ˆè«ªru]Çc%ñBJ„K^œ2ŽhMäaúZz§ÚYÄÉkS29ôƒûN¼÷±Åчf´»  Šzõ«§z¿øÏ^\ülÚ'P§¥§ë`‘¬ôËf3hd=ÜÊÊ IMÈV QJé²,Ë$â;Ð1Æt± e¡±÷·<þƒo^ôßi)N*ðò²zöç¾|×ÇQv;»‚`²+ûÚ“C“Æá£€!ª+R—æßÚòî©~`F»€ÀN®ÚOÿÊ7ïþ_V¼…œÖºiš¦vÎUÉ}^'YVVVxZ/ÑV Ý›˜Ë…Æ{OÆ­ÖöÝèJ·Éx,1M ”wt㮿ñÀ‰¿rßB|\cÜPrÚDrçFñØóç{‡ÿÝkóOÔ2¤á°6y<0éûR´•EÂõÂf ˜”Òh‡|ð¾Sï}ëmãœÕÍ¢¼‘g†ök¿ùÊž_üúùÞ‰´ç±N B›£ÄZ\UUMÓ4¡(Š(°L+·Ü ,7+0—M”R:\­uQE™ÜÓNŒÑj­»ˆX@qÏl³ð£÷¼þC÷Íû·N–IägèÕ¸ñqieD¯¿^Ù£/ ºÏ<9û­aÀ:I™(ƒ¥@ëmÅú»W÷?Ø=°»tûfº|{¡x¡TqFclï¹Æ~ëÓGwþÓÏœØñ-fn”Rs®€šˆœsÎF£º(Š0œ÷ÞE9" ,øªˆ7«¯¾š°\ `¶Ô¸–e©ôÚ/Z³G•´™ÖZ“ìš"Û¤Z2³%¢bOÇïøë÷Ÿü¡Gö¸wo+¦©©5l ® Çx®‰´ª1Ú^æ ¨m¡o)v,E{¡Çµzèõþ¯ý³ÿPŒm‘,iP¡ !4u]·öJÓ4ÁZ_{í5Ÿ÷[OÂ2¥ÖåšÙ.W ˜K&)(MœR)$¯¬µJ²¼k£c´ÁLNj‚+ˆÈ¼kÏêýî¶Õï¾s‡{Ó¬»®Ö è™üÀë“Køæ7Wg?÷oíúrí×à@D/ |J©&„Ð ƒÆZF£‘3ÆÄ¦iÂh4Š«««a,yÉå”]P×|ÕϵìÜMîrK±QÓ4m2OVæ†ÈZ«”RÚ{¯R•š±ÖJÁ´€cf–óf‡wÞ6¸ïž^µoWáïÜiÝBiõ.kãLG…b+OU]7j¹azi¤OœªŠ—¾9ì~ë 'f^NãM}¶MÎ%I∨Yë7sMjè Î9™Ð7ƒ%¯¼Q`¹ªÀlšÔæšw0¶*ªÛí’ô‰´p¬µFªÖ˜ÙÈÊãT d•RÖÆˆ(q‹eŸ`IQ½i×è¶{:ÍâB§Ù³Ã†…BqQ©íñ#ÃâÈ¡³ýWÎ7j,6N’Ä`^«ßŽ™›ä‰9™‹#ÅOÌì'A1ÆÄlFÌTÉr=]èkÌÅ Éã4y+F®¢òÒ‘8Ò*kŒQb#¢$þLšdµnýqZ6¥RŽIÖ ¢¬‰I·cääùˆÜzQqÍŸnI1'ÐdøB0Æ„Á`àC>à±e7 ,טK‘4“SDÚÈ0!(È$ÒFN²sH–ect*qÑ!y¾iÓÒ2gX‚y"Qd;3çœÌ¿ YyÐZÇÁ`àE¢d‹B×MÒL~ú… Ü–kÌv$MÊlGl›¼Ä2—8RÌd­¥$}H½q(_\*—„øó¡ÒSÆÖ¯[ÍBˆ©^6Ç"â°Ö¾´ÖÑ9꺎Š šS6Õ^¹‘a¹¦À\ €µ*›©¨¼M#'_©›KVU…ý~ŸbŒ”’y“ DÓž¯ ~ñ4J–Óðj™å×NÂ\ZZj¥É`0¼|1©²‰ ºá`¹æÀ\š-I€µŸiGàX['(c3B”ˆ”ÿÎ\å墢ŽÚERN$Y’U×uÌ×Úä?#plEª\`á Ëuf+ÐH¦{Ò¶I ´RfnnŽä{9çó• Þ-“´^Òr6X96P×ïã»%YÄ´[ÒŠ´q©¤íeÚ8Ôi§¤ëØ$Õ•’º9OKš•Ć˜Y“™­4³GÍì93ë4O§ûýQ÷|SJúî&IC¼¨ÝÃEmjl_Òõjw…sýQ%ÿK ƒ™àn0ÄÊ yÙrPÉ39=’nOJÌ,of7›Y»U§ÝÍŸOxßurQ‹à¡àö‰9uÖîUêÜÚ//›±ÖyÒຈ÷þàq^/Ÿ–IÚ'iqÿ•¦Æ:hãP' Vö p—ªÛxÍl‘™í4³ífvk•Ë\ï–i3³…u¼Ÿ¬à"§GR>ý?ÃÌžp;Íìq3{ÐÌnw?dôúž7³\ìrQ›–Ó¡›¶Kj­£v·«ò—ÕÌ“•2Ûë`X¦Þ»Rº$-ª³ã?Íçy¼¨Û ý¨i|ji¶Ð̺BA»u–¹!4‡™]Y‡ûH³¢ƒþôDúQ(k·ÍïóeæÏ»çÛBÙ¼‹Rxœ§ñ¢¶Y#‹îIM)ØžRýÔä­fŸ®—`N5ûøøï KÕ·äP=y ðà@}ÐOÅÅ™]邳QÖ•Yf•™õDÌÐÌfÖÙ>òÈûH·¤1ïÿ®ß|kÍ,Wå²97°ÿg¤ì8OëEíÍò²ËGâ¾KɈ(Üžûêüý®ÖyÒþÞŸöoTpן:•þ o=/wkdƒ»õá^ï>)é/$fS€4|ÐOK€wk…:«ëBó__&¸ëÛ\GûÇ¢ ûÈ}1ïû|¨,ê2ó=cfÏ °žU¡r ùçQ_P,Tº¼wÈ«!¹XgŸ¶ñ|yeInOêi1bº¡Žßïj'íïýiðÜ­— o=xWiäîâØW'çËz6U}~‹M’þA?-ÞjÔZçæ]Q!¸kfv°Nö¼¼e°4Ǽïo fî0Ÿ™™UXW0“÷öçiÌàí”ô¼¤inz~˜Úþ¬¤Én›)—Ÿö:ãxk;'¤-À»T^ö}5mïLññ@ù^õZ‡û™Û'Jb€Y’úi ð®²ê¸›ö /^O=×aFy7©wa¿˜ÍfIÿ Ÿ¦AÖVÛðXW'ûÅ |ëVÅ|`&WjÃw}…y+xk]g/èÓàõ3Sý/#nD{{$ù_êÜq\¤í¼¿Y^@¼žÞïj'íïýiû 0˜à®?u(}A^¼ÔaF7Õ7{÷[l†ú©º=s‚¼÷ÖÑ~±±Â¾±,ýýu×o]•²mkð6ºõ™™}=¡Çy=eð§äeà.sðÕ¤³TÞ—÷§(R©Ý_Oéyoõû@Z>\£ÁwƒAÞuÖ÷iFf”óêÜ5÷¹!˦qø»]#3ÈÄ6·þd]Õ >È{_í7++ôý éë6×w«˜·ª¯›w£›½-eôi𚤧%µÊ rW:7n——ÉÞ,é©”Bªéç[Sxn#À[Û>ôïpw“äm’´g˜Ïým)Øï©ÃŒö ¿ïýïçÜs6x½ÔÂÁÑä“wU[{÷Á:ÚgÆW¸(ì–ôJB?wV›y]c€÷^7{g’v{àõ§Gûz¹Lõê n}]éËt«¶4Å’”ßðÖ¶$9ÀK€³¾ÜQê¹s=¼ïU—¼À®?íå2¤é]j?ô™ÙÃUw7§`?h­áBý ý}O‚ú¸êºÉöM¡y×ÕŽá¹ ÖÌû¥JO€·ÛµçJI+ä•^Xšç^÷øI³\¤[õàõ³˜ëá=±ÖyêåóÁx¥S=´(Qá©÷:Ìx¥ %íTß2U~fi†÷q Òô.•úÌl¥™õÔÁ{KÂ/îº%í“4¿Â¼ ÔwÔà¨z¤Í êç×뫘÷ÑúÿéмëÝã =Ôš•¹Dé ð®twþÝ ~ð¥Û¿op¿ßâž?(/c}ÅÇFZߺ•¾”xkÛð¦C½7 rK?©rŠlÿ¾ð…y_øÂæ%|?ØhÓ»¿QýK3<É%$HâEL]xÜõÝœÀþŸ¦¾e::%-.3oNÒÖ }½:a}½5*wֻɭwkž ’ÚÞ[$MQÿÛ¶ï -{kèù=n¹Õª¯ïMuôžXëԨެƟsÚKŒ?’ô yƒ¬M =w—ûùeI·©’)òjò¾S^ûÚ:Ø^™ÀãѶV8·$ê.…ý’f'íýú¾¤Jz\Rcó—t­¤%¸ÍMî3_ã0­o¼¤‡ÝþŸ4ó%é _øÂ…¡ÇÃÛædJ÷ÿÿíÞÃM½e¾.é¯%Uß/uþPÒkn>ãÔ@}ÊÆðoZ#éЭ{¿¤Ï&¥s†ÜõÝofIÉb½WÑ™7yIIº1ðØUZ}VR1aÇä÷0+†i+DßMè¹êîÂ¥Úé ?7U^ðv£¤9eæ¹KÞ ‘åjLÏ‘´AÒWÜašýk’Îí¨è3’ŽÓºNKú-ÜMª'åyOW˜ï¸;ïÿ(áí=.ij™÷µjD-7›Ý(q2îzèšÀµšŸ©Û¨Þ ®?¸èšÀú[Íly•e6WY®!î·.ç4p-Ýà­éÓäeõ4ß í÷F3Ûçúl»™å‡¸¾¼[¹õ6&ô8¿¦Æe¯QrK4lppOhxnÑÝ ïK’G”Î ;Õ7ë¿^Þïj'iæËËÆʾïP˜öÏ=i-Ñ´TåŽìTújo³À7F}KQ”B}þl"Ö¹éh Ùî*‚¶·¹yï¯bÞÎ4û. O@«[Þ SIíûÛývÏ×uOxIÙÅlÚjð¶É î>2Ìí@^ׯYݘÄC#bêRù ç´¿ßÕ:O-Ðàƒ¼Ý’–ÕÉçžz îEy»TÁ]öÜèçÓî翱Y@š?䦣½Y—åÜšÿž óïLHÓWªºtšÖ%¼ïóÁÖ}mf7Ö±u¨ÙÀ1½˜MS€w£¤ÉòJ.ŒÄà’kÝúŸÐÐʾÄiX¡ô«÷AÖäM[p·Úóa=X¦Þ o=wÙê›ÿž½H^¹¹“’ž‘—Ý Ú¹éh ÙB3ë,¬½¥Ì2kËÌßef×$¨ù3åe2&ˆÕ®Œ¢nfÓÌì` ×™Y®Êesn~ßA3›–ðã¼ÖÛ¬—&ô¼°R#Üõ§RtÞ¯‡ã$¼RmAÞnI×ÕÙçž4ô÷*IGè¼·/áç¾zÙP‡êâí™-ŠòÞ\a™;Â¥Ìlq›ß(oäçZ/äV§¨ÿ„‚¼ÛÌìúr™¸.ó÷z7Ÿêî.HÁq¾¤Æe—$ô¼ð¨F6ÀûhJÎûx¿«yž¤[¨ÊAÞnIËëðsOú{÷ŸûÚÙ€d}ÈMWC½LÞçÌl§™­¬r™›]`ïi3›ŸðMpƒ¼Û0«¹°ÙªdÞz>P_N3³ç#‚ö›Ùƒfv»ûùXÄ—Ï'4s·–‹Ù4•h@uûC›R¥? ÇA­ó¤ÁB•/á“æànµçô·¯Þßx¯@]]è }fIÚVÅ…M*kñ¹ÌÜÕfÖnÕiwóçS|œà­Ïó~»<€â0µÎ“QAÞn¥¿s5ç¹Æ”·/ïuH™}e>Øv°iR«IÒ#\Ô<ö `fMf¶Òeë>çj+û5–Ÿs¯4³¦”4y¸nÙmçðI…6Õ^ª#­ÇÁ¾AÌ“&‹ÔäíQ} ¶Wé|Ø¡dßÁB€wx¶Q£€Y)iOèCív¥ûöLxnTßÿIë$åÙ4©s†ä=(éf6%ìúÐ9ï5÷X­ó¤Í"wþ_Q'ûÁ²·»S°Fj€µà6JºJï‡G•²2U’a©¼/êG"¸»SÞ†I—ö ?P— …ÂŒB¡ps¡Pȱ5bmº¤û$凰޼¤Õ’6Iê’ÔíÖ YKÚì¦Åç`9ˆ3[lf›Ý´x¤—‹A{§›Ù}f–Â:òf¶ÚÌ6™Y—™u›ŸÿÒ¢P(Ì,  …‚ …‡Ù"±5]ÒI&éÑA®c†¤mnþôŒ¤&6/8ûÇñ¾s°Ä‚™í³^ûFz¹×¹­ÓÍlû›ä:f˜Ù6ëë3ãóˆõ¡KÍìz3û}3»ËÌ~«šoéÍì«föÞ¸¶«P(ä …ÂÚB¡°Çc3m­3W(¶†æ¹Ÿ½(v‚Á]šRã:fI:¨þÁÝÆm‡¼¤µÛb¸¦=nýùzÜÉ̬É3Ÿ0³;9ìâße¡i¤—€¸¼_õ1Ò˽Ží w}Sj\Ç,3;ÜmdOqü”5³¿6³ff¥R©Ïð´™½;bù¿tËý_qmc¡PX7„À®?õøe\æî¢B¡0«P(ì Í·"¦›á)L`ÏŸžŠi»%=ú[k¹µ°IÒîÐòO+:¸Û(iIL·ÃºîZ—Àsà 3Ûmf‹¹üÌpvï,±â—U0I~·…¦ñöëž2Ë£lâzŽ{ÊFÖS1n{ÞÌÖFú†Ë·þ|LÛï—U03ëp…¯ÐÆž¨åBÅ®lƒ™5º/]ƒj)IÑä>„¯…˼ÖÎ6àõüðso(¨[ xK½ü7¿4³óÝò |èùݸ¶3PFa(Ó:·.?¨ÛU(–º`¯Ÿ¼§P(Léfب‘ ìmŒñ®ž—´RÒƒò2q;%-¬rÙp`´KÑeåÕæ}.¦ÛÀÏ@Þ&iNè¹%î¹nI3Ë,¿À¹z$]zn†¼ ·¹×IÒ9p™™õ˜Yg8³Ç̦¸‹¹{X~¹[6h{ ƒ:O)¹ö…Žá¼úf²ï‘4¹Šs\sÄr­¡y’R¶a‘¤’¶KºµÊe®wË´ÕpþD<ÎG8À»1Æm_gçÆº˜¶_èï̇‚Ý{ÌlríkŽX®54Ͼ¶?of+ÍìA—‰Ûif ¹ïtE•epÁÝMfögðz}èyÅwKÁÀn”pعÅ_Þýþ;qmk(P{ÍÖÎØí. × …é…Bá™wÑëJIíò2ùªù¿Sý3ùÂÒFõfHÇ5ÀéÿýQYª«Õ¼ž?@@ÈðFm·)Šémëî"m½™ÝdfKÍlŽ{|Ž™u¸ó×õ¡ešÍìy÷ÜíëÌù_…tšÙ51i÷°JòÛ]hZ!i©¼`ì>÷ï[T9À{CÄr«•¼² ݱ^Kæý ¡ù;Ôÿ‹ ŽŸuýÛë·ùçþÀsKÜsÝf6³Ìò Ü—€=fveè¹îK@3³ƒ1mØ ÷>¸ÏMKýÏóܱÜê$½O˜Ù•fÖîÞ÷V1ÿÎp&sÄ>Ôø2õ` ÛÜQbb$²Ø©G Àëôf?*¸ Fl_6³ÿÛÏÎ Ì?ÛÌî /ÌîMR€wë˜S&¸§P(ܘî_¯‘ÉÞmKØa ò.¨0oOD{÷©7ÓµQ^=^ÿ¹8x{$åÜﻇ©ï7…^#ŽÞÇ*\œtÙ=«Íì:3›¸¥õY3Ë…Ö79pQ^Ï‚µ›oßý2˜…£z3ñWÉË꯴¯wË O®°\ÜÏ}ª­¼Êª2çÁƒ*ŸñZá\·[å¿Ô Ë%ü³ßú î´Å¼Ý¾EÏ­dfÎ/³ü¢@€waÄóSâ|ŽŒè¯.3»Ñș٪ˆ»P¢t»@ðä–KÀq ò.¨0oOÄvØçà‚»Ï?Ä´Í7—iËpè2³¯Ûý±ˆlÜ߬rÙ o¸Nï‹k›‡à- WPæ¡»P(\—îoÓÈx“8šü|èè¨p¿s€6ß!¯$Cx𵸹:z e ¯3Žç½åföÜ`n=v²3Üz–©å¸»\XŒ.ì ðž›)檶Ú×+:¸¬;W9y_äUê¯.I˱þ¼¤Q úì×6Bž}1o·Ÿy™s¿ï¦vo ŸkcÜþs&!ÇÂ|àí(Ø^ó” òÞñ™â™¶õˆ^ìÛÃi·Û–ù¨;Àȿѯh©5,?i€Ø>×v%À[!¸ÛU(–&hxt„zHT佯ÆmqK̃\ó%-æþ_&ïvõظÜm¥Ïòbæ Ëb {¾Ö‘¹“xaïÖyCD€¤-|ÛjL÷}¼Õ<ý ï ÜMBÍíŽ*û¬S^-ñj|]ÒtIS• föèÅô6ļÝgîóbx1n?ÞþÛ¥b×Ìù·Ä°î3Ê47=?L]ý¬»›išÇWÙœÛ7ù‰š¹~Tv~ Ë¿/ªfo ÀûßãÚöÁx …ÂüB¡Ð>@p÷šB¡/ I©E¸VÕ –¶O½·/ß\Å2w&øÐXà‚튮)¹º†ÀÎnEÀ–´ Wx;ÌOQ€Ë?Ÿ-.Sf¡VϘYs=\Ø»uv”y:Ιœx{­ª² Ë+I‘Ô/´|í5ôÛfI™*Ö¹[Þ{3• f¶¶Ê;önß¿¹ŠeîŒy»}óÝ šÃi™ûÒoïôpõ•;\fë•ϯ®1›µ)ÆýÐ}îi¬¢lU%»¬Ý…ÁAü¸ÒàܾÉÿ_šÁ½¯¯aÙÏ— î¦5À[!¸ÛY(¹ùžu=€Ý`e… ÜíòjÊ®“ô@`¹ –[šðÃc¡¢ƒ¼K« r$aС`ÑJmj,IçÅ…föø /rž0³Æ6†3À;%1ß÷Óò:CµZÃÈ^—€¶ÞªÚ‚¼k«X§_Ó÷ 4М™­¬pünwÁŸufö@`¹ –[óv×"<ˆÚü¤7ÏÕß–Ô Ÿû Ð/Èë÷ÝUv}GT€8†û·™­vß>ˆ=~–²»›§;%eœHž@i?"›«r¹;£Ê2Dxc›ÅQk€·P(,( e‚»…Baá`×ý:»R•oµ—¤Å¡ß›%ÍRù¾­)8DÊ»M÷ ¤9’©ÿHóå¦çäݲëS€›¦Kš¦á ð.ì3–Ðóã3{¤ÆQ£s1oÓ¹ðnù¾Ÿ–×C òÞ›ðóý|IÛ"ÚÕ#éI ’²e– ÚÖ!éꄜ㮬”êæ[ú½ÙÌf P÷5!çÁîJ»¡žGãÜþ´¼Îýí ])ƒƒî³À"7xX5ž3³é Øÿƒp¸Ëª`Ï\|©™åÌìþ”Õé YJ¥Ò¬PíÝÿ¨áÃÁ;Ìì.3ûbÄ´¦T*ýž™}ÙÌÞ×ö"À»¹Lp·½P(Ìʺ_gMª\ž¡Y½Ü`m½Ye–Ù™¢CÅòv¸Ÿá¶> é.IOÉË Û(/C,Ÿ„kEˆ6Þ$¸ÊïfVèí&Àieöý¤¿Îpl÷¾”œï[Õ7X,µ àVS¿÷šœÛšª(ÏÐän,;«Ì2;ÐnßtW/t8¼‹q¼ ò‚¼e‚ž¸k¡§\°s£™Ýjfù„ìÿaO›Y«û̳½ŠìþîüðT b YJ¥Ò§B·wÓ‡·šÙ5³7Ĺýµa …œ2ÁÝ}…BaÖPÖ;µ´QÒÓ¡‹Ö›åe:Í‘t™‹ÛÇRv¸Ü^¦w$¼]x«;§5º ·jÌJè…ÝP¼]U\æc¾ï'ýu†S­AÞSv¾_¤ÊÉU3u©÷Ž—8Ÿv–¹ízc¨&y§«¿;ße4Þ_æx,mŽm8¼å^#ÎíOúëŒpÊ•-¸#ÁmÈ£nžñ|îÙhfãÝ|_OKýe’Ù ”Yp®¦@Èù¥Ri”™5Äqp_Þû"‚»{ …ÂÌ¡®;&6†.LoUïÀ`Kqa{[ЕЮO{sN"À[Í9í©bž×¹Œ°{]Ö[SÌÚs.Y º9æû~”kǾ$`?„׉£‡Uý diôe OMâ= 8Ç…ƒ8·T[:ˆÓÂm h3Þ ›™]ã—¯p¿ Ùãû:1ßOV”)á‘èÏîBZêJ·¬p¥Ö…æ¹×=¾Äeñ_3P™®¶8woðãÌlr©Tšì²nÇ Ã:?cfO–J¥½¥RiïpGJÞ{BÁ݇ …”áXwLܧþ%ü[Í]w¶GÞhéMê?`Í5)9LV+:“«GÒõnžV%läô‚OÁ6nSõe&%mUʼî‚ö™)7jv°>^·]b¾ï‡Ý¤¾%Y¤¼Mƒ|8Z©Ú2XoIáG¤áðvÈ+ñ÷óÜ}á ~©÷WW¹•\‰‡öÐsI(MA€w€¿ÍÌn –$¼–Ó4˜×‰ù>²ºÌû|™]ïæi5³™ l[ÙrJ®<ÃA÷û:7ÿJwœßà~¿Å=ЕiXQî3WÛœÛ7ùQîzn˜Ö÷Ppð53[×¶W„- « …BS¡Px¤P(lˆ*É0ØuÇÈMê_WVò|­’žPôí§~ÖF°TCÊg³%É*Ÿ±æ·óÎ@èÆ$žÜ´È"ÂmÝ ÊAÞ&IÏD,;ßM‰ ðººrÏ P§®RfÛ63[  »‘ðZL÷ý(~ wûó5Ê«KÞ-/ :˜×‰£Zƒ»iº£Á7%ÚÝù/ 纛ÂuEÝãyÀz"âpî Ü¢}(ø5>m(À»­Ú’2.¾5MÞ@ w{¹ù\»Û\ææÊÁ¼NÌ÷;˼mìçw‚à7&¬}Qn1³)n°Ø ;CËÞ1¸ì'À Àëøÿ&3»Ë ˆ¶ÆÌ>ë¦à€i`fo¯a{ýÛÜ{{lª!Àûx¡P¸s$Ö#‹Õ7@ëþ{‹¼@îÁÐÅë&õ-Ű@}3?“,'/ÀuѾÕvn <ö¼¼AÖLÒ I; T1=¦Þlî°ñ’ž­r=I:7¶†/Úƒõéü‹3{¶Ì-ŽwÆu• ð>Ÿ²o“;÷50Ÿÿ¥Öª!¼NÜ 6¸›¦ ï@ƒ¬ým ¿Ãw<^;(i^‚Îw‹CÚéîñ[\ ÷`èPÞ,Å`f ‚ÁÑ„¸¹´Â6T:‡»äˆ¨;<æ»)©Þ&×÷Íxý þªÁ¾NL·KÎ œe« lßxìy7ÈšùÙ­ Ûÿ}ë\—{/¿Ó-w[™çŸw_ ÜA€€×ï þ‹ÁlÛ¨©–-f6ÕîºeĹý#„M`€7ÈØ'/ˆÙèþ¾Ð}X^¸ÜEþ×|Xä%=Z¦]Õø^ Þ²î÷;]pdU’NUNª·–àn’.𦸠®(÷…æ½>bžƒ1o߯aŒïntë\ä²¹Òà îãQóÍpÇúöaz8jpןV'øü?_Þ”Q%y®‘Ô )[fÙŽÀûç,I™óš‡é>Äjtÿ{ØÌ¦p˜=!m®Æcå’\0ëÙ~±Õ§ýæ5Ÿ»%¿Ç̶ÇëÄh›äݸQ¾øX([Òé~¿Óm“U ÜÿtÇûæJe–*<ÿ´[p¿mU IDATÏxx}ÞàËx ¸ö_jXßÓþònÙ/Ĺýxû Öѽ]Þ@kžßন ž%J~=Æ&IO• Zô¸6úv»íµH^à·]ÒI÷¨o}ÞØŸÜ´@}³°£¦‡Ë5Kz®ÂüW*a%Üàh;ËݾXæ‚0éáÝE2³É À´xowÝ;L¯óz[®ê‚»›U]÷¦˜·÷Võ¯?д¶Šuv¸÷…é =^ƒutow·a?x~ƒ›rË.è<ÓöVëÑp›k î¦4À{»?ØÖp¼NL¶GÓƒ©ö˜Ù’À¼»Ýñ²È~ÛÍlŽ™Ý¬Ï›ýƒ Ê>1L_únpŸ‹!À À¹ƒ ð–ÌÌu½7”½û7u¼ýë¨vËËDjvÏåå?7«o}ÝVy¼` ‡… <&kà€åíe¶Õ:×þ· fÊ»u»GÒŠ$œT~µ¨i½ëó­ª>c7–.3».X#×e%í.SraÅë¹#b™fw‘³"¦uxý‘±w»úy÷ÊN,¯ñ‚nŸ[ÎÏÜétA݇ÝEóÒxý/‚VÓë¼ÞvWq<ûåxî¯bÞΘ··–àîfU—»JÒ”|&tÎÛgfÍî¹¼ ~mÖ×u¥l® •pX˜öú„JLDy8°Ü@µÙ}W&¹DC`žr^?ºr8^'Ûbr…>½½Ì±²Îõu‡;fºÒ=}fˆQÿ·¹÷íG†¹rÓîœá—¹j䊀só™Á[ë‡13{K ¸ë‡?÷öàíçÁ*/xŸ•w j¹Lפ}˜›¦¾ƒ)…§Ç#–iVoóyÙ¯’ö¸õ=¬dyk ðÖ2Å6ÀÈ@òo±ÜXævä3[\a]­£ÌßXßÃ1<÷G¥ïqÏ=WãÅÜ­n¹ûÊFüìÀ˜ïûQÊxýÛñ Óë¼Þ¶W8–o ÍO…ùwƼ½UžÃ:å•ã¨Æ÷31¥Bç„«<ÞŸ5³Yd:6&¤½ ²e}…Úì©d-0O¹¯?¨æ‚áx×y;L &áñˆešûÁî ‚N÷eé4÷åf¬ƒ¼î3Ïd—u<Öºõ?ç±XH•2xÍÌ~Rå:>ÜýeÚO€·ŸÛT}ðnŸÊB–$sh‹Ij“Wº!J«¼Öü²‹åÕåÝ./Èû¨¼Lèåq> „‚O4<ÁÝ n}ÍŠg€÷±*.Pv›Ùœ!G¶›ÙÌžû£.h;†¸ÎΈuvºlá f¶5æû~Ò_g(Ê fFËån¹_[fþ.yõjã*§ê2x»†é܈€ïƒ'Efí—„*AŸk ðÖ4ðdÔkĹýIAþmsØ—ý צ2˶$»Å TØåÞ[§¹ÒÝf¶<ÆÇÀJY7p¥ À¹}sÿb°¬B þn)ðýÇföÖà7°îVæ[ÌìDDp÷xRÚO€·Ÿ¥z`o}‚Ú»HgruÈ+¹0VyA`“tƒÛ†Ýî±)òÝ’–Åõ4à¦iÏMw{ÏÛ!Xc7*›çÅ3ÀÛä2N{zZ ë›U¦]SLÏýÛ‡{p¸ÐmÚ}¼1ß÷Óò:Ãq> yo®°ÌêŸñº8mýS \–b·;·U#›’σK‡! ³>Aí c7 W R·¾f¼ñ ðºú¹ôcG¥/g]×\ôw u»Ç¦¸}ªÛÌ–Åtxt„¼r¥ À¹}s?›Áë—Udô–Âe|²ù3¼”¤öƒ°…Baé0®wiB¼Ó5ôïM i«Ÿm;P[ªÍ¼ðƒ¼þàjþ€EϹç6É ”.ávðk'ovÛ$xîJ·zTþVôÅíu]àññn[ø™rcz\`fÛB%Ïùµ'k\ׯÀ:î‹ù¹ÿÖˆ‹±»†¸ÎµQµøâ¼D€7l¡;oíTuõ…%/¼OÒÓª>(Šø¦C@禵×7­Ì¶èv_Î,³üü`߈ço ÀÏo Ûv Ë«\—äí1³ë] û÷9¢ÕÌ6¹}i)g0Òr¾ø0ó6÷ØÅfv è-…ëôsþ ií/ CAÞ‘˜&h“äT9èYiJÂþœ@;Ûå_Ãí¨5Ø5YÒ6õfë®”}V^™‚gÜk.‰Ù¶X§á­»[nº'ÆçÁÆP]Å'¹ž`Ü 8ÿßê.L·¹:‚¹!®/{\6ï½1¯ÇI€Ã-“Ô?Ü•Réb€w~‚Úëßq°Ùüòç®tÛ¢§\Y·ŒïºÀãã] ¯}8îŒÁö×e€×•eð÷óv|Ò—®Öì6?[ו>èqõª›Ý l]f¶D#øAç.÷afMÄs6³Ÿ”ÉÚõ~ÍÌþÔÌ’ØþB¡°îxïIØfY¢ÞÁÃj™ºã ^Èf÷7o“—±Î\Þ(/Ø]«)n~ ÊÝú6©7Ë·Sñ„./é^ \‡x(Ó>·þ|ÌÏ…³åÚì Ü®y'ï0±·GçæË s¯Äý3á’j‹ÐíÒ˜ ¶®³s㞘¶;{bÔîÍîoÚæ¾” g®oÌû¿+ɰÍr¯1³Ýú6²|;“2!Hæ‡ùO˜ÙU1ßûÌì3föe7úüo˜Ù”¤·¿P(ä …Â½…Baßv÷¹uçÙÓb%ºl“7€Úõý½yëŸ"oµ.ye nqë}V^°s›<ÆÈŸ\äMä:nrËßϽ¥¹/6Â_rp{.¯ó}ÞÝe°o„‚šûÜúó1mÿÒl{x;,I›s¿k›«Å?'ðØÎÁ”g ¬Š«oßåÊ?ÝâÖû¬ÛÛb\“H¤àêÁA…:ä{‡jм@q‡¼:¶ëÝúÛä•r@„u4º àGØ¢Äê}¾=4hpPµ9ðþ).PÜá>S¬wëo33>ÿÃìfõϲk×ðÖž¦Þ ïAy5³éëâòv3[Í– VïÏ7Gd·gýh3›òìÀ­ªs“¼r ’6È«Ã;ܦɫ÷û˜¤ñlr€×+¥´ÍeðnlI¦ ¯1ÍÕû}ÌÌøüÔ"Ë&€äʰ yÈà€¤Hìí;üÇ\hnngfÕ76“Ñ‘#GŽ}þóŸï¦ë!Ÿ/Ê?¬Âï ÿ@½{Ï{Þ3Q^à ¦É-``7eSNRƒ›FIÊ—™FæË…Öá¯ô?þûoø½¤TCRÿðŽŽŽîs¹P‚Á·`0.œ ÿ _,ø_¦”"~–BÏçý@?‡×În’t›fHš)©IÒqI¿´CÒw%}·eMÛñ:ß\_—ôEIg$í‰x)ÕÀ&êZ8¨çgj§õÍÈ ge•SÑM=©šüyý .>ètxí켤›$Ý%©9b–&IóÝt½¤#‡×Îþ¢¤¯¶¬i;]§›m±¤g$½—=€ú’äoæ/¤I8;3Ä%© Þ[î ê{K¾?_0³SꛩéôN»éŒ¤îÀïÝî±`ð/œå ú@_ó%­—4—MqÖVIŸ‘ôÚ›‡×Ξ!éqIs—ô}I‡$í’4]R«¤%ò‚½Í’î—tÓáµ³?Ú²¦mGSäy¯•ôSNÔÏE>€ú:Þ3ê›éîFKj LcÇ~üã×_ök¿öö7\vÙe_pÁ“&L˜ÐÜÔÔ4nôèÑcòù|>—Ëå$©X,OŸ>}úÔ©S]ÇO?öÚk¯9pàÀ];w½òïÿ¾yû·¾õÍ'OžÆº€Êùn? øOJêpÁ—lªa7_ÒS’Æóù­“tT^ÆâOhoò^;{‘¤ êÍÚ}AÒ—%}'*3×eú.—t§¤Yîá#’>ܲ¦íßêìxØ-išû÷QI”ô,§ êç‚@úõŒzo³÷³4GK#/hÓ$é¼Ï|æ3³—-[¶`Ö¬Y³/¾øâé…B¡ IfÖÿ3ðe¼—‰:©d2R÷©îîWöìÙõ /´}ï{ßûñúõëÛ$uÊËÈñ§Ô›ÝéßÞOF'ýOÿŸ{çIZ ém’Þ"¯æåe’&T¹ük’vÊ«ù¼¤ç$ýØõ9j·@Ò&yÁ]>¿õ;Iò‚Y׺ýŒö&”ËÜ}VÞI’ô‡’¾Ø²¦­XŲ9yåîp’tuŠ2yçËËjžVæù—#ž;.éÃ’þ™Sé¿èO¤yóæe¶nÝ:¾ÖåæÎÛ¹eË–"]::ƃ›~¦æy½q’ÆÝpà s>ùÉO¾wîܹW?~|óÙÔÉ`PÏ,"Êf½/cÑg”LF^ô/Óû@FÒÑ£GlÙ²å?þþïÿþ©¿ù›¿ù¹¤cn:®Þ`ßiõÍè$ÐGÿcdƒ×Iz¿û÷p—qê‘—qøOòDú ›¼*áàn¢?¿€ày¡‚¼©m¯ËÄ}N½e>Õ²¦íó=#I-kÚÞYf=«ä 6&I?—ô¶„ÕäÍD¼ß-—ôˆ¼;'juRÒoHú§ Ò}ñŸHóæÍ»hË–-¯b¹–-[¶´Óõ¨ƒc;˜µ™wcäõÆ777·Þ}÷ÝKßÿþ÷_;eÊ”K$Ð ò¼øžõûwMH&Sæ÷ŒüSF{_ݳûŸ6ýÓ¦ßÿý;ž-otúsi‡¤¿•ô¤}tE¤¨àn¢?¿€ðù íAÞÔ¶÷ðÚÙ·IºÇýú‡-kÚÖ”™Ï$©eM[f€u­Uo&ïﵬiû£ošòj/7 a¯JšÊé€tiÞ¼y—lݺõ¥þ· h˜;wîØ-[¶tÑõHùqíöäel6É»åzÂE]4ùÞ{ï]ñ¾÷½oéyç7Þ»Zôn·÷§³?e*KÚ½{·vìØ¡Ý»wkïÞ½:pð€^;òšŽ;¦S§NI’F­qãÆiBs³&]p.ºè"]rÉ%š1c†.¹äe³Ù³Ù›î€ôþPðË(£cÇŽþà?xòsŸûÜ·÷îÝ»_ÞmÞþmü§Ô; A>úŸþš·JºMÒÇå•ìx=‘ô-I"égtÍY %mTÿàn¢?¿€¨sÁQIô#Ú› ‡×În”W?¶UÞ—?³ËeÝVàÍIÚ&/8zHÒ%-kÚN&ô=ýßå}Ù3Xi>@Ò/æÍ›wéÖ­[w "À;}Ë–-/ÑõH©¬zƒ{õflNhll¼àÏÿüÏ?òáøcãÆ æ…|;vìÐæÍ›µuëVµµµéäÉ¡]566jöìÙš;w®Þþö·ëòË/÷ªµf¾Œû];Öyô»ßÝðÿßrË-ÿpòäɃò}~Fg·zƒ|%ºœþGMæJú’¤_ág“ôîïÛZçý4PpWò¾ô¨%›ï¤ÏÈ+W×IZ¯ÞÚ«Õh;¤5¨eilïáµ³¯—ô¨ûõ-kÚ¾9À¼¼µ®3Æ‚™È•QïÀtÁÇ®%qH½¤g€ÔœÁ5oÞ¼æ-[¶¼F×#…²nòë¬6É©eõêÕ o»í¶›&Mžt±J^f¦˽ÔÛ³gž|òI=õÔSÚ¿ß5O²S3ÊLÎ(sAF™ Rf|F™1î•$é”d]’5Ùk’4Ù~SiyṀɓ'ë½ï}¯Þ¿t©¦N™âŒ2™>A¿Œ2Ú`ÿ+ò'òÕÿñ?þÇ$–·&?›ó´¼A¾îÿ Æ”4ãü’.gšz^I“ƘšMçåMc]•Ö=Rç錎œÌè@WF{:³Ú},£YìÊÒÿ¯¿Ë$Ý-/c7îïý&/£÷÷å ÔVoÊ+ËÐ4ÌëÝåöƒ¸Ú)iú0¯ó¸¼àVš‚¼–Æö^;ûë’VÉ+Ë3q lÛ¼’Úå•(úFËš¶O%ìýü]î\ =ÿ IŸTtжC½_ ’´TÒÞH?nñÒqk­úY›[ZZ¦þÝß}ã¿^}õ;Þ›Éf³²’ÌúfmþøÇ?Ö·¿ýmmÙ²¥wP­ó¥Ü¬¬²oÊ({YF™ñC;UØQSi§©´ÍT|¡ä]~HÊårzÛÛÞ¦~ô£š?~ŸlNÿßfVzöÙ>µjÕ§<|øðw±ægs†k³ÒÿÃÐÿ-%½ýÂ’æO*ꊖ¢&6íl?)ýçáœ~r §Íû²:|2KÿŸ;yÒÿ®Þ¯c’┼zœwËËÜ®‹äeî6àù"®Fê>./³õß²XµW’txíì6I³$=Ù²¦íæ­*ÀëæÝ(/ÈùBËš¶Ù èï£ê о,iZèù—%½M^ð6ŠàÝ/é½’^äý€ú Hö1¼%ÿlÖæõ×_ÿ¶»ï¾ûs´¶^XR`-7ýøÇ?ÖÃ?¬;vxkÊK¹yå~-«Ìe™~ƒc Û•Œ™l§©øï%égYåŠ9år9͘1CŸüä'5Þ<¯>k&ã~z·í:thßwÜñ§ßüæ7ŸSßlÎà-ûFÿ®ÿGçLïžVÔ¦÷höÄ’F¨ûe&µµgµqWƒžy5¯3j ÿGÎ|I'éM oÇ6IÿEé¿Åx¤ƒ»qÿÜ7’Ço’‚žVgí•$^;»Óíû_iYÓöÙ óÖà½WÒ­’Ž·¬i;/áý]”ôî ýÚ!/HünyYû Ž‚‰4oÞ¼ìÖ­[›jü`”™;wî‰-[¶éz¤äøõƒ{£å ¢5QÒ¤{ï½wù§?ýéÏŒÕ0ªd½õUÍL»wïÖƒ_ýª~¶õ§ê)ecKjxwV¹EYeÆœÛS‚u™ôïeØ †“^ oΜ9úô§?¥‹/žÈäô}===gþöoÿvýç>÷¹ïH: /›³S½pÕSoXúÿü|Q{Ã}èò—?· è<-m|i´þq÷uöŒ¢ÿ‡×ïÊË~-¤¤=Ýò²ÿ"Å}¶GÒ”:þÜ7ÒÇî«’¦&`?°:k¯¤Þ ­¤?jYÓö{UÎåû-kÚ® Ì»NÒíRuá˜÷÷Hº³Â<«$ý³¼/Býs'¨ þÛçšÙsƒXn¬¼[{$ ÷åÝ’ß’Ëå.zì±ÇV_sÍ{–™IÅbél`ïôéÓúæ7¿©üÇÔéÓ§•9Oj¸&«ìÕYeò¯Ï5OfLFºF²wö¨øcS¶½ø¢î¼ó‹Zºt©>ò‘(ŸÏŸ­ÉšÍeGýö7®ž>}ú´üã],sêX줼 ß¹$¾ÿÇ6]ÿ†n-›~F…×é༼ôñ7žÒ‡/?¥M¯ŒÑã/£ÿ‡n´¼ª>™²v$ý¹¼‘ä?#/¨Ÿ6ÿMÞ€P9Þâ†]Ñm_Ú_~i‚jÙû¦¤ëË<þª²5°þz°L^†ï[$ý‡>õ#ɼM[·ní<[3´šÆf2š;wîy[¶l9N×#áÇm8¸×:nܸiO<ñÄï]qÅ ,p+¾™i×®]zà´gÏõXŠ Ï¨tMI™Æ˜µì”Txf´F?7F ™]tÑEúíßþm]rÉ%.›SÊd²Êd¤Ÿÿü?ÿãºë®»ûرc/Ë«EwL½A¾4gr©ÿUêѲiÇuýNjì¨xm¢=}ç¥ñÚ´o‚”¥ÿ¡EÒwäÝêŸfÿ&i¹z3ÔÒd…¤GÔ?HUŸûFê˜=-ï o'd°:k¯$éðÚÙ[%]©Pî0¬w“¤%’~Ú²¦mnÂû»š »å}IôAyƒ±‘Á @Ȳ €Ä Þ–?NRë„ .Ù´iÓ—gÍžµ X,*8}ÿû›ô‡ø‡Ú¿¿ìâ’Š¿sZ¶,†Á]y-ê^rJŸ9ªž‹ztðàAýÙŸý™þåé§]{JgΞ=ûªM›þéË&L¸D^†Î8·MÔ¥ÿýÿÆñgôgï8¢ßžÕ»à®$m0ý—º{Þ]~Þ)ú¿6SÝEÿ¢:hë"×Ö©)lÛ·åçNÀº_ŽyÛGâïKb°3SaJ[{}?õïÃkgË'·ÿœøóôwÞýü I_8&rò¾ª”å}dÈÁ]‹ø¯Ò ¤ýæ¤îi§´ÿ7^UÏùgôò+¯èk_{X¯u¼¦b©7“sÖ¬YW=òÈ#«%MrÛfŒÛVi¹UPý?¹±[_zËˉ îJÒ¬qÇõù7þB“ ÝõÜÿ化ЙYÇçÆ™nŒNaÛ¾#ïVí¨ ×ѧ$jÛN»íöí ½‰¦¢½.øú÷ë yÍ¡XëÖ#I_IhpW’¾ éÇUÎÛñX“¤'ä ¾R,ÉÞƒ\®D·#a‚ƒjùuW[Ö­[·|ñ»ÞµÌ¿%¿T*©³³S=ôÚÛÛuæ¢nµÿæa•Î/òjr€`nÖÿ·EOÙà<Þ4”`oÏøíYþ²NMîÒk¯ÑÿüŸÿSÇ?à+•Šzç;ß¹ìî»ï^./£1MõXÕÿohêÒïÏzIŒ>“ø %Zÿïôuycg=öÿ@Ö«>jîV²Èm‹4ú®¤jdjò¦Ñi·½¾K{éÏÕ[‹÷¶Ãkgß4˜•¸åns¿þÔ­7©L^]å¡Ü}Ø(éAN¤[ì.z¿ñod/½ôÒÖqãÆ• >çr¹â—¾ô¥–oûÛmµ®ÅŠ}éK_:P,såæéììÔ¢E‹Î°{ &²ê î/éÂ~ô£ïøÊW¾òG £ÌJ*•LgΜÑ7þîzi×K:Ñz\û}¯liõ Àf‚?M–©â bý÷Ïô›§Ö2™žŒ.ýþe×>^O›¦û˜FåÝ®ŸÉ¨§§çÌç>÷¹ÛüñJÚ'ïÖÝS’z”Ü/wjîÿK Guóô_*ŸÙ’{± ؾ¹œ.eõÐþ9zåLs=õ9¿›ð ÅH¸EÒ_¤´mËäÕåÌÇõóÛëÈ?íøÁÎïÑÞä:¼vö4IÏ©w ±?’´¦eM[±Šesò2wow’ô¶–5m/§`Ó,—7ÀÚ` ;*é’~Äé€ôŠÝÂÌ™3Ïß»wï÷OŸ>Ýç–Óp­]3ËöôôŒ­µoCCÉL&3à…~>ŸÿÅE]´ä—¿üe»bpŒfåÕ]'iÒĉgüà?¸âĉž­»Z2møîýüç?×ÉóNê•ë^Ritmñ¬~ÝpP7Sã™Ãúÿ;cnáP%ÁZ½¹S9½aÓ›4¶k¬Þüæ7ëÚk¯U&“Q6›U&“Q{{û¾%K–üN{{ûI$sÂE%o‘šû¿9wBÿí’ÿÔØ†â°þ!6Ð;G&j¦³»Pd x°Nôà·©ÃΫ‡þ/g¾¤ºý½º%½CÒORÚ¾¥’6È òàí{z:-¯Îè“´7ù¯½@^Y?Èû I_”ô¨º¼®ÞîrIw©·dÍ~InYÓöã49§þƒ©•;÷?.o µ(/G/€2—é±0nܸ÷œ8qâ;Åb±Ò@µÆ *ΟË厎;vù±cÇþ…Ý1•WKt¬¼[Ï/þÖ·¾õù·¿ýíKÌL¥’ɬ¤-[¶è©§žRwC·v}h‡ÎŒ«-Ýz#­}»ý‚º¨±[ÅÉŧ™`PLßbM IDAT7˜ÕÛ'®\Ýáœ?^Ðìï_¡|± w.Z¤9sÞrvÀ­L&£Í›7ÿŸøÄKzEÞàK'äN]Jsÿì”~wêO51ß=lQ„>ï™2ï™Þ‘ÁÝ? ?4Gzõÿú5ugG§½ÿ£äÝrü&N‘¶IºR^°7–JzXÒ…tõYû$ý–ê#¸[7íu™¼OHšxø¸¤ïËËÌÝ%iº¼º³KåÕšõý\Ò‡R’¹[«õTxÈm›-œ&H¿\ÿ¨îîî—&Mšô­S§NýV©T(C©Ö8Á€ó744kmm×ÞÞ¾•]1à×]-¸ë­7ÜpÃâßüÍßümI™R©$³’8 7ªhE½üž—tªåTÕ/Ð/°›qÝlßÇ”UÿÇC5vì…ÿ9{fú‰™L&Ôèʇu1_TWs—.Ø3Iû÷ÐÔ©SÕØØ{×âE]4ýèÑ£;~ö³Ÿí•—éä÷,­ýo¥¢>5©M>1,/~66¨§œ ÿ üÛŸ2åöƒÐC ò6f{ta¾S/œžª}éíÿrî”ô1Neµ¸¾~:¥íÛ!/À{’®>ëay=Ú›"÷üó¡£ÿýš ’Ð^(¯4A^Ò›åe²¾Ïýœ¥ÞÒ%G$}VÒê–5mGêôx¸]^I§ý’®©³c€ºë[ü&Nœ8·££ãÉb±xA&“13ö¿×_o.—;8a„¥‡&¸‹¸›wÑ2NRëèÑ£/{ú_Ÿþ«Ö– .6+ÉÌ«»úØcßR{û½úæ=ÚÿÖ½U¿@8¸k™þY¼ÞãÁ³ÅÀ)˜}RäƒéŸ Î~™»n\¶àcÕfòN{á]²cº&L˜ }èCjhhP6“Q&“աÇ^yÏ{Þóÿœ:uj§¼,ÿVý`Nijúÿ]cwè}çï–·‚öò‚¸ÁîÍ7h`±PæîÙ-o}{a°'ö=ñFýèô›ÓØÿå\&©Í]¼£¼S’fKÚɦ’ïðÚÙM’®“W†aº¼ n£¼/:^—ÍûIßmYÓv¼Î7W‡¼š»ïvÛÔ‰†8ÿqííí[[[[wttüèÌ™3çÄk˜YfÔ¨Q&LX|ðàÁmìˆ ?{3/iŒ¤ wÝu×GZ&¶\\*åeošž{î9=zL'&žÐþ9ûªßïûw­o@/\ž!TšA™ˆ[÷ûS÷¬‹›û&°‚ÞÇ2®f«Uä}åM/kâ‘5¥çŸÿ™ÞúÖ¹²lF™Œ©¥¥åâ/}éK¹ýöÛ–w‹þ)y™}=JF€·êþŸš?¦÷ŒihçB©_&®eÁÜÀ>aÁ nß*ò¿'°ÀncŸÁànƼÇÌä]4f»^¶ u 3uý_ÎÝ"¸[Ñn[}‚M$Ÿ Úþ½›0°ß‘ôÏ’^eSP_²qÿ:´­µµõ- û%/ãv8Ö믧¡¡aÿ\ð‚»ˆ¡œ¼@ŸI“&M¾öÚk?V*•T,–T*•ÔÞÞ®^xAÊI»씲ÕÁà®…ƒ»áß³^Î\‰ÿ§W¶ÁEè“¿L ´CßeÜkgX®÷ï9ûï*bp–5ýâ­Û¤œiûöêèxMÅbQ¥bIÅbQK–,ùؤI“&ËË‚­Þ²4™´ôCVúÈùmÊ õÔÊØõKr˜+ÃñK5¸ÉB¿Ÿ}<=Ÿe"æ ”Ì_ŸË˜>иE ÙTöØ\Iç´Xµ»mõä yÁÝ›€ú’ˆ7ÿÎÎÎc'NÜtæÌ™‹Å&œôº_–aÔ¨Q'L˜°dÿþýÛÙ#ÁÚ«ã$]°nÝÝ7¼ùÍožï×]-•Júá¨ãÇkïŒ=:<íPU+gîFv³êè=ìUŸú»Èôì—ù)…nïäìf‚Míûï_ÑÿŽr¦pF£Š£4áØD?~\S/¾X~¸0ŸÏ¾è¢‹ìÉ'Ÿ|^Þ­œ§åT]JKÿ/ý+ͽwÐ/Ö';7°è1¿Þn¸³]7ª&¯ÿOÿ÷AdòŽÉžÖiåµ_¤¥ÿËyP ¬Vëqt¡¤GÙêÈ©àÛ;¨Ù¤ü¡‡úùå—_>sÔ¨Q†Z‹×w\~ùå3:Äàˆ›Œ¼/_ ’šÆßzõÕïXZ,z·æ—ŠE½úê«:tøŠ…½2³ºA¢Ëgî†ÿm ]0S7˜Åk.ûÒdY7›Ì‚-Û?;ØÏîûX Ã7œÉ«ê2yw]ö+õÎèµ×^ÓýûU,yœÅbQ máÒñãÇ·Êa»à¶m& ý?6wFïhü÷S¦@6ÐÍ~žÍàÍôÍ䵈ÌÞLNgùƒ¿n ¬{0W¤oõ‚Æd»ÓÒÿQÞ*é×95Öì×ݶ€TË&éݶm[Gssó;vKµ—k”eØÝÜÜüÎmÛ¶u° fü[ƒÜíùŸýìg—6Ž3¾T*©X*ª§TÒ‹/¾¨l&«—߸[ÅQÅŠ+­*s·O€µ÷¹>¥ú{ûΜÎfûf­7 "[(°;” oϨýêÒÊd2Ú±c‡JÅ¢7•Jj3fü­·ÞºT½·é7¨7Ô˜èþ¿zô/5:Û3¨:[?7œ©,¡¨ þ;.WŠ!<p=~ön&°l­£ 2gtU®- ý_ÎmJfY‰8O·±¤]6ið¶_|ñÅWåóùýµfòšY&ŸÏï¿øâ‹¯:pàeG~xÌ\kÜ;ÞñŽkKÅ¢W{µXÒÔÙÙ©3£Ïhï´Wk[sŸr ý³x-iÛ'‹×Ͼ ÕÏ=› œ ×äUèvþè@¯*y•©þžý—/zI§ §ÕÕեÇ«X*ɬ¨«¯¾úZIãÝ6Í+þÞŠýß”íÖ[Fíú+…Ë,„²yý,] eïfBõvÏ~sêô=;D6¯…^ïlFqfg¶klæTÒû?Ê…¢öîP|ÜmCH­lÿè]»vš:uêœQ£Fí*gòúÏ5jÇÔ©SçìÚµë]k„»|ùò9­­\⫊Ţv¿ô’2Ù¬^¹t·J¹Ê¥DƒÙ¯Ö/KW¡€ªúdäö¯»ë—`°@ Îúílð78ެíkU|+eñ–r%íšò+e²Y½úê«î6}¯^mkkë%ùÈG®4ÖmÛ†¤÷ÿ¼QÛ5*SÔ ôHO‘ÝL »ÖÁÞ>ÝL ÄB0#74O&4 šY» Õq¶pÍÞj7Z¦¨9™ÓÐÿa7JÅiqÐF¹m©•Mê¾sçÎí­­ïÉçó/›Y¦\×P-ŸÏ¿ÜÚÚúž;w¦ÛSáÛó›>øÁ¾·Tê½Ýüĉzíµ×d %½2å•ÚÖœ‰*ÓÌÜU ÈØ lûdvFÕ×õ×ÊÞªók¡RyJAÞ]“wª”+ª³³S'»ºTrµXK¥’>øÁ¼W^Ö¸Þ¦_uÿ²E]‘ûÕ ^¤OÝÝp78€^°ŒB(7ª4ÃÙ n.â±P@9“ •d”ƒe"2µ•jx³mS^=Iíÿr>Í©‘mÉ&ùß»wïžU«VÍÈçó¿Š ò‚»¿ZµjÕŒ½{÷î¡ËcÁ_£¤ófÎ|ãU~b±XÔþýû”ÍfµÒ^õŒ:Sq…¦ˆŒ]…³hÕ·´B¿½Öo^;;`š?34­ÿrá’ÁÜþ“zÃoU–j8ÓpF{&¾¢l6ëݦï‚{ÅbIo|ãÌ«$ç¶mܼöÿ²/ktæô Ïúe·u(7QC7Ø×V¦Ö®…J:D|-"˜4gjì‚Në2Û™Ôþ2_Ò NC6ÃmKH¥lÒðÐCiiiyW>Ÿ!ä w_hiiy×C=t†îFääÝRܸ|ùGgÛÔÜ›ÁYÔáÃG”Ífõòä—kZio ×z3b#K3D•pPï`iÁ:ºYÊ9º}²|­o ‡vë›=ÈÜ윮"‹·u§2™Œ:::œE;¶yùG–Ï–àå¶u"ûÿM\ ñ`Ÿ–ËâÍê,[™ Á௕DMåf‹(¹Ï…ú½Z3ŠÛ’Üÿa×qJd[@%Ù44bïÞ½{?õ©O]Y(~å¼ff™B¡ð«O}êSWîÝ»w/]˜óC]9II‹½ãª’Ÿ½Y*éøñê9sF§FŸÒ‘óÚ+®0˜½Û› «Þ`ª\õl&m8¸k}†]³@&oð¹Þ ®·\).Ø ü[1ÈZuY¼‡ÇÒÉÂ)‹Eu¹Ûôýéêw\}•¼_Ag‡‹EgÕýß”éÒ$ô«dBÁÓLà1eC™´áR ~½ÜЀjÁ@îÙys¡ßƒô…‚ɪl®¡‰­¥ý«®¤õ9ïçÔȶ€J²iiÈúõëÏ\vÙesóùüO$)ŸÏÿä²Ë.›»~ýz2w‘~h+/iÌ¥—\rE±T<œ:vô˜²Ù¬LØ_cH*¸õ_%èí—5Yn06¿Æn&r`.ë“Õ+)kÊDd ‡ÊýÕêØë_‹·ÒVÜ7n²Ù¬Nœ8¡b©¤RÑ ìÒK/½BÒ·sŠg‰†ûšíÔm¡ÁÌ‘©-è×p½\‹+X¡Ϡ{ìÞàkÁßïú[­†8µøRû?ìû·Ö`RÏ+Iëÿ( Üþ€áÑà¶)ÀÿaïÎãä¨ëü¿¾U=3Édº3™ÜBÂ!„Ë®+Ê¥€‚xƒ®ÕŸìþöçc•ߺÑõØŸÇ..(qÝUÔŸÊ­ˆ ð–%e8B@®$ä>æèž«{ºêûû£ª¦«kºgz& LϼŸØf¦ª®úV÷ô¼çSŸ¯ˆÈ”ãLµ Ú²eKg¡Pø›-[¶tjx¥_) éœsÎ9ÊM¥š¢êÍ¢ç‘Ïçq‡½-{k[Z2¸]o+UËšR[ _m¬r7Þ·ì´~§BÏÖX˜kaq2̃D‹ˆøó"ÜŽqÄp»›wã8CCŲ‰¶Ü”ÛtöÙgEpŠ~j’½Ö4þ üã^°%’&Ç º:֗ׯ«l“ÿVé·oÅ`•Ý&^\!P&Ñ.¢ôG‰ÊÏu,s‹Ûëqü“NÕ[¢ö©ˆˆˆˆˆH­‚ˆ¼ü¢Ø+4œ°ò„c}/š\Ë'Ÿ ¿i€Á†š:”V zËûÝšX¿Ûèë‘íl"¨+¯ÞÅIöä-·&±b!^Å ”žÛðw5†|©~úû0††††'Úò=ŸN8áX‚I¶¢€ÏÔËø·ÐÇLÛ?¡¥ÛĘøOXj’}zã­7*Mˆ–lÍ {M¢Š×T uãÏÑ&¯Ÿ€™~³ü\=%'êmQûTDDDDD¤ xE^~ñ)ÐR@ãâE‹ˆ&×ò¶¶âä{$ú.—OÀ6üSÃã–\“¸”õá…»•ªwG{#þ00­ÞžzÿjŽÓ[£ö©ˆˆˆˆˆH-ÔßOdrˆ¢/hlmm]¥†ŠEŒã’mêsA6Y­oÉ0¢åA¢/AÕn2t3É^¼ÉV Pª.+ºµ€ ¿·X ÆZ¢ÄÑä»ÖØà^6¶¼è©Å“IÞ`Æžu««± §à WÁFϧµµu!¥¬“­‚wÔñÏøï©…^‘ÉÄN©æææ6Ï÷±QÈçy8®!Û”­}Iñ«~¦j$úßBY ‡ROÜD»¢û)¬I¬'ÈYkÊo·`H„µ&ÌoãO­âÒÆÖÓÐëºXkñ}k-Ö:477·…ï}“m’­1Ç?íuÐ ì(‡„­tŒ˜Ê•·e­*m… z?v˜…ckMi§'û1Û ttL w¶¶måuÖÛø—=}`ŽÞº9á¾íÓ®‘©D-D&‡X7SR3fÌÈØX§µ×qémèÇ"mâßDÅ- '¨Cºèß §ÐÇ{áWÙ²–‚» ¥ ª0my{…1ÃgÊÛJDÛPc—kÈá8Á[œç•öåŒMJýW k´ã?ËÏNlÉUZXSýöxev”Á—÷Ð%5fx¶Jm¢JáJK0£l†¥æ|—æbO=Ò|½%jߊˆˆˆˆˆÔJ¼"/¿xlæN*•jö<k=œíx®A“×[/ã_I«ÞµoEDDDDDj¥€WdòøÇiŒWoÇÁÝ|í ÂÙ‘aå•‘e^ø˜jÕµñp·ÂÍÑ£+ôÐ-^&v)=*öx3zBYÅ 3ˆã8ãàyÅáå9ŽÓHyÀW7ãßèðJ*íÊÑŽŸ²]]֖Öß׎”äd}Q]K…ŠÜø±¯0¦<(®uÀüzÿ¸™z;Ô¾©•^‘ÉÇ®ï{ç•G§šL¡æ…Øñ¬DÒ•rýð‹J•»ñõUýÞØ½’ýT‡ûóZ“XVðÝø:ðûÊqÇahÈ;|>¾Ëä öFÿ”Í{aÉñª¥2ÖÄÆh¸Ê–Xøšxl0!^0Q^Ô~#9äÉœ¿Ò:+VGAoµ ¨Âõëuü©ƒçWïï¯""""""SŠ^‘IÈó}°vDÀw ÑÄØùX4eZÔ•Ú/زÊËøS±U¾. mõ§>20‚Âä#&²é®ëbŒ NÑw(MúU¯ã?A•Ú1$oc”ël…°‰Ñ1ñ6üÎVçJAnòö䱚¼m*¿ˆˆˆˆˆˆÈx(à™|lq¨è9ŽIE_*•Âhòéw‹5.…*=‚/‡´Š÷ «0‡c;ƒ±‰ÉÎ9ÿZ5•&ÒŠWöø²$1\C- dM¶)6É–œþoÁZßcü-]'Åø{N®W< …›*»pDøoG¹˜äcm¢mƒ­¸ìJíX=‚£ZÆ7jž;£^ÇŸ:x~uýþª] """""S£] 2iX‚C¿0”/Dážïûcp—v"í#Íppj++»OòÊxVgK˱¶r;†ŠdƒSõƒ€¸tÇx¸km…eزg5!3íÌ à3ß·ø6hyP( Ñ~fr…=cŽÿÛ|PVRá©øuµë¬­q™¶Âq;’·ÙÄ¿•ŽÇZ ÏUoã7 ·Cí[‘Z)àyùÅ㬠à,ôÇ>ÇuIÛ–š:VPgʳXýe¼Š6^´kGžroÇÚªw4#B;“¼?Õï;iÒ8Žƒµß÷ð<k=òù|?埭—ñJe&´‚ä>¶UžÀˆjêDà…ôåpåÈÕ&o¨<–ÖV9>©­:¼š¡†L½Œ%Ýz[Ô¾©•^‘Éa8ÜŠ}ý}ÙxÀg­Åufû­ãX¤©\õh«m*»Qõm¬Ê7^›Xœß'—g(kÑZ%赉í€D0=ŠV;×uñ½ÒdežçÓßߟбýÜ“vüóMmZr©’:16.61¾Éå$k_-¶â}ÏèmbÏÃØÊ‡fµ ýjQ˜1·žÆ?i¯ÞµoEDDDDDj¥€Wdò°€{{sñ€Ïó<ÇažWû’’_W vMYÅnyhÊCÀáëL)ެP™iÊÂBë¡j¢ Bãd_VÙF™qÅpó˜ã8‹Åáýè[K.—ë$ø<&Wç˜ã_hZ0î…Vê¹[±ïm¢E‚·Jðcë—.&~ߨõe!nüXIìñá 7qì˜xõo2¤®AaÆÂzÿ¸> Ko‰]W¸oEDDDDD¦¼"“Cy@¡««{w<à+‹¸®Ë޹ 3Ž~ºÉëƒàÏŒè—Ý8h ;[!ìÂÁR@l¯ãÕ»X[:?ö¼L…Þ«ª·y¨b‘Y„ë¸äóùR%¬ïÓÝݽ(„ûz²ôa­iü›O|éÉ– ¶©…^‘É!ŠÐŠÀÐ3Ï<óŒ…RùÁ¿~ýúç<¥€¯®Æ¿/}̸UjÛ*º66vÛ1ÄÃÝø}}0±`7ÞžÁVªúMôåµñö•ž¥ãc<1ì@ë+êqü“Ö[¢ö©ˆˆˆˆˆH-ðŠLQ„U …B¡oïÞ½/Ä«8{{{q—cyŸl*e‰ ¯LT± ±Š[bÁoP¡kÊ‚À¨CåKT½ibU¼& ŒË[:„k®Ò÷w"íŽsŽÃu²Ùl8¹–µ–}ûö½P(ú(õ`,“lÕ<þ}­'ÐZŒ¥bF„òÉÐÖ$Ú.Øä¤j±–6v_kËûòVš¼m´çfƱ™m+ëmü+YrpÃ}*"""""2å(à™<¢È«ôoÛ¶m£oípÀ×ÓӃ뺜ବ=’* OMYoYÀûº+\¡¾¶ñ£¦Ò1ëïsï½›hÏ`}Föa®Qß‘¯ÇišU¯ã_ÍÐÛãïC‘)«nÞóÏ?¿å¬³ÎÚ3ÞÇýᘧ€W&¹"Á$P}ÿõ_ÿõøyç·%“É…|/¼ð<óçÏçìÆs¸gà 14ê‚°Ö–¾¶¶Â=€Ø½À!› zað;|›µeË–…‡¥ ×”v£ºe_Ss¸Û@¯›yŽãð̳Ä÷£dCOW×–|ð  /Ü·ÅzÿÞeodöÖÛqü¡q¯ÀFanîúáõ&øÞ\gÃPwÄ…0èq$•®‹BäxÛ…áÀ‰ê]“è<ÜŸw<Ûä4Òô›¦Âø'ýø<Р·Æ  ÷¡ˆˆˆˆˆÈ”U·okkkþ¥|œÈK$й @?Ý´iÓ§vÚG¢SÍwïÞC.—£-ÓÆé©×ò‡â]c.4ì–Ùòh–xÝ®-•g–®¥¼jׂ1&ÇÖŸ,- ‹£­«XÅˈïÍ8Z3œÑt&m©9tww³sÇN'è@cŒaÓ¦Mw=á>-0y«7kÿLf.½G¼ŽÌ–ߌ{%†0P%V͆½&ìSþýpA¶©üä£×&Æ|xÚ2ØxÐ;p Ù¹˜æ¹õ>þ•ì~\ª·Ç ùy¸'µL&ÓHä_~¿¸øb6›-Ôã ¦ÓéåÀ»€×³öð¦Žðµý;àg¹\îò""""2©¯Èä’e½“n2Ü«zw¦™É›g½ÇuyôÑŽáýd­O_ϯýëÛ,“ÿôüqï+ÞŽ?^¼Á>¥¼§n¼®9yžIöÒ­0ÁšñGN¼V6Q[ì~&¹ìwýT3ýÇ¿k*Œ5_¯³ç;™^O_¯“çúà ~¸K¸Ì+ÂuÔ•t:}z:¾xø*p6°pÃËêðº¯ϧÓé{Óéô:ôEDDDdº©ç€×¼Äy©X‚é©ò@oÿÞ§Ÿþãí¾õ±6ù¶oßΞ½{™ÝÐÊ3.¬ñÀ÷Â5‰jÙ‘“­•­¦lR.›|ÍXƒ‰M²6üpYkÙ¸qã/³Ùì.J§çGáž*ãï64‘;åSXãÐÊŒ­Ü^ÁThµ`-’÷³c´s°‰IÖ&Z¹kKÿi—ã6LÉñOz„ Ÿ¬Ôæçá>«G­ÀbàTàBàrà6``ªZ:ž ü†±ÃÝZþPsð›p™2u] ¸uüüÏžâë‘—Hݼ6l0¾ï·Ž÷²aÃWÃ.uÀô &ÛêºõÖ[oìîé~Ñúk}¬ïóðú‡Èf³¼bÆ+xsó[k^xyÈ ÆOöáü&Ã[c«_âañpOÞXoW3"ì5ãw.J¿ãfOww7<ð¾µØ°÷jOOÏ‹·ÝvÛ@¥ÉµŠÔG¸7®ñ·óO ÿøwðJã-l"´5ɺ‰ž»É ¶ÊcãÁ®ñ¬upå%°`åTÿj>GTˇF¸¯êV6›Ý•Íf7d³ÙÛ²Ùì·²Ùì…À\à-À Sxì®Nã>W ÂËƸïiá2eêúð#`ºµä8qŒÛÏÎ ß3ÎÔa"""2õÕsïbcL×x/•1"“]ƒyU[¹¡¡¡=÷Ý{ß:Ï÷|k-¾µ ‹ÜqÇX oM_ÄÊÆWÖ¼‚!o¢/oüŽ nITïšÊßÇ«3ã“´èç —©=Ü]Õt"Ͼ€Ûn»¡¡BîY<Ï÷ï»ÿþuÅbq ÷¡GÙš¦ÖøçW¾›Â‚öƒ²rë»[-¸µ£„¿U+w“½~@qÑ)_yÉTÿjž¾¦·Ç1}-ÜWSJ6›Èf³7g³Ù‹Ãpç÷SiûÒéôj`Íw+ŸÉår¹\n/ð?kXôšpÙ2u]ü_¦QÈ›Ëåã.¹\îw¹\îf M‡ˆˆˆÈÔWÏoƒµãû==¼ÿ€†]êDð&ˆêz衇زeË]¥I¤|vïÞÍ}÷ÝKCªOµ}šùW`Âÿ†×6;ï³´˜–q¾˜Ê«yKA¯)V_©zךX_SÞ›×VjÍ02Ø-[ Zœ®XôyZâ‰'xðÁñ}‹µë[z{{wÞtÓMßöQÞ{ÕNõñwšç0pæ—ðÓýÍÖÄ{æŽÒ›wÄíöà„ºÃ;¤1CþœÄmn›ã_Ixoø¯LÝ}³X ¼øðX&“Ù’Éd>Éd†¬l6ûà5õà¤ÓévmoгÃuÈÔözà E»‚Æt:}z:>`¢Q™âê6à]½zõfcÆ„÷ׇ©'Q¤V$ìÅÚßß¿í÷øý7ŠÅâP<ä»õ–[Ù¶m‡7-åoæ†F3¾3GVóR6Ûpõnr‚¬J—á‰Ö ‚Ýñ„»¦‘¿]ô9Ž˜q[·nå†n ¾ŠÅâÐÝwßým”z¯9(Mêcüi]Æàk¿€u›ú“1±Kib¼ ݶüþmg¸MÎþΜeÓeü«Ù|Fo#|&Ü7SÕR‚~²Ïd2™Ë¢+³Ùìz‚ Ù:êxÛÞ>EÖ!‡Ö|à2à;ÀÃTžHñtàn`ö4ÞOn¸î /:ëCDDd0Ú"uñ:5@0‹ —ÚaguÖ»W­Zõqß÷ñ<Ïó˜1cùÈG˜;w.›žäk{ÿ‘^¿wÂ+¶ñLÌŒãÝÃŽþ½™À[O‹“泋?ÇÊæW²oï>®þö·ÀuÇÅu]žxâ‰kî¹çžŸ;€N ¢¾{¯NhüÍîǘq÷ç1…Ü”xئ Cç| ³¨}ºÿhþ¸To‘ü˜ z·.e2™øñ™"hV‡„[qÿ|4›Í„˘ü'pLµõd³ÙIù¹/NßEm¼=¹\®5ñØZ_Û¿ÏårçLÖc µµõ|àªÑÆï¼\ÞÝÝ}s¾DÎþ8y€`¢±IÝšmÇo™\.gä±ú‘!""2õ8Ú"“^N‰ªÿ‡?üá†Í›7ßT0UŒ½½½\{íµtvv²rÖ+ùÊaÿȂԠ¯ØÄþ#1[²Z·êu¶Â²Æiaj!ÿ爯³jÖ‰ìÛ·oçÛôöæÂÊÍàôü-[¶ÜvÏ=÷Ü@ù©ùEê?Ü›Ðø›Åíäßp5~Ëâú´,¦xá5¸‹O™Žã?š÷é-’ûÂ}1%d³Y/›Í²Ùì†pRµK€O{cw}poì’Íf÷o¡¼÷f½8nЬã@üˆCî,'ëÍ"à§¹çó±¯~Å4šxMDDD¦·º x7lØàø¾Ÿñ}?=ŽKfÆ ®†]êPR žªì¾å–[®Ù³gÏCAÈ„|]Ý]\uÕUlÞ¼™#f,åëK¾Á‰3O:à'`ªýoá0ÊuRs;ß\v%K›—òü ÏóÍo~“®®®²poïÞ½ÝvÛm×»)š?•*7'4þNÛ2†.ü.Þâú@Þ?ìTü·^‹;wÙtÿj ª;ŸžÆïO‡û`p*odø^EP¹Y ÜõåÍf³ORŸí;æO‘uLæç·´ÎŽ‰Ã ‚ÝwÀ2Î'¨îŸêŸýÌår¦Ú8Q£EDD¦¾z®à=ÙÓcŒÉŽãÒ4iØ¥Nù“E*´ö[kwÜ|óÍ_éìêzÜ·±‰¦r½|ûÛW³aÃZçð÷‡÷´]‚[G¿ã¸¸\:ï½|qé—˜Ó8‡õ®çŸþùŸÉårÃÛi­OWW×ã·ÞzëW¬µ;€ý§åÂ}åO÷ñwgµáŸ÷ Š'ÿÖÔÏø[ãâ¯þ0æÿŒ;«Mã_Ý>àuÀ³Óð=ñÙpÛ÷M¥Êd26¼lÌd2ßÉd2¯nËf³½ÙlöýÀÿŒ=ä4àßb߯£¾ûñŠ,"wFÕõ Ó`mI§Ó¶ÚxL‡”ˆˆÈÔW·=˜6lØÐròÉ'çÆ3Ñšµ–Gy$½zõê^ ½ÔñkÖôhœA0‰Èü¦¦¦#/¸à‚/¤ÓéUQ?Ö¨7ëYgÅ;Þñ\×å©þ§¸j÷·x±°uRoäÒÆ¥|ú°¿fŬxžÇõ×_ÏwÞ‰ã8¸®‹ë88®Kooïã·ß~ûßçóù-§.÷0µOÍ? ñ·»ŸÀ½÷1Ý›'õFÚ9Ë1g\YôJí–w2ùOC?Xž&w·M…Iôà­äàÙl¶#ö˜¿¾»Ï¥Ùlö'ámgw%2‰{ðn#¨Ø‹´ær¹Þðqó=5®f{.—[2YÖÖÖCþ~ÕÝÝ]/Ÿû \’¸î‚I; zÑ/NfVYÆAë–ŸLæ U^9˜aA]RÀ+Óüu…|3 0¿±±qé¹ç¾þs³g·þ‰çyøžçaßÒ¥KùøÇ>΢ÃQô‹Ü´ÿF~¶ÿzúýþIµaÍN3ï™ ow1 N;vìફ®bË–-¥pÏuq—žlÏCwÞyçW†††¶„{Ùðº©îÐøã±ÿ óß×a†ú&×–5΂Sþsâ»qRÿ ˜Ü@0‹üTvA[†)S¹[CÀ AeúG³Ùìu±Çýˆ /Àvàøl6ÛÞö}K§À$kvOЯöó½Vk1Ù'YÛ¡m£ÐÙÝÝ=·^'ãýeàÉjï+\·¸˜ žÔRÀ{d.—Û:Ê:V«âUÀ+""2uƒ‚º4Ñ€×q}¨‘©òÚM†|óŒ1‡yæ™_¸`ÁEÏÃ÷=©Ötè»zPÆßöwÁ#×aŸ¼ã½¼ãÛ+/ÂYý8Ís4þfp-péݾTåM©ž»5¼‘x¥nð AE#À³Ùì߇·]D0ÉÔ°Ið~ ¸â¯æË¹\îOÖc µµõ}À794½xwŸíîî¾®^_þWøõ‡ŒöñÈ'®»xG¸Í“ž*xEDDä`†uiÆ M'Ÿ|ò *xeš¿~ã§ë§¹ÀÂSN9å¢eGùa žç WrúžÏ’%Køð‡?̪W…ý…ýÜ´ÿ~Ýy½þKûÒhq[¸ íB.šÿ6æ6¶1<öèc|÷»ßeëÖqÝ Ôs\×q1Æ mݺõÚŽŽŽ&ÔÚ䘞§å”ñ÷ûöaû)l¼ ¹—v šÒ˜•ãžôœ–yÿƒë„AÉTé;Ÿ'˜<ì_¦â`3à-µkÈd2¾Þ¶ X’Íf½L&ã´/ˆÂßÉð¶Ôx÷Û÷´køðöwr.—SoâÉïa‚É¿ ŒÈϺcßÿSø>áÕËÆ*à‘ƒÔ¥ 6´|òÉûðŠ^ÃÃ!_ÐÌæqħ¾rå+ÿ&ÕZ„{^Ø—Õó}N]½š|à}ôÑX`Ðëçîî»ùmçí<Ù¿ñ>é•Í+9î8cÎÙ4»31Æðì³Ïò¯ÿú¯¬_¿× C½ð”|Çq(‹;7mÚômÛ¶=LPµÙE0ÙXžéî´ñ·…~ügîÀnºvÚùXÌ¢“pV¾ççá4hü¡ÕÀÇ×ùv<¼—:8Ýz¢ï³ÀÅÙlöñL&³ø)ÁDjq÷e³ÙׄIðÇŽ¨é…Ùlö¶ð¶²^¦“5àH§ÓwgÔp׏\nWø˜–pÛÇxÌ=¹\îL½%Ô…~‚>»Gü1c4QÀÛ |øE½m¬Z4ˆˆˆÈÁ êÒTðÎZ½zu¿†^¦Øë8 ùf‚Söç666.Y½zõGgÏž}޵։&à .>¾ï±úÔSyç;ÞÁ©§žŠ1‹eo~/ë³òß¹ <Ñ÷8ÅÎz‚m©6^Ù²ŠSÒ«ù³ÙÆüÆáºàá‡âúë¯ç¡‡ŠõYupœápÏïé鹫££ã{…Ba[øË|6ü%°€Â½ƒ>þ6·óýø/>ˆÝÑýØê´yÎaí8KÿwÙk0é…ÿ—Nð9‚ª¶uöÜ ª¿ÂÈÓ°§”DÀûçÙlöØmKç7ñ°×d³ÙûÂûüxWxýÕÙlöSáõŸ®Œ0ÉÞÕÕ›cY’Ë嶇™¾'¸c<æÔ\.·Ao“^Ø~ ¸º–m[>A0 [ÝQ¯ˆˆˆÌ``Ryï{ßëlÞ¼y~6›uªÝÇó@yÕæ A¸ç‡9„ão³;ñ÷>…ßù·±±ñÈ—óÉ …-ÙlöŽmÛ¶ÝîyÞ^ ‡RÅæ#{­*ÜÓøËÁ±øðà˜—xÝÏ?~@pZö´•Édöóñj†+{'»t:Ý|'<6'âÀ'r¹\A/ñºóp.°W»BDDDd|!À¤µoß¾“[[[ow]wµÖšñôc¨Q´\Ïóötuu?þüGtXÈ{‚S{]JýY› ª:3Àì¹sç¾²­­íœæææ?I¥Rm/Å+‹ýýýuvvÞµÿþ'(…z½Á^ÔgÕ / ö4þÿCk5ðfà¼ðëÔÁv`ð[‚>ªšô*”Éd¾ üí!^Í׳Ùìgêi¿¤Óé3€¯¯ªñ!ŸÍår÷訪[ß®:´+DDDDÆ÷Ëÿ¤¶gÏžã[[[hhhh=TëêîêêzÕÂ… ŸÒ!!ST4iaŠ èk èÑ…}³OãŸ7oÞÊÙ³gŸ6cÆŒ•ËÇi:OÀ÷ý|¡PxapppcOOÏú}ûöm¤tú}¥P/OpʶGz­jü奖&èƒz*p"pÁDmsj||ÁiOôCÍi׎”Éd ‚ÌK9™ÚÚüølrò¶º9Óé〷¯'èÓÚÞÔAðǡ߿ÈårOëhª{…c|v…ˆˆˆHíê¢ÉþöíÛX°`ÁúT*µè`UòFË)‹»vïÞ}Ú’%K^Ôá Óàõ½v¢ŠÎèôý¨²sfìÒlŒ™ÕÖÖvÔ¬Y³Žmjj:"•J-L¥RmŽãdÇi6Æ4RšøÇ³Ö|ßï÷}?[,;‹Åâî|>ÿb__ß3Ï[k£ o v‰*5£Óð£ŠMPÕ¦Æ_ã?ÙÌ"h%Ð'&6V$í%íEDÆ«‘ šý‹Ú""""ãû…¿.ìÙ³gUkkë Ü®!züÐÐОîîî×-X°àq 2 _û† ²Ó¡4!WŠRugü5…×EßG÷‹xüðM†wC•™Ñ÷Q•f1v‰«POã¯ñ™¾N'h·áiWˆˆˆˆÔþK~ÝØ´iSëÑGýTCCÃÂ]ÖÐÐÐîçž{îø+Vtë0iþ½D›¸Äÿè:'v‰óc—¨3ây‰Kt_P°§ñ×ø‹ˆˆˆˆˆˆLè—ûº²k×®cçÎû»T*uäx+ycm¶ìß¿ÿõ‹-zF‡€HÙûA<ìKVyš ÿ&_QHçWø×OÜ¿¿hüEDDDDDDdºxþùçççóùvòùüÎçŸ~¾ö¢È¨’á^¼š3~º~µK•+>+…‚¢ñ‘éæ¹çž›W(ž±ÖZß÷ýÑBÝèöB¡ðÌsÏ=7O{OdB #ƒ¿j—ø}Eã/"""""""2ÒöíÛ—äóù-£…¼Ñõù|~ËöíÛ—h¯‰ˆˆˆˆˆˆˆˆˆL×^{mC>Ÿ¶RÈ wŸ½öÚk´·DDDDDDDDDD&™íÛ·–Ïç7ÆCÝX¸»qûöí‡i/‰ˆˆˆˆˆˆˆˆˆLRßÿþ÷ŸWð>ûýï_•»""""""""""“Ý“O>™Îç󇕻?ùä“ií‘:±yóæ¶|>ÿÍ›7·ioˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆÈTнnESìëÙ±¯[’÷íüîqF{LDDDDDDDDDªQ€øè^·bp°˜´s€Ã€! èžžn]³é±Ç;­k6ùÚ“"""""""""þÑ~± IDAT§€÷%нnÅ“À iQîÞÌ~ üøeëšM[£;¿{œiûØÓV{UDDDDDDDDDð km;påˆkÌ™Ñ×ÝëV|øÜWÑüøFô*äPÀ{À¬µg±cÞ·ÝëV¬‹Ý<@P¥ ° XTêž®ö}ÿ» wǧ££cm{{ûZí ™jð Z^€îu+~ ,'j7[€çÛ3€v`%p&`cãÿúWÀÿj]³éyíýÑutt,nNjoo×±þò½_è9DRÚ/™¿2ÀÖÖ5›¶vÇîu+ŸV+àsº×­¸¬uͦ­j×PYGGÇåÀZ`¶öÆ„˜*_;£Ü?£¯ý1n—ú»ŒÆÆ.‘ jiiy8­ÚÇÞÞÞ“µ—DDDDDU¨Ö Þ¸îu+€Ã€½À, ]©"·{ÝŠfàcÀÿæP–<\ÒºfÓ#…Øo:­U»gįWoÍï&ñµSá_‡êA_<ØóË­ð¯MÜ_&ÏÏ“¸8iüýÄíñc@Dj÷` ðïÀG‚¶jjii™I0CU½½½ú|#"""" xT­oçw3Žã ¼x%AÀ›Ž^$hÙ°øp«ïûŨ2·{ÝŠVà‡À)¯º¾ø­k6mÖH:::*އÞQߢ}ãÄ.nxI%þ¡<Àó€bøoüëøu~ì {_ΟÑXº‰ñÆ{´ñ7‰ñ«6þñï£ñ÷PØ+RM#ði`>pA‹§K€Çîs9ð-‚VO—½À5ákKÛ/""""2M¨EÃK¤ícOÛp²µ¯T›4ÆnžMú|Øë8Î5ÝëVü°uͦç[×lêî^·âCÀÚ6DÎ>GPÉ"2ñp.ä5„Çg#Aoè )v}t¿(Œ‹‚»(Ð+C@>üw0¼.º>ü%«;åпC)´mˆsSx‰Ž‰ø˜'«yad¥nüXˆÆ:^¢ñ¢û‘2WŸ¿þ$ðîð3B܇€§ŸRjIÔ|QÛ?5´´´Œú~¨ ^Uð°ñ´h«xû B³Š‹ /QEäsÀ'Z×lº {ÝŠF`ðN`fìqï~ÒºfÓ´¯XQoM¯ù(œ‹BÝx˜;3viš?ñ¾wžðÎ7¼öŒ¥ æ“nJµÍpüf׌_tŒ7d°a®ãbk­Ûà{NjhÐwúsƒÅέ{ºžýùoþß=ßù÷Ÿ?Ipªi?0»ÄCß(ìM¶rC7þQ€?ÃfZ|ÞÖté›^wây§­\yøœæ%K[¸¼9Åì9­³5¦ëƒõ†Çß·Ï£«;»«¿HÏÖ»_ØÞÕ¿í·ë7nüñ-w>FîAcÈ[;<þÑ4þ"%?Þû>ùÇa(UªÆÿØv;ðmÿÔ €WDDDDjýe_Àx{ðv¯[q=AÊ`;°” ܘG¨Ay¯]*Tnj]³i÷º‹€oœ¦¹ø«Ö5›žîã¡€wÔ×z¼Zw8Ô »–èòWzÏ)kÞvÞ;Ï4Ûï™1œ±Y;|pF_c(àßãW„/‡¡¦ÖÁm=…g¾ÿË;~þÏÿzýœN]ú)û’U½rÆßfY;껼è¼?{×Y'Ÿ~‘ O=|næp[·>Ö·Á@Ø`8liDlüçI0Ô&X¡q0Æâ8&ÕÈŽ}ÙíOlÙñðÏî~ô¾ën¸ã¿Âñ0†kéÓø‹”¹Š ru¼®>¨íŸðŠˆˆˆH­¿ô˘@ÀûJàuÖÚ[1EÀm]³é¹îu+NNÞœI©Š`?pUëšM_—±¸—ò*ÞÛ€´®Ù´s:‡ÞНñxÅf#¥ Ý Ìþè%ø¹¿¸øãóÞexÅà!Ö‚±±î¨AΖÌt«­vø~&úÊ„ÿ †Âº)öø-›¿ú¯¿ºæ{?¹á1 È„½QuoUtìñ‡ú³€†÷\xöiŸzËéo9é¨Åg49–¢ºa Þ¿5˜ð†”kÈ[‡GŸÛyÏU7ÝÓõ·Þµž Ôí£<ì×øËtÒHÐ’à à‚Þ³—M`9?v§÷´+(hûëÓDÞ–––Ãí¯øQ©··÷äI¸© eÀb‚9*ÇmÐEP±¯ÂÏ‚"‰£ÂÏ4³ÂcÉžlÛ5ÉŸ¨iû16ñ¸š¶ßÓ…ˆˆˆLù_þåÀ>É8ÞÎïg¢ÉÓ*é^·¢øðu 5vS?Á)‡ÿÙºfS±{ÝŠµ“ÄïóÙÖ5›þq:‡Þ¯ïd°Ûþ0h»û?¾õÉS—´œíõ»ø¥¹Î¬¢c NÎ9®‹ëNªò[~Ïß+b­Å·$–gb¥žAèë5¶xoËýþÌ÷^~5ÐI)ìÚ9$ƒ>©}ü£6 3Â_üÒÑ¿×}þcï~Ó«V¾sö ']ò°Ö†‘®5‡rük¤\—ž¼Ÿ»ù?ÿà?|ï§@Ž èþ¤Ô¾Aã/SÙ§+ÁrÿžúèI;Ý·¿¢ñ¼---3ß5?æeô]àµeHæ(µó)Ä~þGýÚ£Ÿ ©ðgY3¥žð©ðûhÞ€‚?fÎ ¿øgàúIò;Ä˲ýƘë‘)G“¬M2­k6 ×v¯[ñ ðcÃmÍÀZàO·§Æþ൅R°{Q°Û´uÜð½¿;®yàT°Ïa£>ºQ•¦1¸ÆJ¥0)CÏÖçyî…ظ½‹î[yvßÅ®]ìëƒÎÁà¡m3`Þ,HÍYÄ1sgÒzØRV6‡£ZÆì#ŽÆz>ÅbÏ÷±X°¥€×ìqÿtžyýÀo¯|ýÓ}3n¿øcÿ@ôvR z£ / z¥²xÕn4YÚ¬püÓ@ëM_»ü#çžrìÛ\[Äó= ù¡x•®y ÆßXk° ~Ñ6;NúÒ׬øÐ»wÕ‡îxä¹_¾å3W~èÑ͆¿ÐF“³©šW¦ª™cÜþ,Áo£ÔâéÍÀÿ ¿žèrµýSHooïõQÀ1øhìûþð³Êâð÷K)Ðì?ô†?ÓLìç]Cø³m.¥³SÂÛ‹á¿áÏÄy¿ì§µvZo¿ˆˆˆš àåú`Ó ´Ç®ê0Æt×Ûoo-ÛUùv¯[ñ‚Ê“èTºnàË­k6}£{ÝŠ9Uï 8ñg­k6Ý9Úsíèèh§TñÛÝÞÞÞñrì³ŽŽŽe§¡E6···o>HË®8Ó¨‚7^µÙþb›æóþß\ùקγç,X06øÚƒë84Ìh¢ó…§¹ÿ¿ç¡ÇÿÈ#Oí¤Çƒ†¤Ü ŠÓˆñxgxŒcê͢g*ÂlN>~1²ê¼ú”U´-{Cƒƒxž?|¿5Ç8á² ïwïzí{/ÿ'‚S2»‚¾J•-ªæ}üf¼î›üÅ¿üàů:îŽõð=”k›±Æ¿Û×…”¾.fx¼Ã÷<cq 8Š>x´Ö0þcqŒã|“âWÿõô/ýüwþ è7ÐmKAAã/SÔáÀF‚à+éöðç}o…ÛÚ€›€Ó+ÜÖ œ¼ í¯OlÑP/}{£?ÖÅ»ÿ˜ ÷‰þí ? ÷‡÷KSšÃÂ$o«üŒü¿“O†ß!^–í7Ƽ™’aÀÁú²6qÕfcÌu‰û,.Þ Ya1[€+1›Ç±î3 úÖJ#¶'¶î1^kíeá¶ŸTe»¯®‹ow÷ºà=§°E:U­k6í'\ÛÛºf“WíIwtt\îï·T¹ËMÀ•íííw'7b<ÛÛÛ¯›è΋=3«üòÖÜ Ü8Þõ$žë2‚I_¨pÝu+XžD¯çxÕn a°{ùßùš/_zæç¡~'è­êƒõÁZchjšÁ@çNnýÝÝÜtÏÃléö™Ùdp0tá0t\ Þ²f¼%3ñ6aç6âgRçë[ð,&;„³gwwÛîæ~žî…Aßúx¾e o9²Õá-g¬æÂןÅ̶Åäóƒø¾Åð1Ç`pð›fùWüøž/^ùo?¿—RÐÛKy5¯B¾ÒøGU»ñño¹ä¯ýó«?ñ–hi03<ÏÃZßb}Smü7wù45s žea¦W,žÉòùM,™ÓÈÂÙÌmqÉÌL1«)åûò>Ù"û{=v÷ØÖUà…½yþ¸s€ÝÙ!]ƒ1–|–Í©>þccŒë¸ôzÎà'¿}Óßýä7ÿï?Ãq¼šWdªx3ð«ð½<² Xþü¯¦R8ê7kûëWKKK1±?ÊÔyÀÛGå †£@~y…ÛßûMâºø}ž–PÞÆ,ò%àï&ÃÆ[k_–í7Æü"""2%ƒõ!%ùaòcÌ™ám­í>=ŽE~ X[KUo.ÿý!ÞWÃÛ“X÷™ŒðZkÛ Bë#'²ÝaOÞ-§lu?®¼°CEa%ëuãXïMÀeíííÝáãGŒg{{û™ãÝia°»vσp{/ooo¿±ÆuL4à9+l×ñë8 w)µc˜ ,Ú|û®^àï?/ÈÂŒ ª&Ç¡iæLžt=?¼ñîܸY3 )×Á¤ò§·Q<µ•¡öÙà„!îDù–TG–†‡»hº¯[ô)z>ƒyËVÍã=o:—e«þ„ü@ô™RI‹ã²Çi{qÙ×|2üE?åÕœQÈ;]ƒ¾xK†&ÊÛqÌ~pÝkÿdù¼WçóA¹®õMµñŸÑd(ú0§ÙåկȰzù,ڗ΢µ9EÁóñ¼ §nðÞ6r‡ÂÖʼ®khtºû‹tlícà }ÜÿÇ,]ý †Fk ¦©±‰‡^Ø{ÿŸ~ìÿ¬%øCP¼mGµl©a&ðI‚‰VW%nûpø³Ÿðuý`5ð4ð `kxÛ_ßL<öéð³À·΀Ðöב–––〧F»O¼O+bßÿ x?AðKø3í+fŒß[z€Ï…ÇGäà.`Qìº?š o­­¸ýƘ¾ðöqo¿1æ;±åWÜ~cÌ‘) ¬)Þ0ܽ›Ê•«cy8s¬w²¼aÕî•T®Xk»ßUóv¯[ñ: »uͦ‡kypªþÛ¶q ðÖöööŽ x;::ZÃ_ªÞrûü‡Ao÷ëšÎoô?:%¿… bcÁ™§µ¯¼mí{¿íx3üò´>˜ÑÜ̳®çŸ~t#OìÊ3k†ƒë8NocàÂÅøGÌHè¥änüo‰Çœû™yë.fþgŽOaÈãÄ…3ùÐ{Þʲ•«è懱Æ`Œ½©&û†µÿñ—÷¬ïØH0«t7A5gtÊþt ùâá~ÐâÓê[;çÕ'¯<ñ7_|ÿ·›Œçz~0kšµ¾IŽÿã»ò45|Î]ÕÊ…'Ïᕇ7Ó_*®GN’6'iLø:†æF‡'¶÷së#]üþÉšÀ÷|NZ4rü1Æǘ<®wþçô—ÿùÈÆÇcº|k£ñÏ£_êß½Tn1Áxw…_ÿ¸$vÛ}ý÷F œ«í¯---'´8a´ûÕyÀÛûý`A%v%ÜOy;ƒø6t )õœ›ì}1pÃdØxkmÙöc¯r¿š¶ßS¬ðØÛoŒ¹™’áÀÁú2"$8%ÿn&îF5Æ´±îµL²€— oîÝŒ?ÜôËÆÛ—øÂݲõœ ]¶ýµ¼a¸{ ã><þÀ™£…¼Ó8à‡»Ñ)ùs…7^ý…Ÿ·4u1€o}ŒµX, ä÷ïà›ßÿî¶‹†ÆXËà%K¼pQüÀ×p¬°Ü̯w1ï—;i0–Á§5—¼ÿ=4Î9Œ¡âÐp@½†ßn-þê­ŸZ{ °› š7:eº…¼#ÆßqÌ\ß·-WÿÍûÞÿñsV}¬04¶c°¦Òø[ǥх÷¼j>oû“6†<‹ïÚ]è8†78nùïNnÞ°<‹ï+Ž¿1ÆbÓÔØÈ5w>ñÝO~óßä8¦×÷ít™:fT¤WâQ>!î6ÊC0 j¿~Þèåg{«¶òkiiYMPü.FiÍ©ó€÷a‚jìÈ›€[«Ü÷í·%hÑÔûYÿ#@Ò¿—Q E/"8³îeg­±ýƘ[«ÜwÔí7Æìªò¸ÛoŒ¹™rœC¼üµxÈwRX ûr»{œ÷¿‘ê½fï /=£<~öx×¶e+ÜÖýè(ë½ñ ì«“j|c퇓*¥\2ÜË €%¼ñÛל{„s±ï{x^|ðinrøõ¯~ί¸Š§wåhÕÀàeKéþÉ©¥p7ÀšCðû_|¹áº²o\Äó?8™=—A¦¹‘çvçø›/]ÍÝ·þ‚æëƒïãûž_äÜ#œ‹Ÿºá_®!è/· Üöá¾ëÆ);þø¾}ßÕŸùÊÇÎ:þcùü õ½¢µ¾gâãÿW\Å;²´ÌpùèY ¸é¯Žç¢Õs(-ù!ÿ‡»¾¬«P´¼±}ÿö±WðžÓ0kFOïÌŽëûÆúž°?ë¸Ýû/ýß·³Íô™Z€_T¹ÍæÇ¾îÝØÉŒ,šå²æC—•¿5ŽmH9fsWáÙW|àËk*ö¦ÓøËÔõ¾ðgòÙ‰ë?HéK ÚœNпôÝ”BÏJ=ho'è_û‹i°ýMA–Ÿx¿œô“1†í’½R+ù ðà«áçU@sooï@byõRÁ»3ÜfŽ] ø$š0vÄ'†pLÿ 8,<ì(¿Ïøár£‚7ü¬ù­É°ñÖÚ—eû1ßBDDD¦dPp°>¤Œõáù&à²ÑZXk/§r8pV2=Ȳ.£rõ먭j xÇ iG —kjÕÐÑѱ–ÊAm--ڻĘo´VÛÕÞÞ~åXã.£ZõóòöööÍyíííS©ªÏad¸·¤ó7ßúyÓ`× ¬TíZËÌæf¹ïN®ýÕݸ3é›ÓȳŸ;o–x/_°;òE0ü\ú=NþÆiéÊc yÞuÁ™ÿgç ~‡µÆÁ8ƒMsç^ðWï$8m7òùSô};úåoFø:™´mýÉçÿ}Á,g±çùß3Éñ÷R ÌÍ4ò©7N묅¢}ÙƒÝ?Lк!7Pä‡wí 3W Ñ+ŒŒc]×1{úýK/ýÒûúî ß/+„<"õâ"à§=Õ#»€•áq^ÍáÕŠm±ë<àRàgÓ`ûÝp{ëNKKË7 zo%¨ÞYå®?!&ç]½½½‹+,³È(í&QÀûAïäÁð=»>ïèçÜPø9ljýÞ2@ð‡Œ# þ(¥?.+ ö‹±ã¢%¼|þ?{ïgWYßû¿¿kíËÌdr!!!܃" l¯U¼¥µÇZ±UÏïOOí¡j¥í©¯zú«Åö´¯¾N[Å+ýÙ‹XZ©U¨EE­ªx@Ñ j"’’L²3·½×óýýñëóåKã¯=ú˜\¿ˆ|Ã0 Ã0V¤Pðhð!¹t.‘RD®aæßªo}ÿ‚ÕÂ7CëÇ¥ ÍÁíá]óqàŠÈø¦b½¬Å» f$¸wûm³ƒ9Ä]€V«5æwô§r¦ë|Ý|ÄÝp.Ûf9—«í#[4ÔʪœüðM︾9¶w@³d]Pe¨Yãþ[þá_¿ÂªUM|Ñ üÇŸžçÅÝ£±+sü·ðËè†ÎPÊo;—/ÜÌÀÐÿü…¯ðùëÿž¡†€fˆvѬËàøž‡?ý—×qcC˜“F˜#Y¡õO ÕÄ×ÝO?òÿ~xÓ€ž˜u:JÖ•ÞúK­Æ/¶ÖóöWma¨™2ÙqKNÜ ?‹™ê:)W½ìt¶ž¿­Õ§ÕíJÖíê¦=ñ§~ë‡ua.V‡¹I°¨cùñ¼;µÑ³|sX>“ð7ŒE×÷,O`Ž&]+àú{£YGéÚý$>Îg)se¸¾6ðÖÙþ>þ®×ùÓn†áá'0ìÞ%ÂkÂÏkj–?•±›ò—w¹KõÓáúÿSq:¾éjÞxl/’vÂûŸÑ<¶Ù‹Ê—õõ‹ÈŠÈJ¸~Ã0 Ã0Ž"†À»#ˆ—óåê–o}„…uøGÿú9Fß~„®á[Däªy+7~žúeãÎ5—ÎpþWÌ7‚ ˆ¼W/öBƒs¶_ÌÄÛ[­Öu +œK¿k¾<ÄJ«ôŠ»€wÿË;>20¾w@]†¸ QGSºüݵïá;÷>Àªw¾å <ð’“¼°»×® ˆT\ã¿ÊÖ 9žH8OÇ_t"·^u«šM¾÷ãø§¿? qˆsˆëâœc`bßÀîÿê#øÇW²È›×¿‰o¨v¼Âªþý[>°i@O̲Lq™Äõÿö½Ш×xóKNãEOÙÀá‰lI »}~.36™ñ¼'­çòžJ³Öà»?ªÖו,ËtÓ œøÃ¿û½hh2êßdåŠüÆÊåõÌ,bþ'¼C÷×(3i7o¾‡,èGƒÒå·¯ÿœð9ïFŸù×á]Œÿþ~tÒR½àáááµáš×ï®ÅÇõ’á#)ž-»‰SDˆzøä2ºç¿œj5†%›ø'Tá5êùà¥áûœŸâc ¾Œ‚çAà0^,¾¿ø³pÏ´—Ìê" º~y©ˆLDû/øúE¤a†a+’GCà½zÙ9€sèå‘ê~| ý‚Ý2çí\u”ö9]U/eŸ~ën nØy\¶;y­WôY¶c¦ÜÜyœË øÌÞù\ë±ôy­ãë€î¹þ/ß½zrï ‚§¨£žMðþ÷¾›]SªqË;.à𩃥°;O±5tÿ8¿W¼4Ñð¢ú½(*å¶*~߉½áþa¼’êo¤øFig†ŸaOþŸEŒóq|ÓÕ3€ûðÎÇl…^ÿ.¼ÀÝ ?’~¸ïø½/píž½T/zxxøŸ€_ o?€„~[Ÿ¿—žæçív{OØ÷åaÙsæ8ÌWÃØ÷´Ûío-“?óò?]ú󻾺åú³_U‹ë·À}‹ë_辆a†a,Ñà‘ä–Eî·ýQøËÓ#™» ¾QØÑÜwë Ûö[>ºq7pÝBwMÚÖÝ^6[ IDATå9˜iÿ ƒ¨},?š¿Øð¼§žÞ/’]†:PÅe©ð¯û ]elcsÁânîØÍº¹WDDÉ]ºM‚“7QœD¯â½ Û)ˆ NP¡p÷®Þ¹Î­ˆlP>õæs8||“É®rë§þF-A3ŸÇë2Ç/ª—=÷©çŸ‡jÈóX—û£ú¹{7æXÿ¬ ŸpÁon}ü•SŽ‚J\ÿ±Žrüš¯ûO§UqW¤ º”è_ÞÃÅëøÓZz靿T•Ì)—n=•õkLt\oýerª«¿ùü³®|ÖO¸/‚çQæâ5– ðø&PÌìMÅæâÏÃ8Áòi>¶Øëwá3ÞÀÇ3ìÄ7-Ûÿ…õEÀYKøºß‚ÏY/ôÞÕl^ n»Ýo·Û‰»çâcž3ñ_¿?8<<¬ÃÃÃKü^p,¾Yf—eÞhUDœˆd‹hE¤^&î†aÆ1Æ#-ðn[ä~ÛÉ“z„swsn8Êóvá Û¶Žâ¼2{ï\àn[û,»s¾ù¿‹8—Ö1ôÍÄÇlþÌ[_ö>uŠëvÑn‡F£Á­7ý»&H¹ùž“ßìówUµu)DÝ¿ 'ùzA“|}øÚû^ÄoDa¾Šh´~Boyåão:×LØ{è0w}ñÔk)¸ u]\·ÃgÞò’÷ásöÖ…9[ÎêÇâîp°ö³o}ùû&§¦pÝlZýW7S^uÉ©G¥‘Z,êªzB,‡?3íy-Ï…_ÕH–EÿÜfªãøÅgŸB³‘²÷àôúOŽóÙßÙûÂÏö㜙Èk,W6ö¼ŸI¨í]¾ùºþñ¿ÄÓðy¿Ÿ;ú6|DX.ìþ·¥z‘ívû'áïQ7…Ÿ]§á³x_œØn··Ýnßï3<<üWa»K(#úñQ¼Ùâ.|ó“€Ýív{Ü>^†a†a+‹GZà=°D¯û‘ÌÝßXîH®}[¿…!¢—~ËFŽðüºë8‡ÙÆ9ÖÞܽ»8þ‡ÿó÷Ê䘸¬‹¸.‰ÀO¿ó~¸ýµ”ëÿðIù 3§š»v+Ž] ¢n¢AÔ+_ B­†mú¾r178w%ñî]—äû(’P½ENï?5Ø7%Îâ•([U‚òF4Ïdõ°_æØÏ7S¢gù£ó+«ï§Ÿ4¨’%Â?ü·3ùï×ÝÇ=ßù:ÏÚ|*éàȺÖ8YwŸú†WýÒsß÷ñÏ|ïnÊsXóeW…u¯ºäi=ã”ágOt:*YWâú×’:/xÖI¤©àœ.þˆÕ1~åÓ ûhQÛJiËâ 4yÃú…hѪJš ÏxÊI|ëÛ;{ë/™«éϺúÙ¯zÁÓ.úø—¾yË2®¿aüi¸7âŸ:Ê]œ‰¶ù0ð9à<|3©Q¼ûóX¹þ¿Æ‹¿àã¾<“êãù¿„wǾøìrŸ”áááµø§Sñ —¿œÜ³i†o\÷¬hÙMxÑ×0 Ã0 ÃXa$ÇÒÅ> ¹»9GCØîM°µÏ²~NäíGxì…ÎÖ>Ë.Ç7;;ÒW?1þXqðæîÍA¼ñøÿ}é“Þ&®‹t&Ðîõ4åëÿvæß|îFÞ<¸q7D&äQ Qæ®·m¾<ý¼ûV 7oþòÎ]çœùãùÁ½ë£ò}¢‡JF¯‘ 3ú-ƒÈ»{Ó ·=k#ic€{¾òjµUpÝÚ™àÏ^öÄ·QºxY~.NÁÿn \ÃÐû^ûœ?žœš˜Vÿ¤Öäü³×³á¸E‹»y†®H5jȽ«½ÝàÊ•h[ÁÍ;m,éqô. 2Î)kÖ4yüë mVê¯Ý“cmÞ÷_îOðl¯ sXÃ\¼Æòb Ÿ¥û»”âæGO„ï¿D)Ö=€DßËòÉÜ=×ÿ·áûó€§ã…àÞìÑÏã/þ·2/mÊX†çâ…þ~ÛåÎÝq¼Ó÷eív{Ê>Z†a†a+cFà ¹»7ðÈæîæX"c,–… Ô§?Êçw,8xãh†!`ýÍ×¼åwêãû×™DÕ!i}ûV&:cëÜñüÍ wã8†iïÅåy»E4ƒÿê…Y/æ9©>ÆŸ ·ùv…ø[u" IÑ Z‰lKäu|ù¹'0º¶A§Ûe×Ý·“$Š 8 8˜|î¿û;á¸C,¯Gõóú×ñîíÕúW¼n5ƒ:5©NäõïdlXSç¼³×ÓíºE©ˆcÈEÜ\´|Æ’¨Æ=®öÄ4H2]èí'øJø…qy!•É2Ç–3Ö3¼ªÁTTPq ÃÉÔÀuW]ú:|³½Ua.-ªÁX üj¸/¡t¯ë×ÿ½°ìý}¶ï'„ý–=!¶á¦hÑ•xw?öÿ/pz»ÝþóY" Ã0 Ã0ŒeαäཎþBäÑÊÝ5Œ£I.ð àªõ­Ù‰s^Àçpí‡ÙþÃïSKSþáugÎoÐqW£Æi"‰+Þ#Rqæ–‚np䊂8HM¼ L´­ëqïJ!ú¡7Êé­ºy{DÞ9¸ö¿žI­–òà¿;|ÑŒÄuQU.Zwð¼À»:Ìårxóú¯{Å›.ïd]ŸbÕ'<¥µ™Ng‘ân.¬æÛXÌMK‡®$Aìíi®VmSŸ»[qùöŒÇ>hZ.[ŒÈÛí:Î9ï¤Rq]éfÊ+Z›/ÇÿBh¹Õß0 c6Þ‚o~¸ƒÒ¹½Ÿ³ûÜv»=Þn·ÿ´Ýnï±)3 Ã0 ÃXÙ¯ª^…ïFÜËÑÌÝ5Œ£E.·ÕñÑën»ö÷ÿЧ‡f຤µ÷~ci£ÉO?ž‰tN÷n¯¸KEÜÕÅ@pì†uQ“5/èæ ¥Xë]˜D:/ØRˆ·.#ˆx¹(œ‹À…Ë7v4ü7cãµÕ0>pë“'©7Ùuç­$’¢Y†º.â2¾úþ·ü!^ä sšK™Ë¡þCÀªO¼å²_O´‹:§qý“zƒ-[ÖÒlÔÞTMʬÝJÞnŸH…¾bnIu{÷©¸wÓžñÒèb<$"òª*õFÊ '¯EjÞúkšMñÏ¿÷ò_†Ã\.‡ú†aÌJ»Ýþ >¶ë&ü“i§á³—_œØn··Ýnßm3e†a†qìP[érwß9Ã꣙»kT¹“G.fbd…Ï]Ï0 ¬RcÏÓpÞœ#š1¶ëÇqã+\wÒ_q\©qÆëž»‰»Åk/!fAʈ¶cÄ ×â“/zæå-ÍB£5 ·]±],.»¨yÛš¯©SN8õ8=tˆÃQý5sâHyá7¼߈ix™Ôß0 cNÚíö=À/G‹Þd³b†a†qì²¢Þ(w·G;w÷ÑàÑ£F³«Z­Ö6ûˆ-˜\î*šk}úÿ¼ùâF}în–‘4<ðýoÒ¨×ùäÅ›ó›}F··¡AÜÕiân)ú:¡u¥[%_5¸/ýûX)+ý·‘2 láðnUÞ]¤È–ýÓÅ›ù/·íæÀ}ßaÓ3~ \†ë:$Iù—?Ó_úûïù#¼kŸÉ(,=‘¯·þ«þæÊ½ºæ¦èªS²Lòú×Ó§ž~œ¯íBÜ»‘¸K?q·w9!s7y‹qÂ{)*Ê/(V@D^\y|qáöMÂæ.|Õ¤=×ÏyPØtÚ:k“{®K’u”ɇv0>>Aš··6„½fyâ<Ü ·fqW‹Fh ‰}!o7ˆºšä>¾¡ò~ØWr8¸‚ó _¤Žããi³˜è{Í…â¾3æ¯ý–'­'¡;5FçáûѬK‚C\ÆE»/À?Æ:æv)g±&@]Bc°_¾ð¤WfÝ’u%®­&lÌ7®Aaͦ5ÔÒjý'YÖå%ço~e<§[ùó†a†a†aÆ gÅþ#÷1ÎÝ]÷(^j?Wï–#s¡ûoŒç`¥‘ ¼Ã¯}ñs/hNìKqÕ DÙ·ýû4ê5þõÙ›˜ë9v‘(Ë6Ä$”Q ½Î]ŠHÍÅÛ•ëß ê&¹à›g³–»yŽªÅEÙ ¹»W4oËફ¸WðÍ·™57ÌÁ??sIZcü‚:\ÖŹŒ©Ñôµ/~îøÇôsw)R4WSü•絞±!\s׿–¦l:y-ι…»w)#è“« ­y<ƒöq5ÊÛ…ÛC¤D¿eGêž]èþÛû,Ûj¯“K_ùãùÿ÷Òg\)ê ë¢Y2vp?µTø·§l¤xŽ}Æ›‰J4C!ìõuîŠoЦ墫D¢m)ø–íçBm>VéØŒÅà2ãW$nºV‹è/òæ "è+ò†eŸ¹ðxR kï‡ÉÃHÖl Í:üÏ_~ê•x7ø–Z³­8ž¡ ¾ië/ët34ëJ¥þ wš… Ĺ»‘;»7š!n”¦½®Ú\øíó=‘,=nàÞ&nù~.uÓj³µâ(s~çþÁ ƒÇ¯Azê¯Ù”Lu:¼éy§¿ /ð6)=Ã&ò†a†a†aËž'ð.‘ÜÝÓÃy,–­}–Ý9öýb.^ìGFF¶.t·~ç022rÄ.Þ‘‘‘KÃkë1ðyŒ¾A`ø4ݽݧÍ8üàOh6j|ýìuÕnVý“(Ë6ÊÒÍ=½±À× …ÀKáæõ\…¿}!ú&Z ‚¢¸Jƒ./ k¥©–ñ U7±H Hé0ž555ÌÅíg¯CÒݽÛÁ9$ë¢Ý)NÓÝ[ðï åcúKMàËã´u⪋§Õ¿^cõ†aïÈ^ˆ{7;Hzrw{_Q¤‚¤Õu’T»ÚÏœ–ÛÄB°öÄ2h,è­‡j\Ã<“ró¹Z?Ü·þ­/R©Öß0 Ã0 Ã0 Ã0–=+ñ¸×ñØäîöréQÞwd†m·õ[822rÅ£xÞÛ9ȹøðå‘‘9p×¶HñâÓàë_öü§Hg²¸ &ÞI*ÂÍ­õ^™œ‡{·ˆi¨Ä/Hh”¦…cWrÁ¶Ø®_¥h ¹¹»·ÜŽB@ÌÝ»’h) îaDçj6°Ïú ‘ Äb`71ƒ‹W€Ÿ´4ôÀnHThF:5ί¿äyOÁ |µ%øó/g¨+4ÿóóŸü̦N YGãú ÊšãWY4C,®Jô§A±;x5n…iùºÚãî¥Ç…›ß±ØÛ7÷78vãsÒØc;/¯R?n˜­Ô_]¦n‚ÿ¼õ‚gªwðÖYÚ9̆a†a†a†1oV”ÀûçîörÅ"¯¡Em_Wr«Õ:Üx´Ž\µÐZ­ÖH˜ã^®>’ Bn¯X¿–þ‘Ë\Ê*¬]¾õÜWŠë"®‹àpc£uÈRع~unæÁr÷ná¾ öÍ轊â(^2Z!jxV±I™Ñ+E\ƒMÕèoó1]”é[uçê´}ŠíˆÄ^r'rî0fFG§ªãgë &àºèø!/d‘÷Š‹ŸøJ¦7Z“%XÿW·ŽNÖé ®+qýÓZJ}UsqîÝØU-ù½RfµðZ‰eé Õ*ñ yãµ´tñÆNà¾y½34|‹Ïu¾Y¼éP“F=í­¿dY—W_xüsðK­þ†a†a†a†±hVŒÀ»rw{¹x¦,Ý9¸¦Ï²Q¹a–}®ëwü‘‘‘‰Ú###WÑßý<úÃéaÌÅru¿¹hµZÛVðç1ø· ŒŸ…˼èŒî¡–¦Ü~öqEôÂÌ‚låÕÐB\¥ÌÂsyéqîj±wÐÆ¹¸ ‰„u¹ðë#\ô½ ûD.DQ Rˆ¼ÉlRÆCP6b+²xç¸vUØvÆ:Hë¸ö^ŸÛ«ŠªcËàÄYxo,ð-r· ÔÎÛ4ø4íSÿÁµC³ŠûýFÀÁôZÄNÝÞæf¹›Nwó‘ ‘[·×½]\Mƒ7?Nå}Ÿ\àÞsw¯s$«‡ ­UëïçmzeƱ9x Ã0 Ã0 Ã0ŒÁŠx—Hîn?®YH¯ª^AÿüÜëfÛ¯ÕjÝìè·ßÈÈȼ¦…ŒÛwɵΰüêùžCÏù\M±ùšÅœÜÑÈ~ˆl ­p@ÕyQÓ)Œí¾uÚê°µÌ0He@pß"wl,úZÊ ^©¸k)·B t>‡7aˆœ½^´ÓJÖn ¡}­¢mÞ¨-dïV„Ç(‹w¶˜P¾vÚj/ O‚,CCTY=ùÐÞÁÛdéÅ4ÄóäÕéɪYµþ õáÁ…5WÓjýòi’xnû8gµLO,ƒ¦Ó—‘V¯õæýB0=n,î5ñ#:Wçõ ú{>ª¿ªròšÚÉ¡öKQà7 Ã0 Ã0 Ã0ŒE +ëX¹»½\È<Éàöý`ŸU£ó£ßu®¶ÍÕ ,D!Üp$Újµ¶šåæ-ò†óù£æâºEžâ¥KüŽÑo¾æŸy®¢ Šf¸Œdâ;sÊðÜ#AÅ¡+R]©±¬½eõæŽÝ²ÙZäºM4rßzA×%eæ.=¹½HRж…p(…ˆ(HÓÎ/DGY¼ù¹ä×0‡Ø÷­“WAR‡©qÀ¡®‹Ë2Dá5/ü¹s©:8—RDC ¨¿â¢s/î$(׿‘(éªæ‚®ÜýÄ["Ñ7rôJ»Vz„ZéÉßÍ×kw®F Ö¦5x‹»EñE–s從ãg) ƒ Z¥þše*Ù—]ôÄ ð¼5–f£=Ã0 Ã0 Ã0 ÃXË^à+wWUשêÖ£ôÚ²ˆS¼\U·Í¶o¸†/ϰúÙ>×AZ­ÖuÀ-}V­Å7(».ŽlY722réÈÈÈ6¼°¼ö(”ãjúgñæ"ï¬q ᜮ¡¿Ð pM’g›‡m3í;—нD>5 ñÒgœ}±8Yæ³D'£’0ÙHp >‡`ÆвInXâL^|¾­FNY)œ¼ ØDª9¾…К€)©‰¢âŠ÷>ÂryÔd­Èí­›â\â|Øâœñç<¯˜U2&êIŠNFÔùy̺¼äég]Œwp.Eo ¨¿ðœ ç9çÐ,“¸þi£†¤éüów{DÝX䕸¨‘è;f5éó5væFÎ^\¼Òñ7gëmê&U#z,ãì`Y¼­Vk{q?8C=ÞÖßÎ3Ÿ‡-a..ef¡ùÎV«uõÔ.ºãe/ñ5±|•õÓ×ÖÏôù«¢ “‡ Mùþ¦!ªê\|<ƒñ …ZÄ3H¡•ilˆ Y·>A —®ŠLËÐ ›ûã H¡Ì9TCæk¤ÈiáÏÍ»T½p^ª…0-‰Ô~§•mæy骊 4ÑŽVêïœpúªì”ðg_0!qý Ã0 Ã0 Ã0 ãˆ0÷Ò#Ãð®žekñ"ëñnÝ/ãcfRGñ¢ç¼ îÖ#"ø‹AÈÄïše“Ó7÷ÌÃÃܬe.r]·Ì÷t—Øg±È`]tÖ«ËÀ9èN¡SÔøÑ†ÁY©žÒ#¢ñ AËÊã¥hE3Ç)¸1N!É®8M\¿à·wQD,2Sdéz‘YsݶÒTK£FqU—¯cÌÄ}뼃3Ž>ò ÃÚš[OÕÁ¹~JTÿä¤uCgL¯ Í… ¼1*=Ù»±H À½ñ Gm.î†ûBEA‹—·¯”± av5Ú7>éÉÞíýýE/¡Ì3OAA›5ïàŽëï:œ´~õ”ni‹g0 Ã0 Ã0 Ã0–=&ð>BˆÈUÀ‹Ü}Ø*"ºcˆ(x>ý£æâC­VëŠ#½öV«uÞí|4¶ÎÍÐÃuóÜnËü<¦@mH&†p]¤;‰ªuÂŽµÞ‡¥¶õrP|¶(2A¤ôÃÆ‚ª"…èë…B /¢ TETªbä&Ÿ£Çï¥GdÎÃùÊH—¦j°ÔY=—ìX[ƒZ\ÙdK:†\{/ð¦Kèg`,ð¦ÃéÔZuÙ´úÓ8²‡.´ÏýQ¬ë‹ °±è›†û&‘ªWR â¿LsâæÇÉ…^¤¿«¸7#¸pt/@Ž•4Z£RºVëØÚ|ž± ^Ã0 Ã0 Ã0 c`ï#ËVæï&͹Ø""#‹=hy[ôozÖQ|\Á³ls`çp5^hÞqów#°¥Õj,ðØ×ÍsÞ·,¡{%n²UkvÇê’u¼ûÐùXIëìZ]g^O’—®jé˜-³kƒ£6ÏÙ­ŒXuÆ–Ù¼¥“²~5< O~ŒR+:ïþõû–Î[ßÌ­*ìÆß>dñ){á fÍáU”]Ã5¾8Ð U‡¢4»c¹¸»T›¬%k››éNM«?iºø‘{¾ïuL÷Ý–2ÂÃgø*$3h£ H‚$^Îÿ¤él+y¿Óo½¾—¡ÌßÅ«µA*õwª¬jl¦Ó—Jý Ã0 Ã0 Ã0 cјÀû""Dd+ÞÍ:—£vð:i-ƹÛK«ÕÚÛ3Âñoé9‡Q¼€ú:¼ˆ:WíÈ"Îa^hþm&ôÞ<¿Õj]Újµ;—2{T,­ˆ(Ŧ¤Þ9œ¨*ªÑ Ô¡i=Cµ¹GŠ6dÓ„Ñ(æ HÈ•ê#ú‘Àš¯÷BœârQX5ZóCk¹ïìÁ>§5w—NÑè™üžsÍêX:îûüþ,±g0EDФÝ)$89kn²'„`ÉÕ_šÄݸþI ©% Š­ˆ¡=Ó›‹»±c»÷>ªd,ç¢}"s8i£×Ö…Õð‚õÈû<æ‚SšdS¢.ˆ{.CÓB‡Í\—œAæ‹=‰Ä»<šAcµOJ Õ…¥hÃF‘‘KpÞ*eS´|?Ú¨I±÷tqVcÕNËóʽª=©«Z:}EïÖh€ÊÅõç@4©CZC¦ÆüXI¤;‘73[ŠŸIª\oýµƒ&G~ºÚç€ýn!*Õ,nªP÷(üƒD5Ôž±â¼ç²i^‘ ­=.b­Æ1htŽqc¿¹cˆÃÁÒéõOÝ,Mß0 Ã0 Ã0 Ã0EͦàÑ!¸r¯[&§»®Ï²£á*~ÔEÕE Ü%ÞUï¦uhÖE²Žw¢¦u\’À<=œªTóm!€«Jp‰ÖÅË«à*¸bQ·Ü.ŽNÆ“ Ѽhë>¿^bË0”Q *=‘¥X\¹ŽÙ>o€AÒ¤5ï„ͲÞÇû—Ú e’mÖÍT³®õOêóѵ§ ¨}¾Î6••”©fój¨§„ÁŠ1ƒm[ò_„mT¦Ÿ ôWU{×WîÍvýïþð«’:’¦EýMº‰¹w Ã0 Ã0 Ã0Œ…E4¬‚3öhÒOà±™~qÍ:ˆë.Ö›äûl.2ËÊH= íÍ"-­tkæQ RYC´Þ‹±II£ªkÚ¾–G•iç£Õ3žc* ‡p’‚¤>îõégePž•ú'UaahÏ×¹ÎÜe«á›H‡/О{G´¬V!Òk%WC× IDATï½Q_˜y½Ìp;Ï7ƒ·_ýq¸Žý|1 Ã0 Ã0 Ã0Væà]|gdd|Îív¼{Íb¢FFFÖöYµÝ¦ùQÁ¨s$®rXIkˆë’8‡›í1ý~–Í¢T«ÎÈXþÓ¼›„fh¥k·U+¢}ÖIxÖ^4‚ò‘{ïU„¤|–?oÚVˆ¿=™%qxa ;…Öª'æz QèªÎ9Ⱥ¢Dõ×.ªY”O¼8d†)œ6j?EVóbIϾÚ#>Wï‚|½Îr«N;­[ãó˜c §y2­þ¡åß´Ó1 Ã0 Ã0 Ã0Œe‹9xW§oÆgÝ.†Kû,=¹¸Æœä¢S–‘h.îÒò1µë¦tî¦!á‰õR†ó:kŠ+Ñv¹˜¶× áW54Ðêsت€§¹NLáðÔx»õ q®o>Pððj™÷;-ZbÖuIRïâT‡d¾qYGj d,M‘O× Qqý©5¼8~„§¬ýï¾ßO[ ¨=¶m¶£–劋͸jÏ£‘c¸ß ¥å¹è<>¢‚Hµþ¨££)xqßD^Ã0 Ã0 Ã0 cE`ïÊ`GŸe[9ÖÕ}–m³)~TÈ'7%u§@ÈbõŠVZcãÄ|£C¥jÊ%n„V:xËõZÊ¿9nUʸ­†ªöSÇŠ³+UâªGÙ!+Žû•\<.žýïõ3ME–YçAØ8NEd]Ô9$ë0% ÇÒø t"óßVë_G4YÔ KOÁzÔ\‡í³}$¼æ™ÌQ+>¢½7…”uWpÊŒìX¬•~÷ ˆÞ­#zµþd&»ô\a†a†a†a,oLà]ôËǽ|dddË‚¹ ïîå›âGœ\lêÝqÉÂ#ùÎfˆ¤œ8Îüô]ÍåÜRØ­¬«8#Cò­J™‚b¼@+ˆ*RäªzÅOD§5ýR n] qѡ˃_^d»æ.áÊTT²Ñ#æûÌ6'M¦ šuÁeˆë¢.c2K:xïRrñÆõw‡Úc»É²Jý‘tq?²£8ŒâHDý÷zf s¥Ï88 òx^ E’HØ[qá¹pKêÑKòõÕß7LKcXð•K}ZýQÇ¡Cw‡³Yª.nÃ0 Ã0 Ã0 ÃX&ð® f`o™ºs222rðÎ>«F[­Öu6Å ¹èÔ=œÕÆ4ë Y.C»D„-c‹ø*_£å½.XU/ΦA +#ÑÄ¿wA¼“H TŠ}r‘7”5o¾Un¥Œ‡Ès~s÷'½ÎMé{ ýÓÇHêE4ƒº q‡´1ä"ïRÉáÕ¨þÙÁn:*®§þ‰€ÖJ)ð;Là5 Ã0 Ã0 Ã0–9&ð® nFû,¿Ø>22rÕLBïÈÈÈ¥###Û€Î0öU6½ yl@töu’}¢Î‹{.ƒnêœyˆYå(Û‘ÅÁ¨rluúò2g7Ïé•ò™ýJ&o.´©ìÂ÷…˜çr§fË[-…bAƒH,…z˜ {Zdõö´i ®ßÒ…*sKr gŽ5Z 88Ô94ë²JöS·,KCä-ò—·sß¡Ÿ0­þMÄçÇΡ¯k6T¦¹gc×m,òª‹¾*©7;)_?£¹àëÇq¹ÛÏÅËË9"ƒÓêë²sß¡Ÿ„šw1q×0 Ã0 Ã0 ÃXÔl –?­VëÀÈÈÈÕôwà® Ëß922²Ø­»xŽ¡o4÷î£Bü$zLýt´{_k0;SÃcå:uÒ:çV /OÍO§¤ŠúÈ„ü(¾9Zé¨ÑÐäÌ»^j4lâ÷ÍV¼œ¨_®Å˜Qã+‘ è–njݗy㶉ܽåEËì]Q-EÝ8—WãkÔ¾³© œß®ƒ¤èø¡)áP`ûì>JïLþæÇ¢þ…ƒ{ûáÚÏ.v“tzê®á7§ó¹÷û÷ì¬âª÷mRÝWbmþ{‚wòVœÛYô>Z.±¸»£ãJΫ"ì¼¥xUEHiVê¯N5Áñùïáþ¥VÃ0 Ã0 Ã0 ÃX&ð® Z­ÖÌ-ò·­­Vë€Íê£FüˆþäõÿþÃ{T•Äe^S‡NBÒOß7ûcúªQc²(Ó¶š­[æ™J.ØGm)²%QŽn.Ö•"°„GîE½¨ëÇÑRÀsÒ#Ç߇æmPDBäÂoÑ`-ŽÛúïgo°¦(O?àóWÝØAº8Gâ2T×ÿû½÷“,Ý&k¹kç].i .“jýëe~î|–jž-‘ÈKÃÐÛ­ˆc Ü.k%w#a—(¡{DÜüjcáW¡q¤Q É*H’JýÅe’%5>}×®»¨ ¼æÞ5 Ã0 Ã0 Ã0–5&ð®0‚Èûr`Ç"vß¼¼Õj]jâîcB.ðMc{ê'¼ðåpÝö>$M¸èÀ@ˆB˜}°8¦!~tž ÚÙ«!W‹¦k=¼ ªIsƒh몢¬ôT“bŸ$dõJäâ±kTÊñ(°Åá°=ñ Ú?žÄÏQZ£{èaºšÐuŠªcoºq s¼¼Þ]<ù³CÙÒSÒIW± M²G(•§l¯ Zã{E[õ‚®‹Öõ ¹Dïs¡W²(ƒ×õ4k‹ÎG\›8ŠqjšÇ¬,Éð´úƒãCîPû)ª†a†a†a†±l1wÒjµnhµZ[€×áݸ£³l>¶y]«ÕÚÒjµn°|̈¾±{'†ïçÈTé¨ÐÝ IKnæ·3T4× 1 ”B®ÄÍË´s‹Ff…Ð*å«pîFbmpè&N á7qJ¢â›miRi´Uwˇ"¦QÉÞ…"ÓWBC8ÉCg¹ðX4‡˜Ü¿¡£‚sÊ'Wß‹x—¢ÀW8¸î÷ö¹oŠË¦Õ?IVYÐÀq3»Þ ÜÂMíuÔºr½cy,òJr%r‹«Ž)}Dà^·°ÎÔ|¹«åç$Cë‘æ`¥þê”ïísߤü%Š ¼†a†a†aÆŠÀš¬­`Bƒ´ëFFFÖ­žMFÌ©»dˆ#¦€ñŽì½þ¢ Ü“ºª8:ãm†².uI9},eÇP6ó`Ec4-œ¹q³5_ò¨†ryÙÍJ•BL”ðÞ;)ƒÃV’BoUªÎâXÈõHQu ŽÝȽ+¡)›ÄŽÞÊ5ô púxƒFÒ„Îc‡Ñh6I:*üß‘‡¯Æ© ¼ºë?ñ±ûô«/Úä^6¡¢NE|ý;ÔDujþ£‡’*ôLóÈe’è+"pèçEZ©Æ8÷^„Äãj5B{£"¢l_íÍèí} óËßM†Iê«*õ¯…ß]|ô>ù*0±ëo†a†a†a‹ÆÞc„ än³™Xòdø|ÐñÞ~ß·ÿòIPí€@FÊÄÃ;8á ^¹{ñ¸CT­®=…@ŠH™¹ ˦a¡Fm±3…÷7ìrs+âžTÄ7‰÷Žò 'qÑTÍ/O*Íׂr5`Ó>î]‘Yâ^µw-’ÖÿÙ½ÖÉœ’t¤Á?~íG߯ ¼K±ÁV.ðv&?yçƒ_{ϳŽGtJ)ë¿ù Ä­G;»IúÏE¿‘ãïóšåbnœËD^I¢l\‰¶*=ûzn¹òvÈE^6wåxq\„ö4VëÛè æÌö÷EFÒ<’„ñ?‰ë/mmð©»úšÀ¤úϘ9x Ã0 Ã0 Ã0ŒE4ÆÒ!n´5´ï’Ó· B'Sœ‡wý˜d`ˆKö—BÛLƒåÍÖˆ”ZºhsÁV¥h¦VdìFÎû<])š­iÀC÷Ý…&‰WKU9T[—ýëw~z'^àÍóW—"¹”:!0þ¹{ºcWgà‚Äõ—zƒdàTÀ!º°ÑE"n»6wWçb­ö6D‹Eܬn5zã¦kd‘€ìª9¼±‹WzÆÉ¼Åy¾³á·ËU[Z£·þ²§;ØþüöÞ!^àÍ~w Ã0 Ã0 Ã0Œ ¼†±´ˆm£Ûÿ%eÊùØ‚}{ö‚¤ÆK÷®†žÆfÓÌ£B‡­XÈ•}«QDC¢Š¸¤ã¼³R¼óÖQˆÂê´ÌPÍÇrâ°hð"nâ([‰Ãe<‘¸/ÕÌØ™f å¥ûŽC’Ùáìyp7Î%dªtUØvpý—€Ñ0§qƒ­¥HÓpè|ö§z}*Ðq¾ ^^©5H›Q\Ño^7˜FýÊr5ÎŽ­= }câuy´‚›ñ ùÝÛ‰¾Ò›Ï;ËòÙ»Ždð$mTêïTU>óSýx<§X<ƒa†a†a†±‚0×0–ñcúÀÁßúÇï¼w*óÏÖgN‘Zƒßû:Isˆ7R`ßmÙã¾é2ž‡bjÿn$­ñ»Oð»Ìåâ©»IáäÕRäu¥“§‘KÑì,ó¤GüMzÜR¨M¢Fj9†5—.îæ—ýúODÒ:Sd×®]8'‚Sá¶ìŒoûÂ\Æù«Ë¤þ®ýÅÉ? Šsh\Ò:ÉÐX ‹7ÏãêM‹kˆ#"77Q¼ƒÌ±@VÍÔÇÏãb‡¯ösîÎ'šAp¤«ÏòÙËQýU|+Á/í”×^Fõ7 Ã0 Ã0 Ã0Œa¯a,=rY¬ƒÏ =ðëù“®S\xf=©5øol£Öâ²ÎHdÁ"o‘ !·ÚlMƒð[½yƒµ¢ùÞ™ë’àЕ(kµl”æ÷W$ßFqZD?Ä‚îBÄ]DX¥¼¢}2µ¡5ÜýÕ›‘zƒ,ˆ…]U®üØ]‚g§|<_—IýÇ€öo}~ôo&]L}¢BYï╤áçi"oo£3"§m%¶!ÎÖíqëjŸuôºy{ܾq¶¯,BÜ%Ü’4I×=Úàp¥þªÞ½ûÛ_<ø7xqwl™Ôß0 Ã0 Ã0 Ã0„ ¼†±4)šm‡€}7Ÿño*JW!sÊÓ<øƒo‘Ô¼ÇÙA©›]àë/ò†fg®|Ÿ„Fj„ßx kL¨sÒU4®ÒXC:|&êº ‹jˆŽV‰lèqäâJ¯ô¼z]¼¹?6y%nئåÕI$ìæMßæƒˆ ®Kºî\dà8¤s¸¨¿‚fŠèÖ'þø–½Ä‹»ys5x Ã0 Ã0 Ã0ŒGjS`ËÒ½Éq;/Z?¾µ£Ž”‡vü3ZÏæÜ±!nÚÏþZ7 s (!OAQ »ø÷DÛè+ê3”\‘“`» _óŒÞüðaüDýè¢IØ×“9½Ññæ%îÂã§Vñ¿ö?‘¡!¾|ýßã¿"$(ùÃá·ÿèÁƒw{ñèçç/×úüt¢ùãŸ?Y_8™ŠHYÿ‹ÐÆzÜÄ4›@$YüQ4ºu¢2HÏ÷J5Z¡h Ö›»Ç=гœ…¹vóûVÕ‘4£¾ù™ Ô§Õ¿‘ˆ¼ýÛòÖû'ïõŸZÆõ7 Ã0 Ã0 Ã0Œ1¯a,]r©l ï@ÜÓ÷ßúƒä´û% RÕþö÷Üújƒ«¹vç…¤šBݬƒGNÞ\dUÄAÖåß·œ¶~½gýWßœ-É£r'¯“(ž!ÏíÕá ˆK¢eB¢É¼»© ýÐS© ¬âî/ý {Óu‚pN¹79õþ›ïúÙ­À~¼{7÷t×ÿÀ—tàöoíoÜ–€8ç¯%¯Ú v³I‹Ú.êˆTݼ…£7Žiˆ¾Šד»+=1Úù !.x!®] ¸?DRê§<Ÿ´Q¯Ôß߯NîmÜö•Þs·ëo†a†a†aóÂ^ÃXÚÄQ ½¿qý÷ß8%MuΑ‰Iʷヌý?þ.isî†ßSd^"¯ ½.o¸FÑ-qen®†œ^ßX-·Î‹¾Õee#¶<š!‰„ãXØ-\»è¼Ä]€v=‡¤>ÈÞûî䎑»I’„N8Þ”ÔõÊOüàxçîrËÞ«þí7~~ÿÕcÔ³Ì9q"Z©c€ÚÉ?š-^ä GUÊ&ly¾2}"â†kÚ¯‘Z”ÍÇ1è«QˆÿšQ?ý—Z£RÿnH×zö[_Ø5Þµ{ˆåÍa†a†a†a Æ^ÃXÚä^È0Žw$îþíÛ²7 ¾ÙZ¦Ðh6¹é¦O3¾ÿAÓA®Ûõ4¿÷øàÏ1˜ 2¾o77ÞpÆÎ)N¡ë”ßû𾨿jŒ2šA—qý§Âµìýí¯fo¬¥ YÖ[ÿݤÍU4Nùyp#y)g,OUÈÙ«Z »ýryUûˆº,® Å=⦨ŸþŸHC}ë ¿÷ïî £~®Ì½k†a†a†a¬|,ƒ×0–…Föp{rbÃI[ÖžZk?1SÅ©â’÷Üùm.|òSY“¬âù‡7rãðe\Ãô,Ne ’Mò?ð^jÍÁ°Îÿ¶êKc'òóßÝùiàAJ÷nw…Ýì;<Õž:{u÷é‡fª’×ÿ‚'?Ö¬:7zŸ¯×‘ˆ¼1çî–“ n@ºã•ú'ˆ6•¸öË?ý<Þ½=JéÞ6 Ã0 Ã0 Ã0Œ‹ ¼†±<˜Ö¢êŽíû~Ø:{ËEëÜÁõœú¦kw}óß¹ð)Ogmºš_=x ×ÿ´ldµ@¯pôæí×òÆXù3üýÎ.Þ_Ëý|Æ®0Oa7O„Tn|ðŽOW#Ý þî=I7iŠ_Ÿ ü$9ñ¾¿üâ}W»˜îÞ])÷@žœ ßÞ5þÃs6¯yüÆF猎uŠõòÓHÖ’¬=7zªnñ×Cò†j")3_A:¸éŒUê/"Z•{ÚÛÞsÇþwŠwîÆõW̽k†a†a†a¬`Là5Œå…Ư/Ý»ï¶ç=ᤗ4³ÃÍ.^äp wÜþUÎ;çV­ÞÀ=ØË¾tª”¹aè¬D8ô:vgróJù½.Dc+ÎSx|g5ÿøðói6Û»“÷¿ç\Ú AE©‰°¯¾þà|nǯáÅÝ}TÍ_‘õ¨oÛ1~Û³N]õ¼¡$ÛÐÅ‹¼eýŸ@sÍñ$ëÎÁÞ‰vÚH’9ÆK o×A§qÖe¤AÆöe蒭Μh¦*qýŸpA 7pÉÚ3ЃÛÑl $YRB¯w{ƒj†Ôižy)éqg28PŸV­%ÈhVßõ·¶_ƒeØ‹Ï]>êo†a†a†aå¿©m cY~n¨À`#pòÛ.9ù#k§ö­™tu”ƒ±N—Öñ¦ßü0´ž¬3Å'Ì_ÿ §­Ð›7QÃ7e»òð¹¼bâLjzx?ï|÷{xðÀa›uRQê‰PO”CõõÿlÛ®ÿ‚w÷Pmª¶’sWgªÿº?¸hÍ6Ö¦ÎÏT™^ÿ dNél÷7 I¼ÐKhdöX\LÞlÏ9ÐŒÚIÏ¢¶±Eš zxß´ú7Ñf¢²/kÞ÷ç_;øz|Þî±TÃ0 Ã0 Ã0 è`^ÃX¾T\¼@÷–Ÿºù [Nú¹Õîðú® ]¼€Öïò/7…ã•ÇwçL¬åµcg3šLòµÑRÜ}4…Þ(Š„KÇ·ðÞöÅœ¯'0°f5·á3üù»¯cÜ)ÍZêÕ:R’M÷½óÖ~2–áX÷*õH¾rÿägÏØ¸êqëkÝ3:Íð]È*õâyÐÜHºéÉM¢í]¾!÷öÑ¢8–:ȦH?ŸæY—’®>™áAnÿÂM•ú*õùñxsÛ»¿yðÍâë~¬Öß0 Ã0 Ã0 Ã0ü¿±m cY~c'ç0°8á²'Ÿrå½—uºNqªLfÂØd‡Ík¼á×.gó™ç31vu3t¼¯Y¡ŸÓÆ^5y&¿>~’¦ ³ëÞ»y×µË΃“¬lPH¥™øX†»&7|òÓw>p-ð ð0eæê±&îM«¿À…៽æµÏÜ0õ?&2%sh¦*Óëÿ$&&ÆÑîكߦ»÷®0ªÏèõ†^=ê§ì#v´ µMO&Ýü4¤Þd`` oýÓDµ‘ˆÔSᎇë}ó}‡þA ­¾þy,‡‰»†a†a†aÆ1‰ ¼†±ü?ùÈ×À‹¼ë€M'­:￞¼O²)™R¡ë sÊdWٸóÏ=…׿öU Ÿt&“c‡q.ã õ|¼y/Ûk)»±qdÎÞʾþ›-Ùj^5u¿ÐÝB’¤4WÑ~à^®½î£|å»?ã¸á:THh$B-$Mõc÷ñ†]ƾ<„4¿ LqìŠ{qý›À*u Ǹvð‚ל-ïK\7T4sBשô­ÿÄê”ìáï“íý.ÍÜ©¹ @IDAT®½ÒH‚eáPî«…[7>‰tÓ…¤ÇŸ‡4úÖ¿– ê)‚¤ÙÇîã »FÇïد¾þ‡1q×0 Ã0 Ã0 Ã8Æ1×0VÆçXð‘+ `ŸËºØ|ÅÓO|ïºîçfN˜tJ–)¦2Ǿƒ]ž{þ‰¼æ²_æôÖÏ156†ËºL¹Œ/Ö·s[}'w¤;qÓÝ×ýhIž‘ȳ»'óóÓiHƒ¤V£±jˆßùw>ôOŸæ wíbÓš”f]¨% 5Qj 4Ò„ýéºû?úíßìÆ»6cxq7o¨¥Çxý¼È;¬ŽYûšó‡®ÞÔè>{*S¦ÚÍTfªg|ç7ÕÆønt;Ù¡û¡;Ró‚oq/ÌôÇGþK èjjƒ¤kN#Y»…丳Hê«H¥>48cýë‚Ö•ÁZÂîÉô¶ìj`ßPm4Ô’²áœ‰»†a†a†aÆ1+ †±r>Ï)P)ݼǟ{Òºç>÷„ìm®3‘ttTÈ2Ç”Æ;ŽCãg¬¯ñÊ_|¿pÉ%46œDgb—e8UÆu’ï¦{¹7ÝÏŽô ÈAöÊ8ûd'^WKT8N›lÔANvkØ¢k9+;ŽóÜFiHBR«Qh2õðn¾ðÅ/rýçná'ûº¬H¨¥)õDi¤PK„z"hZw_ß[ÿãì:p+>k5wíŽJq×(EÞedÇqÀð™/zÁ)ü‰dÝ)]ífNf®ÿÉt&'Q§8IÑÉQÜáÝèØ^tb?:9ŠvÚhw²Iô´‰Ôú0Ò\‹ ‡ n$>i®%Ñ IêÍSïä _ü·¾õo¦hšˆ4SÈH'nÝÍþøá‰ÛCÝ÷SFrLQŠ»†a†a†a†qL †a¬¬ÏtBùÈþ ÞÍ{püÏ?ñ„ß9™ý—tÕ;y'œÐɼùÑ)t»Žö¸òÔ-«¸èéOáé­ó9ó¼'Bm5Y·‹Ëº¨s¨ª·L†œV ÿßÓ ‘Hj5ÒZ:¹ïžðÍïÜÍmßøßÚÞfxjÿ{wób×YÇüûœsg&3“I¦¡%©mhm-b… ZÄ…Xôp!¨;qáBð_påÞ•àÂ…¸SÁ…€Bñ‘ÒEÀ"¬¥MM M'™NæíÞó¸8÷äžL'iÚ¨Íç‡sï̹s~ç…óåwžgÒ¦á/%“¦dµ­i›’Kuó7ÏÿíÊÓ»o¥ïÚÝÍâ‘|áÞë?îæÞL²öÜckßþøúì[³®Ëá¬f¿+õ`VË{«ÿ,]7›×¼¤Ëq¼5MúZ–tiÚIÚ¥Irpí]ë_J©m“²Ö&MÛä•ögxu÷§é;u·rk×¶úÀ1wæÀGG“E7ï‰,‚¾3IÎ|ù“~ÿ¡nëÙ½YÉl> [™?z_’t]ͬërpX3&Ïœ_ÍÓOœÏC=•O;•Õ³çÉÍ&eãdm³Ozw¯'ÛWsñZÍî/篗·óïW.楋¯äÂk»™L’奒¶iÒ4åf2W“”ù÷7¥d«=õÂï.¾ùƒ$WçËìíeѵÛ)ñÏëãnÞ•$ëóúo$eóKçW¾snµûÚÁ´Ëa—ÔºØîuýSSK©¥$)M›7÷˯þxéà'IÝJ?Úõ,ÆÚwí w ^ø¨ßCлœ¾›wzO'9ó¹ÇÎ|ïÁv÷+íì ÆVωV2Jµö!píæà,™Î‡X½³•~ˆÖIIJ›~b´¦ISʼ«7·†º£ï˜6K³+‡+¿}ñÕ­¥u¯eìîf1Ö®®Í÷_ÿIAÿz’ôÃ7œüÌÙ•ož=Q¿±Rf'‡Ä¼ÖRSR>Ìú¡næÿà~mß¾|£þâ•éÏ“úvúa¶Ó»C°¯kníc|ô®¤ú†1zO%9ýðæÚ3O>°üݺóx9’ŸÕàDqôo”$]JÞnÖÿyñêÁÿµuãB¡î0Æî^ú®Íq°+Ü»ûúƒÞµyýד,ÝXùüõ«›Ëõ¹6Ý8ˆM-åÙJ­eø+%É,M¶ÊóÿØ.¿~c{ÿÏéCÜyýÇÛSõ€;ßü÷DZ>LÂ6 Ý0„½CØw2ÉÉGϬöüé寯—çVº½É" ­wñ¥ãtp¯9±w£.ýýÕk¿|íê΋éüaB½ý,†b&Qì}xõ_Oø¯&i?vjå ¬×/žšäÙ“úH©}oï-½™À)µ–ã®,åævMö¦åÒõi^¸´S~ÿúõý?Íë»;_vÔî¿ŽùqGï0×r½Ã²–díá͵§>µòÜj[?±Tê™6³µI-•tMS»2~F¿–¦viºiiginÖæêî¬\¼¼½ÿüëoÝx)}ˆ; »0,{é‡`&ÏÒ±{oë?{WG¯'IVÎm,?óÐjùôz3{ôÄRûñIf§'“ö\3ŸP­ô³®¥–~âµ.%Óéìò4íµ½ÃÙË;]ûÚ•Ýú—ËÛ²˜ orÇÚ‡êww³ÜŸÇþÑ®Î!웤ù†Çù—Fï—GÛ Ÿ:.§é»ƒùzðöFï‡m†PoÜ­)Ø»wõ†o:{‡:¯Ì—ñ~1ÃÍè³7›t³f»,‚Úq÷³˜,í0‹NÝ£Ã0¨?¼›|àþ> çf´ŒCßãÖmn “[º!°Þf=„Ã’öþ›×€qØ?®ÿPó;Õ¿©ßíê?®ýPÿ£C0¨?ÜåÍ=À8¬‡·G×MÞî Æ!ßÞÖcÖõÈöüï\JÞÙáÛ³/¼—ú÷ƒ£ºB]øoènwn¿nîðûqP7¼îŽùÙÑ×üÔÿhünõ?àª?|È7ñws¾8.àæÝß× õ€{í?ƒ¦/•Úñ×.IEND®B`‚nzbget-12.0+dfsg/webui/img/icons.png000066400000000000000000000607041226450633000173640ustar00rootroot00000000000000‰PNG  IHDR¼,!4`bKGDùC» pHYs  šœtIMEÝ  0 Ù® IDATxÚì½{|e½øÿ~f7›[Û´éÞK…¶¦€´ ÈU T°Š‚xPÐ ñЃ_ÅËEñÀ9 E8Ü<€€@¹ƒ« rIléKÛ”–¶)¤IÓ\ö6ŸßóL2Ùî&i»³I“Ï»¯éfgg÷™g®ïùÌçyEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQb‡Eº8 ƒëºG‹È³"²CDÚD¤QDž‘9!½,°¾s Ët )ˆý©À$]Š¢(ý—LÙ ¿@‘óDäm9'Ëgg‹È²lŸi@ð 03Ïu/‘»E$-"qY."kDäMIØñÿ+"%Zß¹†@50²Ê­´Ëx‰}5qW(–Ú2_ ÇÀ÷?/å½pÌèû¡¢(Jÿ^9_:H‰Èù"œ |ÞÒ @Þ£Vf]yÚ¾'"I%"Žúº"òû×wUàý>Ñþ¤‡'ú{€X¶ýe@E·±2+Û~]KsŒëc@E/ìsŸÞzë"·Ðõàõ,ƒ ¯¢(J?Þ·¥3)+ÁçgÈ®ˆHoDœ.ÍXgæ±î³R©TZDž Œû¬­ëÜÀ¸ì²8¤ë»‘¥s,ãÌáÆŒéÃÞö_)°`–dÈm®qAx/Ròo°ý¾þW_}õØ«¯¾zìÁü•ÊÊÊõ&L¸ØwõÕWàç¥cÑEQöqá=3‹Ø¦rŒ;³ÀËbÐX‹ó\÷‡l4wH`Üé¶¾ÇÆUŠHÊuÝÿ i}xp…7¹=ˆg)÷rûù=؃å>OøeŸ¢,r[4À„·4°Ž3‡gðRmë|¦=G)Š¢„BotK–­Z6ÎËsñ‘,ѕڌ÷7‡X÷qö!kD$–cš˜ˆ¼“J¥ZEdlH뻫‹ž0#¼×Ø“kw.¿«°ó ¼í?Darx‹³ìsÅܧáEÓ»Zµ.ÚÖ9´ƒìúÎV÷%d¿íÞŸêßÛŒ·T~{‰Ïè©XQ”þ&¼µÙ¨åáÚNtOæ8Ñm$äœAù^:N‰È;"ò©ŒÏN‘µöó…Zß…Ìá‹Ð¹AZWC!zMÈl(T¨Hã,Ëz…íU…¯ã",ó‚·Ðç(¼_v~ ‘¢(J¯ïÙ±íÔCé=?DñZ”¥îéø^D¾#"-ö;Ed‹}uÓét³ˆ|'äõÝU䮊Ó὿ޗRØ~x+³ìs•ôÎTø¼ÞB6ôcð@Þ"àp=+ŠÒ/…× ß|yIDÎÈ!ĵ!¤3dãJ¼œÅpU!ºëº•"òŸ"òªöþÓuÝÿp]·²Àë{ ?ÒÔœn¡%c$»>F8Û8¾ÞݤŸÖ_Qe@¼Åüˆ.Ž~OOúb~f€,‹j+š…¦/OÙŠsŒ(¼¸ø(”`ö¥Gûªð*Š¢(Š¢(ýšÞíýR]Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(Š¢(ʾHd€Öûf` P­›€¢(Š¢(Š²ÛˆÈ!"ò=¹_D‘Ÿ»®{¬ÿ¹ëº“\×= Eþ.†ºÀ´@ 8"„ê¿’c(ðj8h°¯a_Ü\Ä»Y™CÜ~/ßÛÞȾû-ÛÃÏ ±ÿ^‘ì<Ry—‹H\v¸ˆ„±ÿ-Û“ÏBZ.ç‰HƒˆœgßGtóW%_˜"ùsÙZïºî6™˜Ç¢[º‘«Õvº€ç™À¢N~]ÍGØgsRyM"r‰ý;fß‹ˆL³ã*D¤EDR"RaÇÍ‘¦<•ÿ¼_7{Œ¯ÈVo© ,ŸçCZÏX™¿Âîç""«Eä*» ^BQe/4Å"’q]w§ˆ,t]׸®{ŒëºÇ?WDf„ ™] ì é~æ¢Îwt„wEÅ?ßÄAÑínXXV{»Ý­°'²×ì¶õ"r‚ˆL±ãnL{r@®ÊÓvI¢iaKž¿\?eßÏͲÌçÚÏÎ éìüÀï¦ìû §Ÿç{y<™Q×ÀìÓûÊBÞƒ3Æ¢ðÆíÅ]S£»Ëüù̳àʾŸ›¥Ì¹ö³sB–ÿÝÔ}uh;¡ÈOz¸üï‘Ðï4(ŠÞÎþ´Ý™?ÌvëÈuݨ]ŸÃB8áçâúÀ4µÀ¸<–½‘Ý»•ï‹ó87KéˆêæÖâEyoAxݺ·Ð…ÜÛí®ÔÞÒý0ÇÉåV.±.‘Kó¸Ý×õà·¶@»»C>y;ã·}é=?Cvx-Ïe7d©ÛÛö¸( ˆ®ø»(OÛÀFÙ3ç©ü¸ˆ»å¶øQØ< ïn‘çóÏÍ"²4Õ͹/ŠÈKÁ á<•£}½´›òo N¯(Ê>†ëº‡Ú¹UDв *\×m DwEDÎ(ðó{ëðn³¿<”§²«{YxcìšÒqp ý|š}Ÿ)œ±–fŽn¥ý|PØÒeOà×åˆrùù)?Ÿ/ÏѼÜEäÌÀü\ÔÏ„÷Ì,b›Ê1îÌ<—}gŽúý@0§{p^d×¢yÚª{YxsåèVÚÏ…)½-¼6£%Kõ@ûù4û>Súcy>Ücçå¼9Õ—ÛÏï +­(JØá%‘ØT†çr|>YDÆ‹ÈX©´W@xK'Ü:©‰ …e ÞN>˜Î·”ýs¾¹†îÇݘæšDº™¦!éBDÆØhO*Ë ç¼ÊÛá Þnmð…$ä ¾|LßSægÜLÙòáh ðjà¢k*0Ê ®¿Ÿ¾dÇå{[hðoßÖóÁÁÛú~[†lƒ ÝLÓoá kúþæ5Ý5‘ÛÓ\Ò:Xlï:Í HxÜJp©ˆ<fJ‡¢(!ãºîƒv¾ GDÊ\×Íg‹Ù\'ðiVt€32¦Kx+2æG2Æç[x/¡sZÅ;þR¼?Wu"s/Èóò?šÎ=5t%¼-tD}úËvkñS6··"åìŽð6dŒþŸ~&¼]Io¼²ëS˜‡uÀæ,ó´ÌÊqÂ[‘±Þ%c|Â{tFT±+ámñ£¾ýAxmºR0­dJà°ÖÏב‰i6öä|µ‡Ç3í¸#m#>?·ùäà…¸šƒ¢ì›Â‘¨ˆD{p`øªˆ¼."¿ Yx#x=1fåŠ~(¼:§3¤ŸÕDÃ'8íæ¡GxEdQÆûŸˆÈía¯ý½³Eäí,Þkí:¹Á¾FxWä³I GwdƼ,͈*‘óƒb®(ʾ%¼Ÿ´9¼®ëº""5®ë–f™.&" »ó¤Ÿ o¼—„àÊ@9·ç˜&Øpíª|oY†ßÓñ°‹»6œËk¯Ý¶‚9z·ûb+"wÚqu!lûËzÚ8ÉöZá%ãýj¼”“0…÷|²7P˶mœâá([ïØÀ¶è÷à0>„m!ދ›ÉïýQDz4Ú 5‡7CxWûÃ)‡÷Êྟcšà2¸*Ïå_cst_é®K8»âÂOQ”ô¬ðŠˆÄ]×u²LS$";íNWHÂÕ›Â+½$¼ÙzixÈF¹À{ÈFoôÒàÏG»ö“š·žl¤¦.£AØ•ÓT"¼—ˆÈu}`Ÿ9¿€ÂÛ‚×€3Lá­%{µl"œï} »^ü®È†Xùgߛij/¯Ø§\VˆÈ“…襡 ám‘Ò°„7G/ ‰H•ý|fziˆ¤uÃóÚ¯¢ì»Â;©ý‰®+®ë®‘cígC]×½Ú? ¹®»#„G<ú'¸ž´zÙÏ„×?áïN?¼wæ¹ü²?Ý˲…Ùï‘Û†M"rrŽé–Ntgäyû¿ÖæåýľÏL]¸'0øÂ-"o÷â_¾…÷ì€ØföÆ)½ùŽnuÕoqÆÅ“oÑíÂÛ’íéfYrØCí‡7¬é{ø›wîf?¼w¢(в›ñ"ò¾ßm6Ò» ®ëÞÒóÌSì~¤ù¾½Ì¡õÿæÍN Qx}ºë8¬§ 5äˆò…þ¤µ@t¥º«Ç‹È~kö¶¿qLS7Ó5$$Œ^Jz³^¬ä¾Döüð³íöF:Ã#ìú¤µ9σó8ØžCø;˜7:-$áõ¡Õöò“ÖzíÁyé®OäÕ(Š¢ìÅAf´ˆŒöß»®{’ˆÜ "‹È}®ëþ§ˆâ,\OÏkëG ®Ìó<ì®ì…Áb[·kèˆöÖÚºÖY1ƒ&:ú8DÇüˆ{…}ï7V8Ù~/_Ûà]EL­×f>ò5Oe_e£º×v3Ý ]5*Ú žÜÙ}r€®¢!wK8óXn“ˆaÿdï6¤ž¨°ï[Ddwrwh»Qþ“{à»O†´Ûº^ˆöÖÚß:yEQe¯8£Ò»ÖŠ^˜ø¹rçY¡¼¨u¿œì© ] qû½|žì"Ý|þ ™©›ê€¦8Dá=£Ò»6WÚÍ^”{yŽÔ…®ˆ‹Èåýqûù¶ °¦’º8E…7Ÿä”ƒ_|Ñ9öØcUúNàÕÁ‹‚˜Àx׮ϴýÛ Œ×ú+Š‚{ý¬£³ñrfãÀ?€8 «ÿÑ˳÷¿À M/\eà^ñ\x P¶Ò³õïØ Ÿb ïV_©bVü°¢—ZíÐb_ã@* „Z¥·™<Ìê#óS |x§‹np+ð%»?¼e÷Ÿ$p€Ýî8 «Ûzi6€•Àg:ÝMe`v„wˆ•7 ®• ¥wEÏ—¼ÁÀ°Oßêîé¡À<öüùy¨Ê]a·ÉÐp¯Ÿ5˽~VÚ½~ÖsqŸu¯Ÿ%îõ³æƽà^?+å^?ëöë" Ün‹oçØ^ƒïÐÓ¢ < û€Û[e+»^tCñ7ÝtÓ—ç}vÞ9£G%"ˆë×­ãõ7ªY½zµµµÔÕÕ±cdž ¨Q£˜4iÓ§OçÐCeòäÉc0ÆPWWW÷È#Ü{é¥—Þ l´'˜$}'Çu ×?“AÀ%À¥À˜½ü­-ÀMÀ¯)l3“k€k{éØ×SÁþð£ /ÀÃÀY! ïCÀéÀpgaõ;îtàQàgaõ v\%Þ]‚?9 «¿˜§â‡;ìß#ïßË2Ý]xf€5À1À6=-(Š ¯ oÿ“Ý2`ô9çœsü•W^ù½©S§NuEØÑØÈâÅ‹yê©§ØØòÎ3ÙÁ¦Òx1€fzÁݲÞÅ}KW:¹sOåÔSOeÈ!cX·nÝÚŸ]÷³ëîýã½/[ñr]Ý}¡þÉúZå2}xšÉC„QeBE±· 7Æ u-†õ; «?ŒðFCtؤ}¥þ™ËâëVºFåù·ë¬tþ®ê<Xœ1.Aîès¡„7Û<œ<^@á U´Ýëg½Ä…ÕSãæ~ ¼lvV¿â^?k`œ…Õò¼M°q«ìò~ÍJø–Œåñp¼½(UE…7?”••EZZZŒ1&ÍŒ1FDºø BÄž&ýú׿¹è쳿pAQQQQkk ÷ßÿ'îè>’ʼníàLvvïD·Þ…—½3˜³Î8‹ÏΛGIi)Éd2yß}÷Ýö­o}ë /ê—î‹õøO÷qÔ¨œ¾ŠÃwÏÑV}èðÄúb^ß1œÓçõÙúÜr9/ç› X·tÎÙ,ô±owR(6ã `M´ÝëgmÖ: « Œ;øK`²^”ùÇÀxgaõˆ<Ö9­m&Û÷‡5Ó®N´¢ÅÞe¼¢(*¼ùÞ‘---å»jL©ˆ¬ÒÅ_ÙF÷ð¡zÌÑÇ|Úu]–/_Îonþ ›ÇnB>íz‘ܽ¡Š_(a|ÝD.øÚÌœ9ÇqXºté3óçÏ¿*•J½‹wÛ1ÝWêóÍ¿aº³žó¦·0ªlïÒ=·µ:Ü¿v(o™ýùZߪÙx §F¨¼­À<à••wðû}Dx/nëá E´Ýëg­bÎÂêý2Æ /…à=gaõ?ÜëgÕ;œ…ÕÓB¬óx© ™DéèR°í‹WQTxó}6¦£áED4¥!\`p4ýÈâÅ‹o:ô°ÃŽJ§R<òÈ#üùÅ?³óôÈäüÞu޾WDå3#8í¨Ó9唓‰D¢TW¿ñÒi§vi*•z¯'··ëÿÒÓ±à€­T™ß› «K¹£v?a^_¨“¬ìúéR-VzŸ+Py7õqὸ8Ìãm€ÍÀذEÛ½~ÖoñÒdvV¯ìbºYÀëÀ; «/ËSñ+ðz9 ¯WˆÉäŽðÞEG?¼^ÔYQÞðN*¼‘½2`ʃ>øËOõ‰OÇ î¾û.^úàï4ž¶)•nVœtÞJ:½Í½êL›ÃègÇpÔˆ£øÜçæ‹Åx饗ž™?þöÄTˆœÖœõo{çE쿉AEáÌBs*§R2퓽Yÿ ³ñn-÷Ö£T[€(L¤7pÇS…Õ›ó8 ¼|ôRºÎáÕ~xe€‹‘Òÿ.bŠ€Ñ7ÜpÃÅsfÏþt<ç¾ûþW’ÿ á¬úœ²+bqA"vüc§±ÿvù—-§¾Ï+‰ðÈ#H$˜={ö§ñË_^„w;½(dÉYÿ²MK¹ô€÷B“]€òhšoNz‡ÒK{«þAÆÑ;‘Ý evÆ ¬ðE`CÜ/7Øyë¶ /—…õãÎÂêMx½c쬴© AÙ=/v2Þ×ò)»CìäV¼;(7Øñ‡ãE¸ýnÈî¤s?¼cPE…wo9à€ЧOŸ>6gØA„Y³f¤‹?"ÀÐÏ}îsÇŸ1oÞ‰d’^x7v¾Î‡'nëxüGÑűrë"¾èú²! ¿´Ëoæl:f#ÕÍoð÷¿ÿd2ɼ3θଳÎ:/§/RèúË{¯ð•‰ˆöp‹—à†£ØåaÇw7F8{ôÛ°ñŸ½Qÿà¾}…ËÙíŠÑv^ q½¯oà¾ôPÞœ§‚ˆ¶³°ú:à `?ài÷úY;Ýëgmq¯ŸµxÂnW8 «¯ÏsÑ;¬ÐFíß?$ûƒ6Îü}0ð?zªP”EÞ"Mƒ Ú¯¥¥å5Û\ec‚–dJJJæ·¶¶>¨«d¯×g9pÀ’%K˜0aÂÔÚÚZþøôYsòJÒÅ鬲ëEméxõ¤óÚÓ/1ÒñÞ¸ö5cUGâQf½xgw&ãÆcãÆk?þø/àõƒÙLþµþyä.ýOÊ"©î¿m<©5å XŒÿán&ÜâqÇŽc9ê3Ÿ+Týƒ,~ÛǶÏo‹ TV%PßGêÝ[ó²/‡úŸ…*Ðöµûu`>0ÌÖû~àÎÂê0–c÷Ä`âÕ¼‡¢ï*<^í‡WQTx÷ŽòòòY­­­»®;Öëy,wž®ÿ¹1†¢¢¢Ë‰ÄéêØkŠ€1?üáµ—ŸsΗ.KÄã<ðàŸøGÕKìÕ´«èld—NÒëKž'|â¡Ýþ:Ë®÷·q×ì"½VðÉ•Çsê)§R+æ¼÷Æ^{í¯ð¢]ɰëÿ§ÿÄgg™\¼½ÛÓ¦8`ì+Æ“^ñ_ÛëÝ!»âÚúw‘!ñ^j8ODOã”ÂÔ¿ýú/§rTÛ>ëðn{ïÔ]uÀI_@ûáU”L^o1677W—––ÎŽD"›»k”æËnIIɹ*»y»x)ŽD"ûtÒ‰ç$“I–¿ù&FÔî"»þÔ∽MÈÓu蔳Ûñ7í© òy#V–}iÎZ6od]åZÖ¬YC2™à„O<'‰ì‡×¦ »þÓÜ·»—]›®`¢@Ô{5QhçqRäýý,˜ê«&¢2%¹¦õrI”]ì<]¢»jÁPÙíÌà8¼îÙ´Ñ´¢¨ðîµô¾WRRr²ã8u°KêBû{cL:‹]ÚÚÚz¯®†¼­ËÁßùÎwÎ:t訖–Þ^û6µ3Öe sH{dדހ°:ÒIl‰Ø_ŽtÎñmoÔ”]›÷š)½ïìÿ6µ×ÓÒÚJÅЊQ—]vÙÀà®"¸D2óz;¦ÇèÙ¡Cœ;ºóêåÝ:d ---$“I&Ož\…÷PFýÇ›Ü]žš`4ÚéˆðSÚ_é í ÚüñNçÁøõϱWq7†UŸƒð:×ïëTØyUEQ”‚-D!---ïãÒé´.ñð„·´²²rJ*•$S?èÃ,¦G‡¤Ä·}ðÓÚ£·tê–LÄ`D F¼iŒXƒ þN{og—û |É–$Éd’ÊÊÊ)xÎ{ÿLû]ê?Qr?9´S?»AY äóú™h—X¿ß]Ó¹T;äÙÍn±ÃÝ-4$C©¿Ï!ûÐ6{°Lw]EQ¥ß¯*~6jiYYÙÈd2…ëº4–4vý ßÈ‚ Íü¨¯MUÀ L'ÞâbeŒ˜NmÚûö5쪻Ðk€H¥R”••´ÂTʼտÂÍÝ#“1RÛ/:§9˜@šB{E\´Ù€èŠãõÇë÷Ù›+f;$å]„ä¹þA¦îCÛíTÝuEQ”Báè"è7Ò‹Åbå©T c ͱ]Š®d¾oø ™ KÌÄŽ÷hq6©¶45á8Éd’¢¢¢r¼î©Lõ/swäœX2ç1 ªíùÈýë ",A96ŸÊFÒ[šn «þ>£ö¡mv”î¶Š¢(Š ¯²»ë1bŒ‰%“I¢Ñ(m‘¶®õ0›œÚÈ,F²X˜ì"Éþ´FtálsZ‰F£X)ÑCÍ{ýcnöîÈ$SnOTóeÕd^øÏëÔEÇ÷ÛE·›™,rC«¿OÉ>´Í–èn«(Š¢¨ð*»M*•"•Jîi¦J×wÕÍnŒÍ…/|©T*ÜúÓm-M7K@Œÿ d/o9ÐÉEÇÄÒó¥hB®¿¢(Š¢(*¼ýH·µ¶&|á+uK»öZÉò^Úóºþ^§ïŠ7¦19Œ¯LʈD"$“IÚÚÚ@ÚÎ{Þ럊”å”N#³mr¼â‚q¥=ÂÛ^i×k”æÏµŸÏ‹tÿø¦T´<¬úû´íCÛl›î¶Š¢(Š ¯²;hjÚÙìç°VÈО‹®íuÁ—^ã÷HmZ7ãs ætLšÍþ†2 R©MMMÍ@‚ü4ÖÚ¥þñXeî)éHW&ø”Y;¿2bõ4m§“Œi‚)Ð9Ì7Q\Vý}êö¡m¶Nw[EQE…WÙÙK­ ÛR©i×e´ŒÎ=uPÊÚÿ¶·ì]í1H×Ê®ý,óûÆOðƒñ¥9ƒ1Œ!™L‘N¥hllÜ´²÷ûÌZÿxéØÜßpé­ ¼­³X¹%’ Œ³Rl¬ø"™Œ€w'á-Fýƒ¬Ý‡¶Ûµºë*Š¢(*¼Êîà­Û¶m[—N¥ˆ·Å™h&eWC‚‚ëÉ©§\¾Ìz"g\ƒIÛqiïoÒþç¦C~Û¿ÀöÖm™™Bkk+ÉTŠmÛ¶­³Â—£þ­C¦v­È²k4×Þ+´¾èš”7`_‚·§Bd/6>tZXõ÷Ù—úµÕ>xEQ^e·…¯¥vCmM*¦©iÓ£3²Nh¤#ý Sƒ5_vÓÙu N»ì¥7×o¶÷mÐÎÌèLH§Ólذ¡h!?ÑÍ]êß6âàܾkeׯ³¤;K¯ä–LÑ &ð=“åÍFbÄ!aÕßgиl¯v^EQE…WÙ-Z—,Y²$§vîÜÉä&0±³ì’íÖNé ~:C@|ƒòÛ.½6§·]ø$#Ý!À$3‰Á©!4íØA<O-Y²d ^„S¨kd(­ƒ§d—~:äÔ_ ¦1Òré: sŽènÛ©´D‡†Yì=·l«Ï‘ßȶ¢(Š¢¨ð\ ¾eË–÷6mÚ´*NSWWÇ'£ŸÌ.|T„`¤¶]vÓtÝNé .S$CœýˆqFt÷ØâãÙ´i©tšM›6­Úºuë{@œüõÒ°Ký›&|*çü†w&#b+ÁôûÞøJ Ÿ·S¤7Î¥¯Í“O³þAîÛ¶×ût—UEQúðJ|ò“Ÿ4ºøóJ ؾbÅŠÇR©µµµ9‚afXgÑóû“픞@‡°ú‚›™ÃLsð?ȯiO0»H_¥©dvÑÖ®]K*•båÊ•Ûí<‡Vÿc?Aªdxך”ÝtFÚBFîn{Zƒ/ÀÙ¢¼Ùf¬d­ãŽ»þ>[úðvºÅÎcÞ¨¨¨¸ ¢¢¢¥¢¢Bvshª¨¨8/Ïõ«®jÍÀbàZ BSŠ¢(ýPx»â¯ý«èâÏ+.дxñâG¶×µ¶µ²aÝ>[rÖ.z© Yrq3Ä6³ÁZGÔ·CŒw‰gÉÝ=kÐ|Þ}ë]ZZZhhh¨{â‰'šÈots—ú¯]¿~©ûoùÒÛ`¤7ø*Á¤×tqƒ¾å /³æíµa×ß'ÜÔ‡·Ó›ì<æ“€Ò=øÞ àú<ÎÇÉx½O\œ LÆskðò–OÖC•¢(JÿÞ!xÙR)P ÕEŸwˆ»®ûþ²eËïM§Ò,_¾œYî¡= «ô µË«Mg`É Fvƒ‘ß üî*»Mç09œššÒé4Ë—/¿×uÝ÷ñnçKØõo9‡Äð™=–^ÉÙ ¦5å5AÑíBv“#¢qÔ‘…¨_Ó7û¹­³ó–o*ðÂMN~¬ìÁwKó4sñ"¹]EqÇOç °cÓ9aýpeeåó•••²‡ÃózÚPÞ¼`Œi2ÆÄ­Äít]w‡.úPHÛ{ì±»·mÛ¶6³téR¾1ôbÊMyNém—Ö`Ô6Ímþvaviä–)»å¦œ~ Ï?ÿÕØØø½ÆÆÆƒ€ã€¿…\¯JàN —¦f¹$ºÙÊï@áfà’~û„.>;$˸Ùö‚hz7ßUE…·çøùºd<¾ÀqW}8‹ï‘­_|ñÅëÉDrýúõl¨®å[•—ét>îÞ`N.ªûã¥sªC0§7KC„—ú.ï¾¶Žµk×’H$’K—.½ØhçQ UÿWW½GëQßCL¤ë 4+¾ÁÈn¶éFtÄDHs5/¿Y[ÈúùðbÚ6_´ó@ÍÍ­­¨¨¸½¢¢âØÆÆÆ¾EþS)|.µÒ›yѱX—Eò °cÓÿ„(½Y©¯¯_ží⤾¾þ)à0=](Š ¯²o“Þ|óÍ–/[~[:í²dÉJ6”rÑðKrJ¯³K7d~ÚBfT7St³Éî%£¾MÉúž{îY\7Í›o¾yÛŠ+^·Kª¬õ_Ù4˜ø‘Wt+½Au6þÃ(‚}îJ¶Gjì*»©£¯deã Þ¨¿ œ líÛäV;/…ºÐœ,©¨¨x­¢¢âÀÆÆÆ_ãEöÂÞ3 ô¬ :ô™¡C‡Êna?ø£ /·ziÿ\ÞËÛb¤²²òz–î¢(Š oÏhii‰E"âˆHLDb@LDŠuч†I`ë³Ï>{ˆ µÏ¤Óiî»ï>FlÁÿó}9ÙoïûòÚþôµ\9¼b²Š.À gWû!ÃkGp×Ýwáõ˜°á™çŸþ+>IÂnæ¬ÿò¶ýHžx^á±Á¸ŸþËZÇöVýƒlæá=ࢷh±ó°©e ÇËὯq\+^$ïµŠŠŠ¹±Ÿå› ô\±ß98¤uP<T[¹ÍL ¿¸¢·ÇÅÀ=vþEQáÍ•ÀxcÌ$cÌ8cÌ8¼ÜµýuчŠkE£öÑG½jóæÍ/% n¿ýv¶ýý~5é&f–Ôå˜ÿºâ ÒrÓ”_³uéVn½õV‰[·n}éñÇ¿ ¯‹¦ åËYÿ§W6þìm¸£?–Ùó1øÜ<ù懽]ÿ ¯ô¢ôú²ûJÊ›hsxokll¼ /Oóa¼‚***Žhll¼ x<ÏåFôx“ó (û=aR}}½ dÏ­V¥j_¸6ûâh "‚ã8ÚoøD€!ƘýO8ñÄŸ¯þéd2ÁÈ\À²h wm»“m©m{UÈÈ¢‘|eÔW©JW±è·‹XµzEEEÔ××?³dÉ’«Dä]`G/œ`rÖÿ .`Dý«È«·bš÷ò®ÿ Ñ˜#¾Iýð#ømߪÙxýߎ.Py[ %»™óuÀ¯€[Ó7ãå̾ƒ×l"Þ­öÐØØØ¸·½Æ¬`×Hf˜eÿ®Î"ÅïÉGý‡ú ð©ÝüÚë ‡çyU\„×@m ðO` ^„=ØÆÀWóQ©¬Ìy·¤¾¾Þtõ¹?ž&E…7o›e¼ oa¥o0é°Ã»h¿ýÆ^J»EÇaÞ¼yÌ7——Û^bñ‡ñVëšÝúáKä´ó8ªô({ø1zè!Òé4‘H$¹eË–Ûª««oÁ‹lîìEÙë²þŸ=}.±÷– o>u»™Ö7j&‘ƒ?Obâq<ôHŸ­qx·s ¹œñrv ‘ÆMx}^Æë¥Á—ÒiÀ‚ÆÆÆ[+**À‹DæCx¯Ã{ØY¤²G€o.îgÇšexõdì©€ì^˜Ï}¡‡Â;©¾¾~CÆ÷¦o×××Gõ¡(*¼*¼ý(F?þø)S¦|¯¨¨hj2™ ¸¤”SO=…ÓæžFѨ(o4½Áªæ•Ô¶Õ²5¹•©F†D+]4šI¥“˜Q>“ÆNrk’Å‹óØcÑÚÚJ,#™L®­­­½nÓ¦M/àEùzã6þÕü÷½ânYŽÔ¯Eš6C›WJ*0ƒÇb†OÅs0‘‰sØØ˜ÞWꟹ,¾ü•çß®Ãëzìw…¬s pYccãM—7kll<Æ>aíÎ< ï8`µ½°òiÅK©ˆÑMöIàEšßégÇ™:`<»6 ¼œêËò] FxEéUá]³fM±ëº•Ó§O?×4ÕÕÕ=ôÐCWè*(¨èá=ôcüG>ò‘/WTTœcŒ庞›Lž<™Ã?œ™3g2eÊFÍ!Cرcuuu¬[·Ž•+WòÚk¯±nݺàEL]ccã½ï¾ûîÝx]o5à5Ðrµþ}’AxÝD]Š÷4°½a‹š_n?»] o8 /¢øRàc_p´RšÆ{øÍL¼ˆd>„¼‡IÜxï÷Ãc×”†ËèÛOÃÛ"ÀR»2Ç_÷ ¼ÓÐNȉ^EQò'¼MMMû•••½fŒi?ycr–‘ý5mmmóËÊÊÔUúúŽà=ýn˜1f¿ýöÛïŒŠŠŠÓKJJfcvëà/"©¶¶¶UmÚ´éQà}¼‡*´Ù¾hýû<1¼\Û³“èúIaAç€ûðrƒ½U@„÷p[Ÿ ðîlll\QQÃ{ð x –ó(¼àu5ÖÕC%ñÒîí§Ç—EÀ‚BXYYù${þ¸æ§êëëOÑÓ‚¢¨ðî6;wîœUZZú¸ã8cEDŒ5Þ\ÂkŒ1"B2™¼¼¸¸ø¿tut½Gñ"]ƒaÅÅÅFŽy|iiéÇb±Ø”H$22‰”cbv}%Òéts:Þ–H$Öµ¶¶þkÛ¶m/Äãñ÷¬ä5Y¡Hí¢7Ð럋^Tò+…£ìÅVâ뀵x‘Ñô‘Vî²ëƒzÊ¦ÆÆÆñùœ¼|Þùx9ÀGñ¢œ›úñqåH¼¼iEQ”þ-¼Vz'”””¼‰DÆv7­ˆÐÖÖvnYYÙ½º*zmý;ù+ÅËu-µCŒŽ[±i¼(^«Zì«/yî>(z½þý‚ŠŠŠùÀµìÚSBw,¾×ØØø¸.EEQeO¤÷t:½UDÄu]Wøï]×Mµµµ}[—VŸÁ±r—)ƒìà‹`±&BÿzRß@¯¿¢(Š¢({ ½M¥R[$ ®ëJ[[Û%º”ú4&Ç õWEQE Hï´t:]çy]××u¥µµõ]:Š¢(Š¢(J‘Þ9étz›/¼ñxü*]*Š¢(Š¢(J¿¢¹¹yÿT*µ©µµõ+º4EQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEé³/ÎôöEÓ÷7Æ gÀ‘Ƙ¿ ýƪV]¥Š¢(Š¢(Ê>%¼"r0(6Æü¡aÑŒ¯`ø_±³ßñjŒyKD~9lÁêßõå:ÕÔÔ˜ªª*ÑÍOQEQE…y˜`Œ1 ·ÎVcXmDšÅ˜©ˆ‰1 ‚1f³ˆÌ¶`õ†>(»VUU¡›ž¢„ÊyÀÿßî"@z•¯(Š¢ìK¸®{Xüq ·Î(Ï6mí3ŽÙ¾hzÃöEÓ¥áÖÒpëŒ9}Ht§ÕÔÔ4VWW÷ÕÈ®;1 (³C‰W˜Îô£MÍØ!Dõ/µƒ_ÿ¨Æô³ú÷žâÀÀZ¼Û?««€à¥~^>åååkËËËÅMºY(Š¢ì'l×uï1ÆœÞ틦4ÆüC„QÀ Œl4˜GEäûì޾ýÖ·!ò5û±C¿±jKo×£ººúAà,c UUU}i¹;V⊬Øe nÔN—@›=y·­@Ò~æî£û€c b@q ÞÅv™Dìti[×x`9Ä!!Bz®b0³‹Ï×ÓûqùŠ¢(J¢ûâL[°zÛöEÓ§cÒ"bÆ r±1æâ틦_0ì«.Ø~ë ¸‘¥ÀGúÀl·õ±Å±b7Œ¾öÂy'~üÀI³Çªœ2|pÙˆA¥ÅƒbEÑ"€D2•ÜÙßYßÔüÁƺ†u¯­^ÿÊ5·=ò<°Ø4YÜWnÛF"މ¥])a°ß¯.ûòg>>}âÇÇ:}Øà²qå¥%±.`HÍ-ñDÃΖM·5¬~uMí«—ßxÏÓ"¼ìŒ8¦9íʾTÿþÄÍÀÁö‚­+bxQÖ—ïö£ò;Q^^ÞTøï›››õN„¢(ž}2 °ý橇]¼v@í3†W‰ÈBc  †}cÕ­Ûo^0Ì`nº`Õ×z³ÕÕÕ÷çô¯ƒÅ ŒýÞ¹'Ÿò¥“>~î´q#§cˆ8q"8Žþ"×ÅM»¤Ó.iáu«ÿøü«÷\wÏÓO›+÷}5âéÄ¢‘âD*=vÝÅgŸ}Ö1‡|iêèaÓýYŽ8ÆqÀ1`ŒØú\I i×µ{ŽÃÚ-õ«úÛ²?~ïæûï¶Ç¢‘¦D*G#¾…"f·¹ lÞ ü/¢: ¸8'ðy«Ýþý ü oyyù‡@¥½³¹¹ypŠ+¾`…¿ï.È`#^ZG^£ãI¶ì(° x xÌN¿ÇˆÈ^•oŒIê.¤(*¼þÅÿ¤Ÿå3`˜1¦¾ÐÂkËfŒÙî¿o¸uƧ§j g‹È"0?¶`Õ•ÁïÖÔÔTVUUÕ„ÔÌš5k·òkkjj†‰HˬY³â=Þ;€ëEá5öà?ÿ“ Nÿ‚3޾tPqQYÄ1•Äø`C-ÿ\¾ŠUo­eí¦-lÝö! ÍÞ—‡–Ãè‘Ù:~ 3¦MåˆC¦3bÂ$’m Ò"쌧Z=²ô¦ïÿañýöd³Ãž|úJβ‰Fœ¢TÚ TÞtÙ¹_;ï¤C/\‰-ŽñÁ{µòrÍ*óæšwy«v3›ë¶S¿Óûrå ;jLËG˜Ê‘U3dÄ„I&O€14ÅÓ‰»ž{ãW—Þxï€úhÄiJ¥Ý¾TÿþÌ5V*þ|>Ë4·çÛ¿`…´¿”ß#á ‰o·Xy;´àÝùÙ†w'i0”q,: x|/…w¯Ê7Æ<®»¢¨ð*ïØƒÈBcÌS"²ø…ˆ#Oc¾dŒiÈøî àAÙÙ£2¦¸Âót6áu]÷0àOƘÉvl_q§¡áÖ·ŠÈ×o[°zQ†tÎ1Æü^D ”÷pFUUÕæšššwE¤ÙsEUUÕÓ9Äõ$àFà Œù¾¥ªªêâ,Rüuà›"2 nG¿ ”c¯ªªºº@ë¹5iÌð½xÃÅ‹F 4ºÈqˆ¸q~z ?»”uMIŠŽ­Ä=¨wÿrÜQų)¬‰4ÎÖ6"k[0+v|ñC¦ .âsŸ>†3>u®#éºÔ5ìÜzìå¿YP[·ý_@fõ¶ô™ÒXQIk"Yyð“ç<õÓ o5¸x¤"nBxâ/æÞÇÿJS[’ãf á£˘:²”1Cc -õ²~ZSliH°v[+onhaɪ .)â¼ÓŽåÌ“—´)2¨kjÛvòU·]´üíڔƊê[É6•ÞP¹¸gßOÖ—Úá6à§ÀD ÖN³ÉJçmý ülÂÛ»`„·Óv˜'ž<`÷ó¯Ç_ ·e3ð;`Fà‚àìeC>Ù«ò1/é.¤(*¼ÁƒŠ œ+"ß2ÆœÍLaÝß³6ðÝ#Dä•ÏÄýª1掠ðŠ×ÝØ'¿ÚßÍöÝoü´ùׄgw½MV]]íGr•9ÍŠ=À—gÍšuO–ßx8£›ù?°ªªê­€ðþø¾_ .wcÌUUU_y;xÐ&þâ¹_¿äŒO\†â(<ðçǹõÏ'}ÜpRŸ;¶d÷~xsembØËpîÜ£9íÔSˆ§~óèË7^ñûÇl°K½u‹ß)+.*k‰'G.úî9—}íÄ};NS\dä-67Þ÷wNž5˜/9ŠécKiM¸¤\¯™›˜1ÞŠ‹:†Ò˜ÃªÍ­<ôÏmücå.<óhæ~ªÄS®‰D"üá¹eÿ½àWü¯ÒXч­‰d3šâ¼Üq? MG»„:ÕKà5@Än‡þ´[€±..Þ­÷«€y(?St#xÑæk²‰mHÂ;Xl_Ÿ°ãÊ𢫥öX·ÍÖÕÏo¿88 xc/…·½|cÌv\Öò1iûy{ùƘ7t7RÞNÂk…óu9Ì âs"ò`pQ@ø6cö |w*ðKÉõ´”=iü»1ÆÙÁŽãìÌ^/·Â›7€_",‡Z)7ƘXf^VMMÍ'DäoV:cÌìÁy*p­ÍKàåãaŒ9·ªªêތ߸CDþ-0êׯ˜¿CDäKÀ‰y.®ªªJØï%"ŸŽ&Ûiþ "åÆ˜¿VUUݲì¦>ý“¯ýò˜N>1qxý;\·èn6~´‚íï‘@ž®éá908mZ˜ðûµì¿²‘ožÿ%FOœF:í²tņç?óý?ü^.ÝÎ^>§4-oM¤ÆüíÆoÿvö´Q'€‘Mëßáªï03' â›'£,æH ®ì^ Ö1†XÔÐ’p¹çÅ÷ywÓNþcÁy2zÒ4 b^ywÛ_޾ì¾Y‹niM¤TzÃálà'x¹²Ða½–Žëwéa] |x´€åŸkEï|àÏV‚ëò(»óëó± ¸ø[sss}ˆÂ;ÏÖg^šF=0Æ._Ák»øYà"à.ûÝ#€W÷Rx÷ª|cÌ«º )Š o¦ðJ ‡v”1f[Æ4 ¼¼(€©Æ˜u»qÐÚ Œ±2{œ1æEÿ³ ðZ¡½Îóÿ2¾‹ˆ|ÓÎÞ}Ƙ/?¯®®nÊüHnUUÕ»2»FD°H“)¼555‘ûvǬY³2o—QSSsŠˆ4-’§Ÿ\l~þ.?k&(¡-é"²w³dŒ¡¸ÈaÓ‡müáñwùÌ'çp̧N‘d*eVmÞñFÕÅ7}±,ÝÜâI¯¦7äŸ+­ìÜ|5Ë4÷ÐÑpìûxi…,ÿ>{ñÙ‚—÷úae÷<¼èétt£wœÊùÍÍͳC^¯Ý4ÚcŽß­xÞXî~w~‚—rPük/…w¯Ê7ÆüKwE8{ð2e×ò­ÀßÇîÆëþ€ìÞ”Ý]½Â¼–)»öƒ‹Œ1+— ˆÎ6Æø²ûƒLÙ¨ªª:°« ¹Ùw}cÌÐlÓTUU= üØ_FÕÕÕC2&)dp~Îîħ¯ýò/™PyxID¸ÿî?ðçÚ7YyÛOv}ѳ÷êÿO b$ç`¤}J¯'+¾-£JYú›9<¾éMžzàNŠቕ‡?}í¹¿´¶R Ó3ˆ)EKZ©‘ûå7;cTé¡EFäž;~oÞ\±ŒÿúúLÆ ÑšHw/»¦ûKC¡-‘fäCò‘ IDATW}y&o­YÆâ?Þn¢¸2sTé¡ûÅ7~Û’H,EKЇUä›oáŸhêøÙ)×rý"_Ákߨ9=ÊN_ˆò7Û­ätà?ìzÿ.ùÚÚ+ÑÿNlnn> ¼¼üX+ûÁ†¶Zù^f¹å‹^u^¿Ä™ÔÙÏÖ‡±cRƘw1«2{ ²Ÿ×ÙÏÖëî¢(“ݽ;6òü¶QcŒ9ÎqœtW²kŒÙÐÍÏoX 7 £¡ØŠ=¬ÛŒ@äúß«««Ï!÷mé$àG§õÒ:-Æ/ùáç‘haǶ÷Y¶ü žºöãÁ…å-'±ù¸Æ]×¾b<™u‘N2(Œ¸ˆŒ®ã≮ƒ8xRpß7¦3õº×9v<¥•cxáê³Mýö'Óñh⼋8ʼn´[ùäλ%ÕÖÄÎm›Yµ¼š3O™N<áf]¼úãmžԊí•A ' ¹®ÞµY!’!À@"ér윉,}ñ_Œ7Òa£yjá©·Løæ­ŸˆE–DÊmÕÃOÞÙ„]üÞíü)x½„Ü\Ž÷ ˆB–ï÷pÞÃJðzxÌ p^hnn~Žnÿbe7<‰õ<–ŽG?Œõ¾¬¹¹YŸ ¨(Ê€¡Ç’j¥//Û¸«²y»A¶ô`ÿÊ~x@„ý?»{´ï;9ÆO°¿!x·ËεQ“lÿÙè À~½°>#ÀÐþˆ/Œ*5£ISó·gyðÛû ¾]vE׸ˆ#¸í\l4Äq½_‹Ú4†Hçh¯ém)~¤ ¿ûÆGYûêó8’fTytôæñ¼Ûº‘0êŸH»ƒõÕ¿62–éˆ+ÿüËS椣§’NË®‘]_RÅ‹æJÔ^F½¿ÿZd?‹tLg"6êáŒHoÚf>™5Ú8¸2²„‘¿:ÿį%Rîàê?Ð9 ¯UþðrWwâµÎÿ^:ͼ—ÿ1»EÔॼ…wK?Tš››¶þWÒÑßocsss+0½¹¹ù^Ý\EQá ×uÿMDN±2º©'¹®V8{Ú£Ä.ÓÚ4‡=že;h4ÆôdØlï…õÆ~ãøé—\Þ_ù*+Býð’Ni í‘]Ç“6?º+×F8;çíJ´³»Ž@Äõ$7‚}Í^›ÓûÁðþ5m0õo½nŠÇx)^þ`,ß•8& ;oÎäËÓnšo¾bF.gðàXNÙ5&²éÝLÙ5E Ež¾øŸø^{žo†ô–—Ç6¢œV¿jÒÉçÍ™p9Pqò_¬T‚—Ï:˜}o¨üÐÑZoðU¼ˆóËV Ȱ¢(Ê€¢= "£€;ü.ƺËÛÍ 'Ï}jåxs¦0‹È n¾›+!é±UUU=¾½'+ÞË‹—ÁÿyZÕ)åQʈ·²míJºôcþBð^Ä e×6F³·ðq\û ×ËÍõ½_ ®ˆ÷S®•¾4ˆcp\/:l\ƒ¤½Ô›Ÿ™À'-§bâ”G£eWÎ=ø”Ÿ=¾ü=»lóÕ7­“v¥üÇ_<úìÁ‘TÌM¤dËÛ+ÌAsö'Î-»âçé:[q‘[ßÝm$X›Æáxu7$m2íI¯Ò\W;e›kV1lòtÆ~|ö‘_¸ú¾—oÎsý¥ÍÍÍÓñè^m¤¥(Ê€¦ ^©µj€.óv³°ÿî–cÿ^oS'fuó\=K¼È >jwê[@Ùõ/^†}ñãÎ%• ñ½U,›9×qÚ»ó$´#* b#½vˆxBGÄöÒà€qÛ±Ó܈;âIr°›ßO¯Dp‡W§#±i ’Šó…Ã'ž Ëç—ñR}îcc¾”N&h¨]aF­À‰8»Fw%Ð0Íñ„—Œo{cµhç¨o{ä7Hkˆtü†ÿûí ã8 5„¶«M*ÞÆYû%`Ñ´EQEé_Â+"OcJ¬|ô4o7H‘ˆD»øýSÚØ¥ünÔ&v)LÆ|Ádyʘ1æïß=·»™¬®®~¼ººú×555_,àz4xÝ}ž6¼d:é$‰ºu<1{t‡Ýù"fho¤†ßƒ±¹»Nf.¯'Çí=6dH± öÚ¶]³Ù)µáχ&]¿ÜÓ*‹¦£í<ç£_Z#^ŠÄ~S†F¦K:AÛ–u ;qwî¶7D äáJ†¼ú) &Ó‹áà«ÿ8ŒZ‰P6²‚ô¶õˆ›bê¦ûÙyÖ~yEQ¥?ïžäífZ€ÿ×Å$?”ŽHæ}aýµÿýšššuñûggË󭪪ÚjŒi³Ó|µ«y¬©©98ÕÖï¸ oéO›y"É6ÒÍÛ‰“¤nhl—èn§(¬ñ¢¸þCÕü~vÅ¸Þø€KÄ‹âºÆµ²Û!Äé~ÔØæ„oób’N ·e&äs§ŸH~DQüó/þI´à67HÔ¤ˆ•ïÝm>R|ÑÍ”Ý`t7Ø˜ÍæúéAÙ•H»ãwÚv’bŠœ4ÒÚ$’ŠóóÏú¼Ç˪ð*Š¢(ʾ.¼"2ÚsG@@ÇïÅo]+"Y„ú+"r„ýýÅÂú¶‡¹º¦¦æ”,¢êwy–+á _kjjºêÚè‰À¼fÊy$ îaPvØÿßÞ½Éu–wÿ=o_f4=’<–e[# [Š‘E ¦'`lŠÝ8ËeE¶Øl’­Ý"­@²›„ÝJU¶Rv³Éº¸­ › •dCˆ ) Æ–z¼8xt³ÁÖm4ºÌ}úrÎyösN÷™™¶-“‘Œ¬ïÇÕ5Ý£¾œ÷t—ë×Ï<ïûn¾Í“X>FÝ4’ŸðÞÈ, µéŸó­×»›…YÏpIi›BvCÒí÷U9K¥B¨ÍZÖh4~B½½æ_™]ò0*I¯s÷/?Mðݪt]Ë`f#îþ™>ÁZ’ŽÖëõ·÷yüߺûoeÑg—¤÷(]­ïàÈ€_c%%qGG6mÔÊÝL¦DérbféÆù¤53ë¶+ä•`…^fíÝ$_k7MŽîR0—[¶žCv=ï•-¾=G6 È-–¢–6UtÖ®Â$ \Uõ­ÞnÊ£ŽlpÝêr½÷6‘ð¬kÝ/Y…¶€-kÙ°Þ"qyl2y7èWnè¶,÷‘KRµ,oy§¥«*Õ­¢¥€KâÙ¶4lx¦çÊÂl­ø˜g³ŽnŸ×É¿ÍÌþ£Ò oËBf†wšÙýOõ„cccif[%ÝcùNciUøOêõº¹û™Bh=Ù'4/•”.«öT/óîz½Þwy³z½þUI¼â<ܼ†ïcu})ö8’… ë+½°›w5dÞ4€™¬xÛ¼þòJç²å¸Ìºá7ˆ¾,0zwg²ü5—õñÊu¢V–dò¨­Z9V¯Â¹ã¯ÔBTÍÇï¥þ ÷ˆè†ÓBPõ¬Ý#»ù)rI*T|­0A­ûq´ÞmïyKé7Ú*%U¥“öØf€‹ì+¼î¬ßò+ƒDºùÊ ìBÛO’$Bð¿Xq,¿èîï2³×+ݱë~3» -Cëõú ¥ûÚ÷;þ]y`ï·{!8¿MÒÛÆ+ÌìælÌߨ×ëÏ´u±ÆÆÆÞÚh4~ÍÌ^“ýêÀÞrYqEí¶TT§d*ì ¼üénW¸ÝM€iõwÅ=d^z^q¶â{“þmY–3uJ’†¤Ö‚ªåÁl‡5 |&©TŠ[JÚK®Ê:ë÷¬Ý°[·Å*o·ÝX½Šµg-–OøËƒn¢îzÅ–dUÞ¸·ñÊ­†ó~ ¨ÉÛ ^ –5Páà9¼fö]÷WšÙw=C+„àOu,fÖ’ôWò<ãããor÷Ý’¾566ötÙZ¨Üþ¿ ¯û%í¶ã;-éãÍ´NKžtd¥Jw ܧysÒp+)ÉCq÷vžÂ|Õ5×êV… “öô†RUq<£gÞéù»ÐiÉã–¬T‘+zº#‘õˤÝ@œµ-Xe%n—ÉÜW|£[ýÝsäý¾0¸¬< diFŠšü߀KäJøsê‹%½OÒgÆ-Os¿_Í´êõzt™1‘µ:íŽGi…·ùªÖ+¾æ½ –…³n_C÷¶¯¬Ðæºò “Ù ÁÚ»¯³:NV"—J©ÓT«u$EZ›Æ\RÜIb)ꘪƒ’gUÙ~A×{‡m}º”H–xö Å­ÓÒIiùQ›²IjÉêS³ìµM2R©"ë4­“>>ÖS¯ ¼l_áúÇúÝ¡ÑhüŠ»oϺ/þ÷e8ÆDR{n±5¯NK’4ºdË—$“änÙâ YØõ|Ã5ë=Ëï›Ì“^kþø$û÷ÄÒ`ì½ÆÞeý®^ÒY4m†ôߢ––šó’ÚkxI…Åf;¿É<ôM’y(µ~a7³q:>O¼w¾\RœMZK²ŒŸßbU×’þ•ãô”{ã_XlKꈭ…¸èÊÏ÷ÖëõûƼ¤aI/?#éwÜý ¥=À¿(é ÷ÿ¹Ëp˜.©y~¾yfÓp|µ<Ñ çËúÎú>w˪eÕÚ¼"›tƒna©­Âd,ϬÅYÀMòP«,`öBuðÞíâk¿p±*EMyëì\óŒÒ¾†µ¨p&’ZçZLJ*íéÍŠV­öÕ]^,ë¹Í'äåãÉzse’Ç.“¥+3äç ±´ê›¤ýº*ß<,ç…ðU¡×% ƒéø=ÖùùæqI-QáࢻRfˆvs‡û&wÿï’þ\ÒïîÖnÕ„K-–´xl¦ù¸{,o.è–ùÁU=¼yÅ5­h¦3µ,±4¨¹uCœ%Yµ26…$ ¹!6©vÓênz»WñÌ«¿î½`]\¶á¥óUÅ‹³²$ÖñÙöã’µ6kк¤öñéÖ„²ñ[ixus¾hE^ÕÍÇšUj=îݶXòÈ¥X²(½(r)*„Ýì~ݰ[xN_ÕììR©¦dqF!‰ul®3¡µ«p€+=ðŽÍ™¤HݥNJ??266fõzýÐe<ÌÅ'[X«53¥š]¿,äuKŽÝ oa›Ý<ìfáUnRº!×b“SH²°BoR ºyXÎ^C–…ßô@~ha“šÓSŠâDNµÈïZH$5÷Ÿj?’XíÙ3F$%Z¹HHÞsë+ƒn\K)í.Î.ž],Nï›_,îõóZ¡··›³ó€méTÀÒºkÕœ9ãI’è¡Sµvnp¥ÞBð}g|ƒ™H YÐ}çe>4—´ôžû÷vâD­æ’Û±F›¥Þ:»žþ‰ÞòКÛ4Ì®²ÅëqÒÛ ÈYH̪ÁyåxÕº&¶*l'ZZ˜SÇMïy ¹WÒÒ¾Ö»¿2ûÅÈ¥æÒ‚y$™­[µÉIwÙ°U]Ï®«xɯE}®CoÜ{¾¼Õ£XÝuwY©&O‚çg­ãA¿ùÕù/Š–¼K½^÷z½>]¯×Ÿ/aÕN€šœ8ŸLtTÒ™czËäHï_³àÙL–õëæ¡6mUÈwã¬jŸÞ}K!î…bsëU‹AÚ-Ýx"k€}ËÔfÍœ<ªÄ*š8MHšÌŽy-Þ7©-E'NÛDd%-œyBap‹VUy½°lXÒ«ê*¯+CmÔû]±Ò«BØÍ+ÅÝsU8çÝênm»fN–‡Š&f4!E'Òc&ð@àÅ…Š$ÿ‹CíÇ2þöa½vq³Bw§…^•7¯È.¯ÐªW©M, ·Ë{xCÖÚ ¼ç7¯'½àkÅÞ]O_p×ëš×ëÔ‘o©-Ó§Žt>.é|vÌk•øcIóŸz,ù„É4õÃ^Þ.³Òê­¬óUÙV¶'+¶qÇ- ¾Ý ovÝV„ßnðÍ¿XäÇå.³’ÊëoÔäÑoy,ÓgóOHš÷µé_Þ+F"iîC·ïmùb;1M=þ¨~ãÄvu˚ʶÎÃi\ ¿–UnÕm]‰¤8dm!­èæ“×â^;„%…Û+zw]ÒoLîЩ룠™V²øá‡Û÷JšÓÚNØJJ¦…ß}`fÏÙvh·cÙéÇQiÃÍ’Ç«ª¼ÝPZ ¬ÑŠ0›ÿì,ÿýÊžÞ<{qweÕ]Uy±&PÇÍεCû÷öÏÜ]2-ˆ kxñ¬µ%ü“GÛðPÒ‘oÐóµ½=P˜´Ö ½ù2c–H!éÙ·2tûwC·§7$½ª¯ éNT+¶2˜´½3 ;7ëàø~% ú³‰Î$ÌŽuMÅ®¶¤swñ;C)èè#< ^/«¬Ï·Œ~êÐ[èËí†ÛxyÈÍ+¾Ö§"Ü/캻¬ºA¡vƒ>¼ß-ýÅQ¿SÒtv¬€À‹g›ù$Mp¼½gr!žLJU5¾ò×ú£Ó/OÓ˜YÿЛ/1–ßnÿn(ôð†^Oo^ Î[âÞu7ÏÖ²Mw¬ø£é¢‡¾ü)y©ª“ Éä‡nï‘4­‹óçü¸4÷»Ì~ìäR˜JJeÿÊ=^Û~G/•>E赤PÕ—Wv‹¸¸rƒòߩؕGªí|ƒúÒÝî¡b'ÃÔûg?V ší \2%NÁó2ôvö==üã7U~úÜ̬J¡¤y©î>¹|Š”·–‚¬»cšdÙï,±leƒÐkapSHB¶$™¥m yØ•IAúè¹Ûtnÿƒ:41¡ÁjY¿°wéßÎwôJÛ.Êd­ÄÓ#øÚ)û‡ÛQþɳ3³²P±koºMéC²P^µTYï\d•Úâ:½ÅM6Šëïöé×í†]IŠÛZ·ó:4þ žø–†*öK×þ™™fühâš“Õ ðâ»æ’¢…އÊÒΑêí‡ÑK®ß©úÐ ´oh²w¯BèÍ"m·5!xö›|†ÄÒ pº+2äýºiÈëµ1Ȥÿ23¦-G¦õ¥/|NCCƒúüãÑûÿï‰ø¯$ÑNVë7þ’)>ߌ¦7 UËß·1Ü~ðÐaßrÃ.ÛxýMŠÎ•¬Ô?ôæ¶Oèín&‘ï¢Ö/3ç•ݸ¥Á_§Ógæõå/|Æ× Ú½Çìƒ{_ÚS2÷‹;~@à½"$’Úß8<¶{$ìÞ4<°ó@c\¯Ü²[oz‘>·îX¯½A«ƒo¯êkËÛ<ëßÍï—Ý<@gm 0w‡®9t^wüOU« éà¹hï‡n¿WR¾îÅNüIµ¤Î×·ùþÍ?°i¨´óÀ†oÛùbÛtãªsvB2“ÙÚuô¤a7‘’XµýK˜œ×ž?ýc__[g‡fKûî|pþ׫AS½»x±f"IKw<~è–ÍáåµÁêèýû-×ôîmoÔ¾ð„fJQ/¬ê)ÂïŠÿÜúl×kiØ}ATÓg[?®C{ÿ^{ö|ZÃÃët|>yèwjÿ{IùV—äOù±+®uö>ÑþjýúÁW WÃè×÷ðuöëU?¡öùoËÛsO_í½À ›~Åè( ^­coÕ}Ÿöìù¤o´ãKåï½þ­Õ ©vr鯖E<IÃ’v¾ó–êïm¯9·ÐÒ [·èío»¾Z;«ß^÷ ’üSà~ៈÂú¾Á¥ßjÞ®º´YwÝu—žxò¸6êÔ|²÷£´ÿ“¤Ç$ÍëÒ/Ã*AµN¢ëÿÃËj¿¿u0zõÔBÇoܶE?÷ó¿d¾ð¤šßùŠô~âÉã°É¥°ïŽ/þB%èT'a2ž+TxŸßòØæœŒï«u¶n¨Ü>9½ O~~Ÿ^hõßnz“F£šŽ†óš Ï¢µÔL[ãš~¹ó2½O?¬o~îkzßÿK‹í¦*e}c2~ÿžÃ÷*­ì>WaÏWT jýDço†ª6:\ºýÔ¹yûË{¾äµ»åµoQبxñ¬¼y^²PXÉÁúœN¥½QSah³ÖíøaÕ¾ÿÇtÏ—ïÓýwùBkÉÖUËvàløàŸ=ºôë• )Â.Ï-*¼WÎû¼NÒµ«zé¿zaõ£’®›mEj¶½ù wèM¯µÊ×mÖÞö!5ì”–Îéx˜W'Ëi7mM6èf¿Zcº^¯©Ü¬hrJŸ¾wŸþüž¿Õ`UÚ8T•'>ù食ŸëèaI§%-é¹ÿ3¾•ƒ£D›6 •o{ó÷•?bžlživÔl»¿éõÿÌÞüÆ®õ#Õž|TÑì1Å SJšÓòh1{‚!…Á*Õ®Uyã T½îEš;?«O}þ¯u÷=û| "ª(±0õ¹Ç“wœ[ìИJöH:&iViuù{%ì™I—ÖKºú¶ÑêÏÞ0¬w5£¤ÚlGZX’nÞ¶ÞoýÁ—ÚKvݤo¸A×]wmwüÑÜœN>­oç =rð°<0³ÚT)—T*…ö‰»ó¡SíI:gÒœo/®AÒ ¤«$m¹i£½at8ü›õÛ$®ÄE‘+Š¥$–’,®“BI*—¤rÙ,(Ó|Ç'ŽÏ'?2ã÷*ÝAmZRSß»ÂÁ4¸6Hºj÷Õ•ŸÚ2äÿºVÖîÄ]q²jüžßŠã/… `¦…H'õ‰ƒç¢»%Mi6IW¢ …/žc%IU¥ÏI×íº*¼vd@¯,ÛŽjÐ5¥ áV…•H8Ñ|;Ñ™fäO·´ÿàtò7’&%Wº™D[—Ïb%“ª.Õ$ K6ú¢«K?22 [ËÚ] ÚZ6«fÛHÈeŠÜÛDÇ›‘&ηôà£çâ/J~BÒ¼I ~y/®ARYi°]'i(» JPobc¬´rÙTº¼Ø¢ÒþÜ|Þä2þ²qç×++ÆßÉÎA;;­Bȧ¢ —Ág!ÿ<”²ë!»( tÙ&»Ý*f¶÷Øó*üÛŠŸýÆ_ü Бà-IDAT€µñÿ-C䩊V|,IEND®B`‚nzbget-12.0+dfsg/webui/img/transmit-file.gif000066400000000000000000000101201226450633000207730ustar00rootroot00000000000000GIF89a öÿÿÿúúúâââÒÒÒÔÔÔîîîüüüööö¸¸¸lllDDDNNNˆˆˆÖÖÖôôôÆÆÆLLLàààêêêžžž¦¦¦òòòŠŠŠ666ºººÞÞÞÚÚÚzzz<<<(((,,,¨¨¨vvv"""ªªªVVVìì솆† ¬¬¬ÈÈÈ &&&„„„ÂÂÂ>>>ttt’’’ŒŒŒ¶¶¶000¼¼¼”””BBBØØØ¤¤¤ÀÀÀÊÊÊÌÌÌZZZ$$$ŽŽŽ~~~rrr€€€¾¾¾®®®|||–––hhhjjj´´´ÜÜÜ```²²²°°°xxxÎÎ΂‚‚XXX222ÄÄÄäääøøøðððæææ***bbb^^^èèèppp!þCreated with ajaxload.info!ù !ÿ NETSCAPE2.0, ÿ€‚ƒ„…†‡ˆ‰Š‹Œ†)ކ)4)ƒ3•„* 5 A9@ ¡‚+´&<…‰ ´Æ ¹„)KFNˆ!ƵÒ‡% ˆ"×!'”‡Ïш ,× DÊêÁ‰# 6. „`xUè-TØáÁA d ‘Ãé1ã” ‚†_¼r`Á…A”»‘¯Q…'LìpH`A¹½Q0BKAÎ Ê1ÈÙ” F ` Ác.pdld€‡Ë(Ø`b‰“ Ràp"Œa=xa!/{‰˜6ºƒ€BÆ?6Å%bÆRu$`2$.‡.6dCÔE c!F(C€AS%hE¡‚¨‚‚Ì´@ø  ’$'rbPƒI )D vÔ £ÇŒºŒÀ(ÑøwFj™2µÐƒ3>Xèp@œ cF<:à¡IˈT¨#´JD'7ݼ-üMKšÆ %&Ù`€»£@!ù , ÿ€‚ƒ„ƒTT)…ŽŽI((K/™4FFš¦K  I§…AFL¬ FA±‚ ( XMDF»%½¿$«:(NIÒ .¯‚Œ<»<(0 6[C¥©I™B!Ê$EZ 3ÜQ¢8È$ÀÊ8V+r`B"‚on)•‰‚’O`0€àLÞ‹' "(H€¡c# † B Ö?“„081²…[0 ¡¨ ' ÆBÁŠ~`+A“³FB(€àŽ¥M;zˆ"ƒD<šô”ÀbC ¡§t1J€'UœjÞ̉Ê! äÔ$à€ êîuË §8ä{eÁŠ#Q²”‰%UPNÂ(N°Ò£D&£°š€$sĶ`G˜ÀeJ& 8D0± A©„)жƒ’K¾jÆEʽ@L¤:D8'‹N [ <-\¸¥Q'["& /_‰´ %%: MÎ.O%¸. T‰ :¸&9A*G¤,N&Î J‘.  T°`„Á‡.–Äs¶¡Å°BNp‰! 'ˆ(Äà”{¸Xñq±P zDÈ1@Q6ä}è A…&-†ThÃ#=Bl¸A¤•!BíYÀ‰¦ Â@@ˆ-=!˜¥­É–2Œ@øTHU¶AµTh ¥A‹S¶<@qb`PéJ)¸Ë–Š]ƒXàÒmƒ…°Ÿ*phJ¨#F,ÔÄ;é@ :qMÚZK:@É’(¥ à%5¡Q4À¹Ðƒ5”P˜a)Pb+J¡F„"“‚lQâã݆pE ÑŠ8‰’y3 #¢$!@Ô'IJ(é;ÈÁ¸#9Þ«&T€A‰#8è·Ÿ \ {‚Av„%HZ !ù , ÿ€‚ƒ„……Š‹Œ‚SM‰•…[@K”–•D %b Š`‚-b¦%QEA–-AN"´¶* Q-)*=Ic0µ/XG¦%S¯‹E ´5 M <5´Iª‹/5˜˜Ú`EÐ (<Т‡>[UP`¢„‰(5YPÊ”‰þx¡ÂÈ’&¾ ÐÒ#†(8¤[tÐA…™Š8Èa„!ARà\µC.Wp,ÉpeÀ'0 µt‚‚.LAâoŽ-FZ   ú6\ ` ˆ— µ2%O ð,„èƒ,‡É8@Å„# ‚ …>I¥àÑ‘©ähi ƒ!ƒ¤0ð‰¨‡ zlˆre,YF»8™BÃkPJ+ªP ŒnFˆ@uX²@'KA$À<èÀ.§,¤l”` ×°}DÐäÊL)lFD€¥-H )àE !… ÂƒOT‰Ðå÷íAXPFÿUB/ˆW pþ-X` !ù , ÿ€‚ƒ„…†‡ˆ‰ADAŠ‘…¯@<EAš‰D/%% ] Q#‰X=2Q¥?@b C[:¡ V‡C5¯=çMÐ?2¯+3ІDÃNœt €Gî¹x2Q ("4èP° J0)BH.~ˆÐPÑP 6DØAQÑ’‡Txa…`n™`Ç z¹s‚(Lh¡€À©D,XÙ·³ƒb¯LdH`—1 . ”¡Ç½bF:t…°@‚ \Èwqu¯D*‚<$©Á`ŠÐ¢Œ{%I.|¼0P”V‚*\B€HR à:0B (?Z™Õ˜9¢ P`ÁAÎÒ…‚`ñ Æ!qèðÂKf60¹'ŠiC>,@Bµ-ƒï°r¼-¨=”bIôW5¦T'TáÂL„¼Îå¬#¤=ø–þPAAžô°QBCÜ¥=‘¥PQ¡À Íí$€}˜RPðQi¨Û†v!ù , ÿ€‚ƒ„…†‡ˆ‰‚_Ї/BS˜]F=@9˜P&%[Dƒª—ƒ2¦Fª/D­ˆ]WA°Ef J 3A¾… [ IƒS½)H´Bˆ  >Ô… %+Zˇ-= ·UhPŠe¯Xèò@R6ÔÀBÖ¡0XLô€À†ÊÄêÁƒ#!råÄ „`¨€ %¢pé‘ &³ Bf4jÀ“v¦P@„` @$€¢ž½'PоPA¢Xe‚·]¤ŽL!ˆ"É>t(ÏÕ¢DÙQ¤¦ÍAøÑ¢ P¦<$ n0|T·P ìI‘d®@üS`×€/¢ÀÐ,!ÂÊ7‡0ÈP†… ¸€P IUÖ šÀfŒd †0kž`äšaD! è@І8k–‘¡E•#:&D¸"”5XK€ÅÁ ’Lù I@“LXë˜A³B‡ 餄.h-õ4a„ %À‚ ž-Ë D¡Ä(VH !ù , ÿ€‚ƒ„…†‡ˆ‰Š‹Œ)Œ“‚ ”Œ(’›‰"% 4£Œa°ƒIFPš‚R N)ŠWZ<ƒ‚] 0.FD͈FG AŠ#!5<‰*HŠÏ .&QE«†AÖ‰N$OÆ‹ÌKô€ÂZ¤*ì6€‚ Nª $J”V=pAצN€À°ŠAvDi"1Q… .•ˆBePŠ;´Ddà8 .”xdU¡©/¢ô¢Å1CwzáGC  àcÄ' € E„ƒê©XÀeC”+aÑ¥(’ cË Hz”§Á|„*Œ0RƒAŽÐ@®qˆ€ñRF–Ø`!X!SjÄh,A4TÁ¤”Ÿt(éuÒV,™bÄD‰\T±Ð£ñ„´ yxb‚‰Ì2È ½ #.JÀ2".!C¢p™"”!$taaÀ)(ŒX Ãð9¦¸š±‚ô†`@@DƤPÎ<,°WnR0H@ ¥`ÁÔ Àh-$ˆ}WpÐQŠ!ù , ÿ€‚ƒ„…†‡ˆ‰Š‹Œ_’>‘“‹AO(Pš‹D(!„™‚O<ƒ- - Š>®)´‚C & Š[-ĈN\«‰M;D̉ $®ˆÚŠ–ì…è»öé>V]Bz0K (vxÀ·è  e… %DäÐÕÈ…>@¥°Ðã¢ÉD" ‚EÄ‘- øàÂäE.T:0dÁE1? Th`¦M"6$`DG B<¸¡„I ²J1" Š îu@Rc  ¦HkTH—ààxPE…*(etÏЃ N|ؽ…Tè 7‚QŽ”€ÁEK‡iDv(Èð Q Gl™ŽÐX$QP@e…':lаP!ETV ðáó⥠UÈPô"“ #‚\‚âC †TŸÀGðA80ÐÁDÂê&#À`"Ê‹Tl0±a‰ÖC¶¢D`@‚š&¹X0€ €…%piÀAئDlõ… P-"À…‘ÀÒCw%DÀC<EXÀ@pÁ€„h/W\’N„;nzbget-12.0+dfsg/webui/img/transmit-reload-2x.gif000066400000000000000000000101221226450633000216530ustar00rootroot00000000000000GIF89a õßðØFˆGÜîÕ»Ø¶ÑæËÕéÏÂܼ‘»Ž¢ÆÙìÓÏåÉÝïÖš–¾’ÍãÇ´Ò¯¥É¢ÓçͮΩÊáÄj iyªw®}ºªÌ¦ÔèÎq¥p†´ƒLŒMFˆGÁÛ¼½Ø¹ÇàÁ_™_~­{W“Ws¦q¯Ïª!þCreated with ajaxload.info!ù !ÿ NETSCAPE2.0, ÿ@€pH$8GqÉ$NÄA§3( šL ÅV‡K|P(¡”:(r±BÑŒ×_@X!/Î BxBnb}EgƒorEgŒ^ oWD c JCg oqmo „£± “E µ {p~ ¸rÅËD¸}Å M¦d¯®‡¡àÂKæçÏrìí ¬ññ‹‡…o÷ òò|õøøåè̩òÀ]»q¿ÄÓ¤` 9Cëf)€$'Ã=‡ÃŠ}“Cì^u›-ûH.!‰…Ü»òݧ¯Œ ÉOÈK"1㺲ä5.&{jíT© ÃBBoÆ eØð¨Ä6œ†<·@ÕB?±¸1 µ)¯G»b K„¸ !ù , ÿ@€pH$&4BqÉ$D…b(šÌÏV‡¢Î[4_€”:t:"r‘qh@ÕÞa¨Ã)ȉgBk_oEg~…#rJDgxloWF C ~mg  oD B ¥wK! ¶ ¨C¼¸ɶwE ¼ ÓÊ¢dÌX ËÒßèØîïìrðïæÊ÷×ó÷ûü êúýÐs'¯ÜÀxM &T$$‚|MøûC«ÚŸ Ê A¦¬€BlÔd€è°Úµ‘KdVÑÖ?oFl-X ÁL[äJ*ÅΞ6‰!Ù"ù 5\@ïØÊÀpÐoIm  ÖN!Q…Xmš@×§ %2u:uH2\ËR•#“a!ù , ÿ@€pH$ BqÉ$&DÇáà(šLÅÀV‡ [$ „¡”:4P( rñs†Ö_€…¢IȉgBxBoE gw^ rŽFgW…oWD cB ˜„g oD%#Ÿ¥ v JEin¶E» ~"È#‰L »Ád$¶ÙL  ¨X"yrv¸E —ð—ëñð¤Í»ädÌøýÍÏôü)xWOÀ<2ë²°IÃ&×ô1a‹€¥"üÑUJâ(q¥zý ·+ƒ¾kQ™\" ƒ‹»*ÆtRjÁ‚Oî}C©ÀÉ7":ðsòdE& ¡ R*ƒ"#¥ž-Ôñ©Œ=…L€ÕêÖ¯QŸ2s:d+qÅVµª¨æÓ !ù , ÿ@€pH$.AqÉ4 ÈBÑÑd&"t –jQ P(…Û.Àq8”ÄÅHy˜"3\HV$ÐQBm{Oe u  pzB eRumTDC” ’W ¦mDCe ¨t’B]¯c´~† ¿aV´jU¯U¦f{ †‰†±L ‘ã‘Þ{[HéÃøUñòì´Ó{ðóñâäåܤ¨i‰98á\q·„ ¾*ä€Ú3« Ãn˜% —±L\£@éùØ0ƒ·ˆ÷ˆ¤2ªT‚¥* 9¦™}”tùIÇ*D9…xä6¤Ï´ F]l"±¨Å!M¹!0Õ¨T,D¦*²FT×®pq !ù , ÿ@€pH,*‹¢rILGA±À\&™g”LªE¨B*“…Á.FŽCóУv¶ËÇ~,$¨wSGIr jmgB GTrjTDˆŒG‹WHŠj\ CG v‹B d¤a© zw%µ ¡V©“`¤ ®E |m%ÄmvÏL ŠÙŠÕ` ß๹ÊLéêä©»wèëéØÚÛáà¦KÜUå»b®È€¦-ðFA„;!:tàpë_3G•8  ±CˆE69<“A™€ NŒè¸È;"›,H@¦#"8H„Y³ÉM498À ÍEB[É’oÏ.96dÒ0NRUóAítU ¼DX´8Òl*¯«MÛ Ê!ù , ÿ@€pH,*‹¢rILGAQÊT&™g”XP$ªÓ­ª Èp1r’Í™¶Zë‹Éß9÷˜| jfB Gi~WeF C† „Š}RiCyq‡N„…N ›aGus ªLŠ —`²›¡K ]¤K  ÂL¾`E ƒÎƒÉUÔÕ]®ÙÇKÞß ÙÙË`ÝàÞ ÍÏÐz¢ÖÕ¹ÌîµîWÛDåJlæX @AÃdØpESB`A X¹ “°!1Pá!"®–MZ Á:ˆ AÁ‰&›:³EDÊ!HPƒ­Ï–5:„pçæH?¤„@À¢‹„ŒèÀA¤4£ò£(Y ¢Ð¡DÓVøÎáã.!ù , ÿ@€pH,*‹¢rILGAQÊT&™g”XP$ªÓ­ª Èp1r’Í™¶Zë‹Éß9÷˜| jfB Gi~WeE y† „Š}RinlHCq‡N„…N ]‡aGuz§‘¢CŠ ˜`­ŽL¦‹j†Ÿºz·ƒÂƒ½`¦ÈÉdªª¯LÑÒÑÌÌÎKÐÓÒ ÃÂÅUÉâ×ßÏ¿W×Dêcšj ·­¼sõ0R%‰Ó+qÀàß"ªlQbwÀ Р‘F½.Сà 0âs3àƒšSIHšD§‚…svVÉ< Æ+‡ç4 Ù9¤ ¶ÀøBTˆŒ~AÉ2´$O!(\ÅÉÔ*×—8Ô!ù , ÿ@€pH,*‹¢rILGAQÊT&™g”XP$ªÓ­ª Èp1r’Í™¶Zë‹Éß9÷˜| jfB Gi~WeE y† „Š}RinlHCq‡N„…N ]‡aGuz§‘¢CŠ ˜`­ŽL¦‹j†Ÿºz·ƒÂƒ½‚ÃĪɹsdÊΪ¯LÍÊ ÇÄ¿ÕÖÅCÛÒ¿WÑEÝOšjí·­¼Ìí³B±’B!J`*ð¡Þ"ªlmèÐÁ8p õˆ4êå€!(P0 EM(PC!Ê#Ï48€à› IÀ2Š=èÀÏ! 4§CCŽ q@ñïB‘*Â6‹p Ë$ ïô!ù , ÿ@€pH,*‹¢rILGAQÊT&™g”XP$ªÓ­ª Èp1r’Í™¶Zë‹Éß9÷˜| jfB Gi~WeE y† „Š}RinlHCq‡N„…N ]‡aGuz§‘¢CŠ ˜`­ŽL¦‹j†Ÿºz·ƒÂƒ½‚ÃĪɹsdÊΪ¯LÍÊ ÇÄ¿ÕÖÅCؿ̫`##sËEés¸G¢ò#%B ê)àŠMå¤ãp` &0 EDba°¡Û7ppà@7gý³µdD#Kv[@Ñï!ð(@BÒ$O1Š0oV  !OO>PÜÆäÄÜd™8@Á¯,9JÄÀ€,7‹p +¨A•`‚!ù , ÿ@€pH,*‹¢rILGAQÊT&™g”XP$ªÓ­ª Èp1r’Í™¶Zë‹Éß9÷˜| jfB Gi~WeE y† „Š}RinlHCq‡N„…N ]‡aGuz§‘¢CŠ ˜`­ŽL¦‹j†ŸºzLÃÄ¿gƒÊRÎÏ¿dªªÍÐÎÒzÔÕ ÂÅÃÇz ËʽD¿çU sP¶Ls¹J÷6L˜ƒëHQ V0¸'¤ÆhZ%䞎8@@€.‰EDƒtC l”V`XšS¢©°ñ¥Ë?X q°‘!¨+›½Ãà@*-ÌÒ„ÌÀFxB’ÎÂó Ä\€Få§3ªÖ®·† ]!ù , ÿ@€pH,*‹¢rILGAQÊT&™g”XP$ªÓ­ª Èp1r’Í™¶Zë‹Éß9÷˜| jE Gi~WeE y#DŠ}R† inlHB!‘$CfC N ]‡E¢›j® ¨¦¢$s·ŽL”j†NszÊEÒÓÍgÙÚÞßÍdGå ÝàÞâzäæ ÑÔÒÖz ÚöÏ\ÍøU ¥j cb€Á æÄ£„„ƒ ÌÓª\] 2!d‡ZnVéTgÈÁÌL0 Á‚F 2T¤€È‡ û `æÇ•®2Fj °”âÀ«k °XT‘.5X:)  Ó5,'RÅÓ,ÃtPuyz*R0|š!ù , ÿ@€pH,*‹¢rI4ˆ‚£ 8e*TJ,(Ö"¤Ó £Š*ý GN­ÎÝÅy#¤÷`xDdk\GnEd‡i ˆC Dd S Gm}GI•B$J ­ ^¨E°ša½ jD °xÆÄK n§­x‚ÚEâã©x¥è¥ íîÖçGóóìïíñnhô¨äãæa¤C÷ J·‚V8pI”hÒ> ðKE˜8ÀD\Æ2$ÛÄñC†!"•Ð¥j1Ž´YB² Ó‚S ÌÍT@DÁ„5d8Ÿøé52“›^Iü$ÊÙí  ?”*aijŠ'T5U›*¤ÍG‡ N:J‹lÓ±#Ï2aÕ-;nzbget-12.0+dfsg/webui/img/transmit.gif000066400000000000000000000047611226450633000200740ustar00rootroot00000000000000GIF89aôÿÿÿÎÎÎúúúààà°°°èè莎ŽÈÈÈœœœØØØ¨¨¨ÀÀÀòòòvvv†††¸¸¸hhh!ÿ NETSCAPE2.0!þCreated with ajaxload.info!ù ,® Ž$AeZ ù<ä ’„ÃŒQ46„<‹A” ߈áHa¡¡:’êID0ÄF„Ãa\xGƒ3€×!Ä ßO:-‡‚Rj—TJ‚ƒ* ƒ t †ˆŠŒŽ„—~—" ds]š  š)t–¥-"–i;H>³n§Qg]_* ‹ ®R±3 ÁGI? ÎË´¨v$ý›j3!!ù ,° Ž$À0eZy¤0¨£q £ãŒP¤Ð£W  )"; qXˆ^ÏD50‡† Ո̢%‹`‹£ÔrÏJ{ 1‡ºÍ$ʈ…‡!!ù ,´ Ž$@eš6$‚Æ Ž`Œ 3*Å=‹  …ßÈ Pˆ\"FÁ©’í²`PÐ-­ƒÓÐd5VÁ"2Ÿ|?n"!( ¸Š‘€è•)e€„4xyc?   ‡‰3‹…™™ ”#wyJ l% o€^[b_0 V T[0mœ $ƒ4 >„'VZ ¨cη3ƒÆ$Xš¼%!!ù ,­ Ž$`e𤢍:D3 ÂH0¶,'j0¾Qƒs‰ ‚L(2HM¨Òj#Ðȉ…BŒ \Oéi`u§†=YŒù€EVL=I  ƒ…>‡‰‹•• suI WJm| \"_…b0 B¦ cV"d]*K1" H|@B?ÀI4…È#  S$¿-|¶|!!ù ,¯ Ž$Ð4eš¤a¨:D Äh¶Œ³·œI Á/€K¤$W-á 0(`3œÍÑFãÙÀ=±pf@ïýtéøQ£ìÉ  {f~*€‚„yS*mg) ”enu E^Z^ g@ kw(b& -w#"º xW"¼t #Ç#”%U$Ë`¶t±o!!ù ,´ Ž$Ð4eš¤a¬:*ÄØ± à1œˆ–ó‰v/€Kd¨ÉzÊâé–<Îp6%tP5Ù¡êS|ÉîØH(²FÕ¯›c¸Œ€`05xz*|~€v‰G„0t#  F hŽ0  #C d 1  I¢#(i - “ À uEL q ³Ì" h%±$Â$<Š·q!!ù ,­ Ž$Ð4eš¤a¬:*ÄØ± à1œˆ–ó‰v/€Kd¨ÉzÊâé–Îp6%tPÝñt¸ª¯é„5Å©3ÔÈn»G$ò€´ @aˆÏëwy{hoFS>k#  F Y" Š% E  Cb AI4$ (z¤:2• mI L½l#Æ# ¦F­#É#š>²F!!ù ,² Ž$Ð4eš¤a¬:*ÄØ± à1œˆ–ó‰v/€Kd¨©ŠVñtKG‚ˆ22ôëŽ7‚D"¯ªé$)‘„±é•Qø¼Šqp8 y l |~€‚„6zw2j# F Œ" ’% VŸC œ ]¥6a$¡ Q ª:2 \  EF I—&Ãx ¯"Í“¬F4$Ð]#¸x!!ù ,³ Ž$Ð4eZiä ’J16„<‹ŽˆB” Š?$r½œTêHzDP'"lä(†1±5–y½Ãá8tg†œ—p,’qÛ”Múÿ*   q ‚„†ˆŠŒ€”"}•# b?y{ {)Šs -s­:–9>e ¤E,C\3 ‡^·3[ ¬ž¾¸°S·Ä|˜»²?!;nzbget-12.0+dfsg/webui/index.html000066400000000000000000003352371226450633000167720ustar00rootroot00000000000000 NZBGet

    Loading...

    Please wait.
    Downloads

    Delete selected downloads?

    How to delete selected downloads?

    Selected downloads will be removed from queue. Already downloaded files remain on disk (this behavior can be changed via option DeleteCleanupDisk).

    Selected downloads will be removed from queue. Already downloaded files will be deleted from disk (this behavior can be changed via option DeleteCleanupDisk).

    Delete
    DownloadsDeleteHelp
    History

    Delete selected history records?

    How to delete selected history records?

    Selected records will be deleted from history. All files remain on disk.

    Selected records will be deleted from history. All files remain on disk (for failed downloads this behavior can be changed via option DeleteCleanupDisk).

    Selected records will be deleted from history. For failed downloads (par-failure or unpack-failure) all downloaded files will be deleted (this behavior can be changed via option DeleteCleanupDisk).

    Permanent deleting of hidden records may have an impact on duplicate check and is not recommended.

    Delete
    HistoryDeleteHelp
    History

    Download this nzb again?

    All downloaded files will be deleted and the nzb-file will be downloaded again from scratch.

    Download Again
    History

    Mark this history record as good?

    Marking has an effect on duplicate handling and RSS.
    For titles marked as good no more duplicates will be downloaded, even with higher duplicate score. Existing dupe-backups will be removed from history.

    Mark Good
    History

    Mark this history record as Bad?

    Marking has an effect on duplicate handling and RSS.
    If dupe-backups exist in the history the best of them will be moved to queue for download. Otherwise the title will be watched and downloaded when it becomes available.

    Mark Bad
    Messages

    Clear Messages?

    All log records will be deleted from screen buffer. The log-file remains on disk unchanged.

    Clear
    Configuration

    Delete ?

    Delete
    Not yet implemented
    Reload (soft-restart)

    Reload NZBGet?

    The configuration will be reloaded and the program will be reinitialized.

    Reload

    Reloading NZBGet

    Stopping all activities and reloading...

    Should this take too long:
    • Try to refresh the page in browser manually.
    • If you changed remote control settings (IP, Port, Password) update the URL in browser accordingly.
    Shutdown

    Shutdown NZBGet?

    The program will be stopped. You will no longer be able to access or start it via web-interface. Make sure you know how to start the program again.

    Shutdown
    Files Submitted
    Scan Completed
    Speed Limit Changed
    Saved
    Pausing
    Paused
    Resumed
    Deleted
    Canceled
    Moved
    Merged
    Splitted
    Could not split. Check messages for errors.
    Cannot split. Some of selected files are already (partially) downloaded.
    Please select records first
    Please select at least two records
    Post-processing-downloads cannot be edited
    Please select records first
    Deleted
    Cleared
    Deleted
    Returned to Queue
    Post-Processing
    Saved
    Please select records first
    Marked
    Fetching new items
    No records selected
    Fetching items
    Nothing to save
    No changes have been made
    Could not save configuration in


    Please check file permissions

    Reload command has been sent

    Please wait few seconds, then refresh the page.
    Please select at least one section
    Restoring settings...
    Could not start update script
    Debug
    nzbget-12.0+dfsg/webui/index.js000066400000000000000000000465661226450633000164460ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2012-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 904 $ * $Date: 2013-11-07 22:01:44 +0100 (Thu, 07 Nov 2013) $ * */ /* * In this module: * 1) Web-interface intialization; * 2) Web-interface settings; * 3) Refresh handling; * 4) Window resize handling including automatic theme switching (desktop/phone); * 5) Confirmation dialog; * 6) Popup notifications. */ /*** WEB-INTERFACE SETTINGS (THIS IS NOT NZBGET CONFIG!) ***********************************/ var UISettings = (new function($) { 'use strict'; /*** Web-interface configuration (edit if neccessary) *************/ // Animation on refresh button. this.refreshAnimation = true; // Animation on play/pause button. this.activityAnimation = true; // Animation of tab changes in tabbed dialogs. this.slideAnimation = true; // Automatically set focus to the first control in dialogs. // Not good on touch devices, because may pop up an on-screen-keyboard. this.setFocus = false; // Show popup notifications. this.notifications = true; // Show badges with duplicate info (downloads and history). this.dupeBadges = false; // Time zone correction in hours. // You shouldn't require this unless you can't set the time zone on your computer/device properly. this.timeZoneCorrection = 0; // Default refresh interval. // The choosen interval is saved in web-browser and then restored. // The default value sets the interval on first use only. this.refreshInterval = 1; // Number of refresh attempts if a communication error occurs. // If all attempts fail, an error is displayed and the automatic refresh stops. this.refreshRetries = 4; // URL for communication with NZBGet via JSON-RPC this.rpcUrl = './jsonrpc'; /*** No user configurable settings below this line (do not edit) *************/ // Current state this.miniTheme = false; this.showEditButtons = true; this.connectionError = false; this.load = function() { this.refreshInterval = parseFloat(this.read('RefreshInterval', this.refreshInterval)); } this.save = function() { this.write('RefreshInterval', this.refreshInterval); } this.read = function(key, def) { var v = localStorage.getItem(key); if (v === null || v === '') { return def; } else { return v; } } this.write = function(key, value) { localStorage.setItem(key, value); } }(jQuery)); /*** START WEB-APPLICATION ***********************************************************/ $(document).ready(function() { Frontend.init(); }); /*** FRONTEND MAIN PAGE ***********************************************************/ var Frontend = (new function($) { 'use strict'; // State var initialized = false; var firstLoad = true; var mobileSafari = false; var scrollbarWidth = 0; var switchingTheme = false; var activeTab = 'Downloads'; var lastTab = ''; this.init = function() { window.onerror = error; if (!checkBrowser()) { return; } $('#FirstUpdateInfo').show(); UISettings.load(); Refresher.init(); initControls(); switchTheme(); windowResized(); Options.init(); Status.init(); Downloads.init({ updateTabInfo: updateTabInfo }); Messages.init({ updateTabInfo: updateTabInfo }); History.init({ updateTabInfo: updateTabInfo }); Upload.init(); Feeds.init(); FeedDialog.init(); FeedFilterDialog.init(); Config.init({ updateTabInfo: updateTabInfo }); ConfigBackupRestore.init(); ConfirmDialog.init(); UpdateDialog.init(); AlertDialog.init(); ScriptListDialog.init(); RestoreSettingsDialog.init(); LimitDialog.init(); DownloadsEditDialog.init(); DownloadsMultiDialog.init(); DownloadsMergeDialog.init(); DownloadsSplitDialog.init(); HistoryEditDialog.init(); $(window).resize(windowResized); initialized = true; Refresher.update(); } function initControls() { mobileSafari = $.browser.safari && navigator.userAgent.toLowerCase().match(/(iphone|ipod|ipad)/) != null; scrollbarWidth = calcScrollbarWidth(); var FadeMainTabs = !$.browser.opera; if (!FadeMainTabs) { $('#DownloadsTab').removeClass('fade').removeClass('in'); } $('#Navbar a[data-toggle="tab"]').on('show', beforeTabShow); $('#Navbar a[data-toggle="tab"]').on('shown', afterTabShow); setupSearch(); $(window).scroll(windowScrolled); } function checkBrowser() { if ($.browser.msie && parseInt($.browser.version, 10) < 9) { $('#FirstUpdateInfo').hide(); $('#UnsupportedBrowserIE8Alert').show(); return false; } return true; } function error(message, source, lineno) { if (source == "") { // ignore false errors without source information (sometimes happen in Safari) return false; } $('#FirstUpdateInfo').hide(); $('#ErrorAlert-title').text('Error in ' + source + ' (line ' + lineno + ')'); $('#ErrorAlert-text').text(message); $('#ErrorAlert').show(); if (Refresher) { Refresher.pause(); } return false; } this.loadCompleted = function() { Downloads.redraw(); Status.redraw(); Messages.redraw(); History.redraw(); if (firstLoad) { Feeds.redraw(); $('#FirstUpdateInfo').hide(); $('#Navbar').show(); $('#MainTabContent').show(); $('#version').text(Options.option('Version')); windowResized(); firstLoad = false; } } function beforeTabShow(e) { var tabname = $(e.target).attr('href'); tabname = tabname.substr(1, tabname.length - 4); if (activeTab === 'Config' && !Config.canLeaveTab(e.target)) { e.preventDefault(); return; } lastTab = activeTab; activeTab = tabname; $('#SearchBlock .search-query, #SearchBlock .search-clear').hide(); $('#' + activeTab + 'Table_filter, #' + activeTab + 'Table_clearfilter').show(); switch (activeTab) { case 'Config': Config.show(); break; case 'Messages': Messages.show(); break; case 'History': History.show(); break; } } function afterTabShow(e) { switch (lastTab) { case 'Config': Config.hide(); break; case 'Messages': Messages.hide(); break; case 'History': History.hide(); break; } switch (activeTab) { case 'Config': Config.shown(); break; } } function setupSearch() { $('.navbar-search .search-query').on('focus', function() { $(this).next().removeClass('icon-remove-white').addClass('icon-remove'); }); $('.navbar-search .search-query').on('blur', function() { $(this).next().removeClass('icon-remove').addClass('icon-remove-white'); }); $('.navbar-search').show(); beforeTabShow({target: $('#DownloadsTabLink')}); } function windowScrolled() { $('body').toggleClass('scrolled', $(window).scrollTop() > 0 && !UISettings.miniTheme); } function calcScrollbarWidth() { var div = $('
    '); // Append our div, do our calculation and then remove it $('body').append(div); var w1 = $('div', div).innerWidth(); div.css('overflow-y', 'scroll'); var w2 = $('div', div).innerWidth(); $(div).remove(); return (w1 - w2); } function windowResized() { var oldMiniTheme = UISettings.miniTheme; UISettings.miniTheme = $(window).width() < 560; if (oldMiniTheme !== UISettings.miniTheme) { switchTheme(); } resizeNavbar(); alignPopupMenu('#PlayMenu', UISettings.miniTheme); alignPopupMenu('#RefreshMenu', UISettings.miniTheme); alignPopupMenu('#RssMenu', UISettings.miniTheme); alignCenterDialogs(); if (initialized) { Downloads.resize(); } } function alignPopupMenu(menu, center) { var $elem = $(menu); if (center) { $elem.removeClass('pull-right'); var top = ($(window).height() - $elem.outerHeight())/2; top = top > 0 ? top : 0; var off = $elem.parent().offset(); top -= off.top; var left = ($(window).width() - $elem.outerWidth())/2; left -= off.left; $elem.css({ left: left, top: top, right: 'inherit' }); } else { $elem.css({ left: '', top: '', right: '' }); var off = $elem.parent().offset(); if (off.left + $elem.outerWidth() > $(window).width()) { var left = $(window).width() - $elem.outerWidth() - off.left; $elem.css({ left: left }); } } } function alignCenterDialogs() { $.each($('.modal-center'), function(index, element) { Util.centerDialog(element, true); }); } function resizeNavbar() { var ScrollDelta = scrollbarWidth; if ($(document).height() > $(window).height()) { // scrollbar is already visible, not need to acount on it ScrollDelta = 0; } if (UISettings.miniTheme) { var w = $('#NavbarContainer').width() - $('#RefreshBlockPhone').outerWidth() - ScrollDelta; var $btns = $('#Navbar ul.nav > li'); var buttonWidth = w / $btns.length; $btns.css('min-width', buttonWidth + 'px'); $('#NavLinks').css('margin-left', 0); $('body').toggleClass('navfixed', false); } else { var InfoBlockMargin = 10; var w = $('#SearchBlock').position().left - $('#InfoBlock').position().left - $('#InfoBlock').width() - InfoBlockMargin * 2 - ScrollDelta; var n = $('#NavLinks').width(); var offset = (w - n) / 2; var fixed = true; if (offset < 0) { w = $('#NavbarContainer').width() - ScrollDelta; offset = (w - n) / 2; fixed = false; } offset = offset > 0 ? offset : 0; $('#NavLinks').css('margin-left', offset); // as of Aug 2012 Mobile Safari does not support "position:fixed" $('body').toggleClass('navfixed', fixed && !mobileSafari); if (switchingTheme) { $('#Navbar ul.nav > li').css('min-width', ''); } } } function updateTabInfo(control, stat) { control.toggleClass('badge-info', stat.available == stat.total).toggleClass('badge-warning', stat.available != stat.total); control.html(stat.available); control.toggleClass('badge2', stat.total > 9); control.toggleClass('badge3', stat.total > 99); if (control.lastOuterWidth !== control.outerWidth()) { resizeNavbar(); control.lastOuterWidth = control.outerWidth(); } } function switchTheme() { switchingTheme = true; $('#DownloadsTable tbody').empty(); $('#HistoryTable tbody').empty(); $('#MessagesTable tbody').empty(); $('body').toggleClass('phone', UISettings.miniTheme); $('.datatable').toggleClass('table-bordered', !UISettings.miniTheme); $('#DownloadsTable').toggleClass('table-check', !UISettings.miniTheme || UISettings.showEditButtons); $('#HistoryTable').toggleClass('table-check', !UISettings.miniTheme || UISettings.showEditButtons); alignPopupMenu('#PlayMenu', UISettings.miniTheme); alignPopupMenu('#RefreshMenu', UISettings.miniTheme); alignPopupMenu('#RssMenu', UISettings.miniTheme); if (UISettings.miniTheme) { $('#RefreshBlock').appendTo($('#RefreshBlockPhone')); $('#DownloadsRecordsPerPageBlock').appendTo($('#DownloadsRecordsPerPageBlockPhone')); $('#HistoryRecordsPerPageBlock').appendTo($('#HistoryRecordsPerPageBlockPhone')); $('#MessagesRecordsPerPageBlock').appendTo($('#MessagesRecordsPerPageBlockPhone')); } else { $('#RefreshBlock').appendTo($('#RefreshBlockDesktop')); $('#DownloadsRecordsPerPageBlock').appendTo($('#DownloadsTableTopBlock')); $('#HistoryRecordsPerPageBlock').appendTo($('#HistoryTableTopBlock')); $('#MessagesRecordsPerPageBlock').appendTo($('#MessagesTableTopBlock')); } if (initialized) { Downloads.redraw(); History.redraw(); Messages.redraw(); Downloads.applyTheme(); History.applyTheme(); Messages.applyTheme(); windowResized(); } switchingTheme = false; } }(jQuery)); /*** REFRESH CONTROL *********************************************************/ var Refresher = (new function($) { 'use strict'; // State var loadQueue; var firstLoad = true; var secondsToUpdate = -1; var refreshTimer = 0; var indicatorTimer = 0; var indicatorFrame=0; var refreshPaused = 0; var refreshing = false; var refreshNeeded = false; var refreshErrors = 0; this.init = function() { RPC.rpcUrl = UISettings.rpcUrl; RPC.connectErrorMessage = 'Cannot establish connection to NZBGet.' RPC.defaultFailureCallback = rpcFailure; RPC.next = loadNext; $('#RefreshMenu li a').click(refreshIntervalClick); $('#RefreshButton').click(refreshClick); updateRefreshMenu(); } function refresh() { UISettings.connectionError = false; $('#ErrorAlert').hide(); refreshStarted(); loadQueue = new Array( function() { Options.update(); }, function() { Status.update(); }, function() { Downloads.update(); }, function() { Messages.update(); }, function() { History.update(); }); if (!firstLoad) { // query NZBGet configuration only on first refresh loadQueue.shift(); } loadNext(); } function loadNext() { if (loadQueue.length > 0) { var nextStep = loadQueue[0]; loadQueue.shift(); nextStep(); } else { firstLoad = false; Frontend.loadCompleted(); refreshCompleted(); } } function rpcFailure(res, result) { // If a communication error occurs during status refresh we retry: // first attempt is made immediately, other attempts are made after defined refresh interval if (refreshing && !(result && result.error)) { refreshErrors = refreshErrors + 1; if (refreshErrors === 1 && refreshErrors <= UISettings.refreshRetries) { refresh(); return; } else if (refreshErrors <= UISettings.refreshRetries) { $('#RefreshError').show(); scheduleNextRefresh(); return; } } Refresher.pause(); UISettings.connectionError = true; $('#FirstUpdateInfo').hide(); $('#ErrorAlert-text').html(res); $('#ErrorAlert').show(); $('#RefreshError').hide(); if (Status.status) { // stop animations Status.redraw(); } }; function refreshStarted() { clearTimeout(refreshTimer); refreshPaused = 0; refreshing = true; refreshNeeded = false; refreshAnimationShow(); } function refreshCompleted() { refreshing = false; refreshErrors = 0; $('#RefreshError').hide(); scheduleNextRefresh(); } this.pause = function() { clearTimeout(refreshTimer); refreshPaused++; } this.resume = function() { refreshPaused--; if (refreshPaused === 0 && UISettings.refreshInterval > 0) { countSeconds(); } } this.update = function() { refreshNeeded = true; refreshPaused = 0; if (!refreshing) { scheduleNextRefresh(); } } function refreshClick() { if (indicatorFrame > 10) { // force animation restart indicatorFrame = 0; } refreshErrors = 0; refresh(); } function scheduleNextRefresh() { clearTimeout(refreshTimer); secondsToUpdate = refreshNeeded ? 0 : UISettings.refreshInterval; if (secondsToUpdate > 0 || refreshNeeded) { secondsToUpdate += 0.1; countSeconds(); } } function countSeconds() { if (refreshPaused > 0) { return; } secondsToUpdate -= 0.1; if (secondsToUpdate <= 0) { refresh(); } else { refreshTimer = setTimeout(countSeconds, 100); } } function refreshAnimationShow() { if (UISettings.refreshAnimation && indicatorTimer === 0) { refreshAnimationFrame(); } } function refreshAnimationFrame() { // animate next frame indicatorFrame++; if (indicatorFrame === 20) { indicatorFrame = 0; } var f = indicatorFrame <= 10 ? indicatorFrame : 0; var degree = 360 * f / 10; $('#RefreshAnimation').css({ '-webkit-transform': 'rotate(' + degree + 'deg)', '-moz-transform': 'rotate(' + degree + 'deg)', '-ms-transform': 'rotate(' + degree + 'deg)', '-o-transform': 'rotate(' + degree + 'deg)', 'transform': 'rotate(' + degree + 'deg)' }); if ((!refreshing && indicatorFrame === 0 && (UISettings.refreshInterval === 0 || UISettings.refreshInterval > 1 || !UISettings.refreshAnimation)) || UISettings.connectionError) { indicatorTimer = 0; } else { // schedule next frame update indicatorTimer = setTimeout(refreshAnimationFrame, 100); } } function refreshIntervalClick() { var data = $(this).parent().attr('data'); UISettings.refreshInterval = parseFloat(data); scheduleNextRefresh(); updateRefreshMenu(); UISettings.save(); if (UISettings.refreshInterval === 0) { // stop animation Status.redraw(); } } function updateRefreshMenu() { Util.setMenuMark($('#RefreshMenu'), UISettings.refreshInterval); } }(jQuery)); function TODO() { Notification.show('#Notif_NotImplemented'); } /*** CONFIRMATION DIALOG *****************************************************/ var ConfirmDialog = (new function($) { 'use strict'; // Controls var $ConfirmDialog; // State var actionCallback; this.init = function() { $ConfirmDialog = $('#ConfirmDialog'); $ConfirmDialog.on('hidden', hidden); $('#ConfirmDialog_OK').click(click); } this.showModal = function(id, _actionCallback, initCallback) { $('#ConfirmDialog_Title').html($('#' + id + '_Title').html()); $('#ConfirmDialog_Text').html($('#' + id + '_Text').html()); $('#ConfirmDialog_OK').html($('#' + id + '_OK').html()); var helpId = $('#' + id + '_Help').html(); $('#ConfirmDialog_Help').attr('href', '#' + helpId); Util.show('#ConfirmDialog_Help', helpId !== null); actionCallback = _actionCallback; if (initCallback) { initCallback($ConfirmDialog); } Util.centerDialog($ConfirmDialog, true); $ConfirmDialog.modal({backdrop: 'static'}); // avoid showing multiple backdrops when the modal is shown from other modal var backdrops = $('.modal-backdrop'); if (backdrops.length > 1) { backdrops.last().remove(); } } function hidden() { // confirm dialog copies data from other nodes // the copied DOM nodes must be destroyed $('#ConfirmDialog_Title').empty(); $('#ConfirmDialog_Text').empty(); $('#ConfirmDialog_OK').empty(); } function click(event) { event.preventDefault(); // avoid scrolling actionCallback($ConfirmDialog); $ConfirmDialog.modal('hide'); } }(jQuery)); /*** ALERT DIALOG *****************************************************/ var AlertDialog = (new function($) { 'use strict'; // Controls var $AlertDialog; this.init = function() { $AlertDialog = $('#AlertDialog'); } this.showModal = function(title, text) { $('#AlertDialog_Title').html(title); $('#AlertDialog_Text').html(text); Util.centerDialog($AlertDialog, true); $AlertDialog.modal(); } }(jQuery)); /*** NOTIFICATIONS *********************************************************/ var Notification = (new function($) { 'use strict'; this.show = function(alert, completeFunc) { if (UISettings.notifications || $(alert).hasClass('alert-error')) { $(alert).animate({'opacity':'toggle'}); var duration = $(alert).attr('data-duration'); if (duration == null) { duration = 1000; } window.setTimeout(function() { $(alert).animate({'opacity':'toggle'}, completeFunc); }, duration); } else if (completeFunc) { completeFunc(); } } }(jQuery)); nzbget-12.0+dfsg/webui/messages.js000066400000000000000000000177431226450633000171410ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2012-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 778 $ * $Date: 2013-08-07 22:09:43 +0200 (Wed, 07 Aug 2013) $ * */ /* * In this module: * 1) Messages tab. */ /*** MESSAGES TAB *********************************************************************/ var Messages = (new function($) { 'use strict'; // Controls var $MessagesTable; var $MessagesTabBadge; var $MessagesTabBadgeEmpty; var $MessagesRecordsPerPage; // State var messages; var maxMessages = null; var lastID = 0; var updateTabInfo; var notification = null; var curFilter = 'ALL'; var activeTab = false; this.init = function(options) { updateTabInfo = options.updateTabInfo; $MessagesTable = $('#MessagesTable'); $MessagesTabBadge = $('#MessagesTabBadge'); $MessagesTabBadgeEmpty = $('#MessagesTabBadgeEmpty'); $MessagesRecordsPerPage = $('#MessagesRecordsPerPage'); var recordsPerPage = UISettings.read('MessagesRecordsPerPage', 10); $MessagesRecordsPerPage.val(recordsPerPage); $MessagesTable.fasttable( { filterInput: '#MessagesTable_filter', filterClearButton: '#MessagesTable_clearfilter', pagerContainer: '#MessagesTable_pager', infoContainer: '#MessagesTable_info', filterCaseSensitive: false, pageSize: recordsPerPage, maxPages: UISettings.miniTheme ? 1 : 5, pageDots: !UISettings.miniTheme, fillFieldsCallback: fillFieldsCallback, fillSearchCallback: fillSearchCallback, filterCallback: filterCallback, renderCellCallback: renderCellCallback, updateInfoCallback: updateInfo }); } this.applyTheme = function() { $MessagesTable.fasttable('setPageSize', UISettings.read('MessagesRecordsPerPage', 10), UISettings.miniTheme ? 1 : 5, !UISettings.miniTheme); } this.show = function() { activeTab = true; this.redraw(); } this.hide = function() { activeTab = false; } this.update = function() { if (maxMessages === null) { maxMessages = parseInt(Options.option('LogBufferSize')); initFilterButtons(); } if (lastID === 0) { RPC.call('log', [0, maxMessages], loaded); } else { RPC.call('log', [lastID+1, 0], loaded); } } function loaded(newMessages) { merge(newMessages); RPC.next(); } function merge(newMessages) { if (lastID === 0) { messages = newMessages; } else { messages = messages.concat(newMessages); messages.splice(0, messages.length-maxMessages); } if (messages.length > 0) { lastID = messages[messages.length-1].ID; } } this.redraw = function() { var data = []; for (var i=0; i < messages.length; i++) { var message = messages[i]; var item = { id: message.ID, message: message }; data.unshift(item); } $MessagesTable.fasttable('update', data); Util.show($MessagesTabBadge, messages.length > 0); Util.show($MessagesTabBadgeEmpty, messages.length === 0 && UISettings.miniTheme); } function fillFieldsCallback(item) { var message = item.message; var kind; switch (message.Kind) { case 'INFO': kind = 'info'; break; case 'DETAIL': kind = 'detail'; break; case 'WARNING': kind = 'warning'; break; case 'ERROR': kind = 'error'; break; case 'DEBUG': kind = 'debug'; break; } var text = Util.textToHtml(message.Text); if (!item.time) { item.time = Util.formatDateTime(message.Time + UISettings.timeZoneCorrection*60*60); } if (!UISettings.miniTheme) { item.fields = [kind, item.time, text]; } else { var info = kind + ' ' + item.time + ' ' + text; item.fields = [info]; } } function fillSearchCallback(item) { if (!item.time) { item.time = Util.formatDateTime(item.message.Time + UISettings.timeZoneCorrection*60*60); } item.search = item.message.Kind + ' ' + item.time + ' ' + item.message.Text; } function renderCellCallback(cell, index, item) { if (index === 1) { cell.className = 'text-center'; } } function updateInfo(stat) { updateTabInfo($MessagesTabBadge, stat); if (activeTab) { updateFilterButtons(); } } this.recordsPerPageChange = function() { var val = $MessagesRecordsPerPage.val(); UISettings.write('MessagesRecordsPerPage', val); $MessagesTable.fasttable('setPageSize', val); } function filterCallback(item) { return !activeTab || curFilter === 'ALL' || item.message.Kind === curFilter; } function initFilterButtons() { var detail = ['both', 'screen'].indexOf(Options.option('DetailTarget')) > -1 var info = ['both', 'screen'].indexOf(Options.option('InfoTarget')) > -1; var warning = ['both', 'screen'].indexOf(Options.option('WarningTarget')) > -1; var error = ['both', 'screen'].indexOf(Options.option('ErrorTarget')) > -1; Util.show($('#Messages_Badge_DETAIL, #Messages_Badge_DETAIL2').closest('.btn'), detail); Util.show($('#Messages_Badge_INFO, #Messages_Badge_INFO2 ').closest('.btn'), info); Util.show($('#Messages_Badge_WARNING, #Messages_Badge_WARNING2').closest('.btn'), warning); Util.show($('#Messages_Badge_ERROR, #Messages_Badge_ERROR2').closest('.btn'), error); Util.show($('#Messages_Badge_ALL, #Messages_Badge_ALL2').closest('.btn'), detail || info || warning || error); } function updateFilterButtons() { var countDebug = 0; var countDetail = 0; var countInfo = 0; var countWarning = 0; var countError = 0; var data = $MessagesTable.fasttable('availableContent'); for (var i=0; i < data.length; i++) { var message = data[i].message; switch (message.Kind) { case 'INFO': countInfo++; break; case 'DETAIL': countDetail++; break; case 'WARNING': countWarning++; break; case 'ERROR': countError++; break; case 'DEBUG': countDebug++; break; } } $('#Messages_Badge_ALL,#Messages_Badge_ALL2').text(countDebug + countDetail + countInfo + countWarning + countError); $('#Messages_Badge_DETAIL,#Messages_Badge_DETAIL2').text(countDetail); $('#Messages_Badge_INFO,#Messages_Badge_INFO2').text(countInfo); $('#Messages_Badge_WARNING,#Messages_Badge_WARNING2').text(countWarning); $('#Messages_Badge_ERROR,#Messages_Badge_ERROR2').text(countError); $('#MessagesTab_Toolbar .btn').removeClass('btn-inverse'); $('#Messages_Badge_' + curFilter + ',#Messages_Badge_' + curFilter + '2').closest('.btn').addClass('btn-inverse'); $('#MessagesTab_Toolbar .badge').removeClass('badge-active'); $('#Messages_Badge_' + curFilter + ',#Messages_Badge_' + curFilter + '2').addClass('badge-active'); } this.filter = function(type) { curFilter = type; Messages.redraw(); } this.clearClick = function() { ConfirmDialog.showModal('MessagesClearConfirmDialog', messagesClear); } function messagesClear() { Refresher.pause(); RPC.call('clearlog', [], function() { RPC.call('writelog', ['INFO', 'Messages have been deleted'], function() { notification = '#Notif_Messages_Cleared'; lastID = 0; editCompleted(); }); }); } function editCompleted() { Refresher.update(); if (notification) { Notification.show(notification); notification = null; } } }(jQuery)); nzbget-12.0+dfsg/webui/status.js000066400000000000000000000351171226450633000166500ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2012-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 865 $ * $Date: 2013-10-07 18:15:38 +0200 (Mon, 07 Oct 2013) $ * */ /* * In this module: * 1) Status Infos on main page (speed, time, paused state etc.); * 2) Statistics and Status dialog; * 3) Limit dialog (speed and active news servers). */ /*** STATUS INFOS ON MAIN PAGE AND STATISTICS DIALOG ****************************************/ var Status = (new function($) { 'use strict'; // Properties (public) this.status; // Controls var $CHPauseDownload; var $CHPausePostProcess; var $CHPauseScan; var $CHSoftPauseDownload; var $StatusPausing; var $StatusPaused; var $StatusSoftPaused; var $StatusLeft; var $StatusSpeed; var $StatusSpeedIcon; var $StatusTimeIcon; var $StatusTime; var $StatusURLs; var $PlayBlock; var $PlayButton; var $PauseButton; var $PlayAnimation; var $StatDialog; var $ScheduledPauseDialog; var $PauseForInput; // State var status; var lastPlayState = 0; var lastAnimState = 0; var playInitialized = false; var lastSoftPauseState = 0; var modalShown = false; this.init = function() { $CHPauseDownload = $('#CHPauseDownload'); $CHPausePostProcess = $('#CHPausePostProcess'); $CHPauseScan = $('#CHPauseScan'); $CHSoftPauseDownload = $('#CHSoftPauseDownload'); $PlayBlock = $('#PlayBlock'); $PlayButton = $('#PlayButton'); $PauseButton = $('#PauseButton'); $PlayAnimation = $('#PlayAnimation'); $StatusPausing = $('#StatusPausing'); $StatusPaused = $('#StatusPaused'); $StatusSoftPaused = $('#StatusSoftPaused'); $StatusLeft = $('#StatusLeft'); $StatusSpeed = $('#StatusSpeed'); $StatusSpeedIcon = $('#StatusSpeedIcon'); $StatusTimeIcon = $('#StatusTimeIcon'); $StatusTime = $('#StatusTime'); $StatusURLs = $('#StatusURLs'); $StatDialog = $('#StatDialog'); $ScheduledPauseDialog = $('#ScheduledPauseDialog') $PauseForInput = $('#PauseForInput'); if (UISettings.setFocus) { $LimitDialog.on('shown', function() { $('#SpeedLimitInput').focus(); }); $ScheduledPauseDialog.on('shown', function() { $('#PauseForInput').focus(); }); } $PlayAnimation.hover(function() { $PlayBlock.addClass('hover'); }, function() { $PlayBlock.removeClass('hover'); }); // temporary pause the play animation if any modal is shown (to avoid artifacts in safari) $('body >.modal').on('show', modalShow); $('body > .modal').on('hide', modalHide); } this.update = function() { var _this = this; RPC.call('status', [], function(curStatus) { status = curStatus; _this.status = status; RPC.next(); }); } this.redraw = function() { redrawStatistics(); redrawInfo() } function redrawStatistics() { var content = ''; content += 'NZBGet version' + Options.option('Version') + ''; content += 'Uptime' + Util.formatTimeHMS(status.UpTimeSec) + ''; content += 'Download time' + Util.formatTimeHMS(status.DownloadTimeSec) + ''; content += 'Total downloaded' + Util.formatSizeMB(status.DownloadedSizeMB) + ''; content += 'Remaining' + Util.formatSizeMB(status.RemainingSizeMB) + ''; content += 'Free disk space' + Util.formatSizeMB(status.FreeDiskSpaceMB) + ''; content += 'Average download speed' + Util.round0(status.AverageDownloadRate / 1024) + ' KB/s'; content += 'Current download speed' + Util.round0(status.DownloadRate / 1024) + ' KB/s'; content += 'Current speed limit' + Util.round0(status.DownloadLimit / 1024) + ' KB/s'; $('#StatisticsTable tbody').html(content); content = ''; content += 'Download'; if (status.DownloadPaused || status.Download2Paused) { content += status.Download2Paused ? 'paused' : ''; content += status.Download2Paused && status.DownloadPaused ? ' + ' : ''; content += status.DownloadPaused ? 'soft-paused' : ''; } else { content += 'active'; } content += ''; content += 'Post-processing' + (Options.option('PostProcess') === '' ? 'disabled' : (status.PostPaused ? 'paused' : 'active')) + ''; content += 'NZB-Directory scan' + (Options.option('NzbDirInterval') === '0' ? 'disabled' : (status.ScanPaused ? 'paused' : 'active')) + ''; if (status.ResumeTime > 0) { content += 'Autoresume' + Util.formatTimeHMS(status.ResumeTime - status.ServerTime) + ''; } content += ''; content += ''; $('#StatusTable tbody').html(content); } function redrawInfo() { Util.show($CHPauseDownload, status.Download2Paused); Util.show($CHPausePostProcess, status.PostPaused); Util.show($CHPauseScan, status.ScanPaused); Util.show($CHSoftPauseDownload, status.DownloadPaused); updatePlayAnim(); updatePlayButton(); if (status.ServerStandBy) { $StatusSpeed.html('--- KB/s'); if (status.ResumeTime > 0) { $StatusTime.html(Util.formatTimeLeft(status.ResumeTime - status.ServerTime)); } else if (status.RemainingSizeMB > 0 || status.RemainingSizeLo > 0) { if (status.AverageDownloadRate > 0) { $StatusTime.html(Util.formatTimeLeft(status.RemainingSizeMB*1024/(status.AverageDownloadRate/1024))); } else { $StatusTime.html('--h --m'); } } else { $StatusTime.html('0h 0m'); } } else { $StatusSpeed.html(Util.round0(status.DownloadRate / 1024) + ' KB/s'); if (status.DownloadRate > 0) { $StatusTime.html(Util.formatTimeLeft(status.RemainingSizeMB*1024/(status.DownloadRate/1024))); } else { $StatusTime.html('--h --m'); } } var limit = status.DownloadLimit > 0; if (!limit) { for (var i=0; i < Status.status.NewsServers.length; i++) { limit = !Status.status.NewsServers[i].Active; if (limit) { break; } } } $StatusSpeedIcon.toggleClass('icon-plane', !limit); $StatusSpeedIcon.toggleClass('icon-truck', limit); $StatusTime.toggleClass('scheduled-resume', status.ServerStandBy && status.ResumeTime > 0); $StatusTimeIcon.toggleClass('icon-time', !(status.ServerStandBy && status.ResumeTime > 0)); $StatusTimeIcon.toggleClass('icon-time-orange', status.ServerStandBy && status.ResumeTime > 0); } function updatePlayButton() { var SoftPause = status.DownloadPaused && (!lastAnimState || !UISettings.activityAnimation); if (SoftPause !== lastSoftPauseState) { lastSoftPauseState = SoftPause; $PauseButton.removeClass('img-download-green').removeClass('img-download-green-orange'). addClass(SoftPause ? 'img-download-green-orange' : 'img-download-green'); $PlayButton.removeClass('img-download-orange').removeClass('img-download-orange-orange'). addClass(SoftPause ? 'img-download-orange-orange' : 'img-download-orange'); } var Play = !status.Download2Paused; if (Play === lastPlayState) { return; } lastPlayState = Play; var hideBtn = Play ? $PlayButton : $PauseButton; var showBtn = !Play ? $PlayButton : $PauseButton; if (playInitialized) { hideBtn.fadeOut(500); showBtn.fadeIn(500); if (!Play && !status.ServerStandBy) { Notification.show('#Notif_Downloads_Pausing'); } } else { hideBtn.hide(); showBtn.show(); } if (Play) { $PlayAnimation.removeClass('pause').addClass('play'); } else { $PlayAnimation.removeClass('play').addClass('pause'); } playInitialized = true; } function updatePlayAnim() { // Animate if either any downloads or post-processing is in progress var Anim = (!status.ServerStandBy || status.FeedActive || (status.PostJobCount > 0 && !status.PostPaused) || (status.UrlCount > 0 && ((!status.DownloadPaused && !status.Download2Paused) || Options.option('UrlForce') === 'yes'))) && (UISettings.refreshInterval !== 0) && !UISettings.connectionError; if (Anim === lastAnimState) { return; } lastAnimState = Anim; if (UISettings.activityAnimation && !modalShown) { if (Anim) { $PlayAnimation.fadeIn(1000); } else { $PlayAnimation.fadeOut(1000); } } } this.playClick = function() { //Notification.show('#Notif_Play'); if (lastPlayState) { // pause all activities RPC.call('pausedownload2', [], function(){RPC.call('pausepost', [], function(){RPC.call('pausescan', [], Refresher.update)})}); } else { // resume all activities RPC.call('resumedownload2', [], function(){RPC.call('resumepost', [], function(){RPC.call('resumescan', [], Refresher.update)})}); } } this.pauseClick = function(data) { switch (data) { case 'download2': var method = status.Download2Paused ? 'resumedownload2' : 'pausedownload2'; break; case 'post': var method = status.PostPaused ? 'resumepost' : 'pausepost'; break; case 'scan': var method = status.ScanPaused ? 'resumescan' : 'pausescan'; break; case 'download': var method = status.DownloadPaused ? 'resumedownload' : 'pausedownload'; break; } RPC.call(method, [], Refresher.update); } this.statDialogClick = function() { $StatDialog.modal(); } this.scheduledPauseClick = function(seconds) { RPC.call('pausedownload2', [], function(){RPC.call('pausepost', [], function(){RPC.call('pausescan', [], function(){RPC.call('scheduleresume', [seconds], Refresher.update)})})}); } this.scheduledPauseDialogClick = function() { $PauseForInput.val(''); $ScheduledPauseDialog.modal(); } this.pauseForClick = function() { var val = $PauseForInput.val(); var minutes = parseInt(val); if (isNaN(minutes) || minutes <= 0) { return; } $ScheduledPauseDialog.modal('hide'); this.scheduledPauseClick(minutes * 60); } function modalShow() { modalShown = true; if (lastAnimState) { $PlayAnimation.hide(); } } function modalHide() { if (lastAnimState) { $PlayAnimation.show(); } modalShown = false; } }(jQuery)); /*** LIMIT DIALOG *******************************************************/ var LimitDialog = (new function($) { 'use strict' // Controls var $LimitDialog; var $ServerTable; var $LimitDialog_SpeedInput; // State var changed; this.init = function() { $LimitDialog = $('#LimitDialog'); $LimitDialog_SpeedInput = $('#LimitDialog_SpeedInput'); $('#LimitDialog_Save').click(save); $ServerTable = $('#LimitDialog_ServerTable'); $ServerTable.fasttable( { pagerContainer: $('#LimitDialog_ServerTable_pager'), headerCheck: $('#LimitDialog_ServerTable > thead > tr:first-child'), hasHeader: false, pageSize: 100 }); $ServerTable.on('click', 'tbody div.check', function(event) { $ServerTable.fasttable('itemCheckClick', this.parentNode.parentNode, event); }); $ServerTable.on('click', 'thead div.check', function() { $ServerTable.fasttable('titleCheckClick') }); $ServerTable.on('mousedown', Util.disableShiftMouseDown); $LimitDialog.on('hidden', function() { // cleanup $ServerTable.fasttable('update', []); }); } this.showModal = function() { changed = false; var rate = Util.round0(Status.status.DownloadLimit / 1024); $LimitDialog_SpeedInput.val(rate > 0 ? rate : ''); updateTable(); $LimitDialog.modal({backdrop: 'static'}); } function updateTable() { var data = []; for (var i=0; i < Status.status.NewsServers.length; i++) { var server = Status.status.NewsServers[i]; var name = Options.option('Server' + server.ID + '.Name'); if (name === null || name === '') { var host = Options.option('Server' + server.ID + '.Host'); var port = Options.option('Server' + server.ID + '.Port'); name = (host === null ? '' : host) + ':' + (port === null ? '119' : port); } var fields = ['
    ', server.ID + '. ' + name]; var item = { id: server.ID, fields: fields, search: '' }; data.push(item); $ServerTable.fasttable('checkRow', server.ID, server.Active); } $ServerTable.fasttable('update', data); Util.show('#LimitDialog_ServerBlock', data.length > 0); } function save(e) { var val = $LimitDialog_SpeedInput.val(); var rate = 0; if (val == '') { rate = 0; } else { rate = parseInt(val); if (isNaN(rate)) { return; } } var oldRate = Util.round0(Status.status.DownloadLimit / 1024); if (rate != oldRate) { changed = true; RPC.call('rate', [rate], function() { saveServers(); }); } else { saveServers(); } } function saveServers() { var checkedRows = $ServerTable.fasttable('checkedRows'); var command = []; for (var i=0; i < Status.status.NewsServers.length; i++) { var server = Status.status.NewsServers[i]; var selected = checkedRows.indexOf(server.ID) > -1; if (server.Active != selected) { command.push([server.ID, selected]); changed = true; } } if (command.length > 0) { RPC.call('editserver', command, function() { completed(); }); } else { completed(); } } function completed() { $LimitDialog.modal('hide'); if (changed) { Notification.show('#Notif_SetSpeedLimit'); } Refresher.update(); } }(jQuery)); nzbget-12.0+dfsg/webui/style.css000066400000000000000000001162431226450633000166410ustar00rootroot00000000000000/*! * This file is part of nzbget * * Copyright (C) 2012-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 887 $ * $Date: 2013-10-22 23:04:22 +0200 (Tue, 22 Oct 2013) $ * */ body { padding-left: 0; padding-right: 0; } /* NAVBAR */ .navbar-fixed-top { margin-bottom: 18px; margin-left: 0px; margin-right: 0px; position: static; } .navbar-fixed-top .navbar-inner { padding: 0; min-height: 0; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; color: #bbb; } /* fixed navbar (applied dynamically if screen is wide enough) */ body.navfixed { margin-top: 57px; } body.navfixed .navbar-fixed-top { position: fixed; } body.navfixed.scrolled .navbar-fixed-top .navbar-inner { -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1); -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1); } /* end of fixed navbar */ #Logo { float: left; margin-top: 1px; margin-right: 5px; width: 88px; height: 36px; cursor: pointer; opacity: 1; } #Logo i, #Logo i:hover { opacity: 1; filter: alpha(opacity=100); } .img-logo { background-position: -8px -139px; width: 88px; height: 38px; display: inline-block; } #PlayBlock { width: 70px; height: 38px; display: block; float: left; position: relative; z-index: 2; } #PlayButton, #PauseButton, #PlayPauseBg { width: 50px; height: 50px; position: absolute; padding: 0; z-index: 3; } .img-download-orange { background-position: -176px -80px; } .img-download-orange-orange { background-position: -176px -144px; } .img-download-green { background-position: -112px -80px; } .img-download-green-orange { background-position: -112px -144px; } .img-download-bg { background-position: -240px -80px; width: 50px; height: 50px; } .PlayBlockInner { width: 38px; height: 38px; margin-left: 0px; margin-top: 0px; cursor: pointer; top: 6px; left: 6px; position: absolute; -webkit-border-radius: 18px; -moz-border-radius: 18px; border-radius: 18px; } .img-download-btn { background-position: -16px -80px; width: 21px; height: 21px; margin-left: 9px; margin-top: 8px; } .PlayBlockInner:hover .img-download-btn, .PlayBlockInner:hover .img-download-btn { /* white */ background-position: -16px -112px; opacity: 0.9; } #PlayCaretBlock { left: 37px; top: 1px; position: absolute; } #PlayCaretButton { border: 0; background: none; width: 24px; height: 22px; padding: 3px; line-height: 10px; vertical-align: top; } #PlayCaret { margin-top: 3px; margin-left: 9px; border-top-color: #ffffff; border-bottom-color: #ffffff; opacity: 0.75; filter: alpha(opacity=75); } #PlayCaretButton:hover #PlayCaret { opacity: 1; filter: alpha(opacity=100); } @-webkit-keyframes play-rotate { 0% { -webkit-transform: rotate(0); } 100% { -webkit-transform: rotate(360deg); } } @-moz-keyframes play-rotate { 0% { -moz-transform: rotate(0); } 100% { -moz-transform: rotate(360deg); } } @-ms-keyframes play-rotate { 0% { -ms-transform: rotate(0); } 100% { -ms-transform: rotate(360deg); } } #PlayAnimation { position: absolute; z-index: 4; left: -5px; top: -5px; -webkit-background-size: 70px 70px; -moz-background-size: 70px 70px; background-size: 70px 70px; background-position: center; width: 60px; height: 60px; cursor: pointer; pointer-events: none; -webkit-animation: play-rotate 1s linear infinite; -moz-animation: play-rotate 1s linear infinite; -ms-animation: play-rotate 1s linear infinite; } #PlayAnimation.play { background-image: url("./img/download-anim-green-2x.png"); } #PlayAnimation.pause { background-image: url("./img/download-anim-orange-2x.png"); } #InfoBlock { float: left; margin-right: 10px; padding-top: 2px; cursor: pointer; width: 86px; } #InfoBlock div { margin-top: 1px; font-size: 12px; font-weight: bold; margin: 0; padding: 0; } #InfoBlock div:hover { color: #fff; } #InfoBlock i { margin-right: 1px; } .navbar-inner i { opacity: 0.8; filter: alpha(opacity=80); } #InfoBlock div:hover i { opacity: 1; filter: alpha(opacity=100); } #StatusTime.scheduled-resume { color: #F08929; } #StatusTime.scheduled-resume:hover { color: #FFA15A; } .navbar-inner .btn:hover i { opacity: 1; filter: alpha(opacity=100); } #NavLinks { margin-right: 0; } .navbar-container { padding-left: 10px; padding-right: 10px; } .navbar .btn-group { padding: 0; } /* needed for Safari 4 */ .btn-toolbar .btn { height: 28px; } .navbar .nav { margin-right: 0; } .navbar .nav > li > a { color: inherit; } .navbar .nav .active > a, .navbar .nav .active > a:hover { outline: 0; color: #000; text-shadow: none; background: rgb(255,255,255); /* Old browsers */ background: -moz-linear-gradient(top, rgb(255,255,255) 0%, rgb(238,238,238) 45%, rgb(231,231,231) 55%, rgb(255,255,255) 100%); /* FF3.6+ */ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgb(255,255,255)), color-stop(45%,rgb(238,238,238)), color-stop(55%,rgb(231,231,231)), color-stop(100%,rgb(255,255,255))); /* Chrome,Safari4+ */ background: -webkit-linear-gradient(top, rgb(255,255,255) 0%,rgb(238,238,238) 45%,rgb(231,231,231) 55%,rgb(255,255,255) 100%); /* Chrome10+,Safari5.1+ */ background: -o-linear-gradient(top, rgb(255,255,255) 0%,rgb(238,238,238) 45%,rgb(231,231,231) 55%,rgb(255,255,255) 100%); /* Opera 11.10+ */ background: -ms-linear-gradient(top, rgb(255,255,255) 0%,rgb(238,238,238) 45%,rgb(231,231,231) 55%,rgb(255,255,255) 100%); /* IE10+ */ background: linear-gradient(to bottom, rgb(255,255,255) 0%,rgb(238,238,238) 45%,rgb(231,231,231) 55%,rgb(255,255,255) 100%); /* W3C */ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#ffffff',GradientType=0 ); /* IE6-9 */ } #NavLinks .badge { min-width: 14px; display: inline-block; text-align: center; padding: 1px 4px; } #NavLinks .badge.badge2 { min-width: 18px; } #NavLinks .badge.badge3 { min-width: 28px; } #NavLinks .badge-empty { background: none; } /* headers in navbar menu */ .menu-header { display: block; padding: 3px 15px; font-size: 11px; font-weight: bold; line-height: 18px; color: #999999; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); text-transform: uppercase; } /* for checkboxes in dropdown menu */ .menu-refresh td:first-child , .menu-check td:first-child { width: 18px; } /* for checkboxes in dropdown menu "Refresh" */ .menu-refresh td:nth-child(2) { width: 20px; text-align: right; padding-right: 5px; } /* Search box */ .navbar-search { margin-top: 0px; margin-right: 10px; } .navbar-search .search-query, .navbar-search .search-query:focus, .navbar-search .search-query.focused { width: 150px; padding: 3px 9px; -webkit-border-radius: 12px; -moz-border-radius: 12px; border-radius: 12px; } .navbar-search .search-query:focus, .navbar-search .search-query.focused { margin-top: 1px; } .search-clear { position: absolute; top: 6px; right: 9px; cursor: pointer; opacity: 0.65; filter: alpha(opacity=65); } /* MENUS */ #RefreshMenu { min-width: 160px; } #SettingsMenu { min-width: 190px; } #PlayMenu { min-width: 190px; } #ToolbarOptMenu { min-width: 215px; } #RssMenu { max-width: 300px; overflow: hidden; } #DownloadsEdit_ActionsMenu, #HistoryEdit_ActionsMenu { min-width: 120px; } ul.dropdown-menu > li > a > i { margin-right: 5px; } /* BEGIN: Icons */ [class^="icon-"], [class*=" icon-"], [class^="img-"], [class*=" img-"] { background-image: url("./img/icons.png"); } /* HiDPI screens */ @media only screen and (-webkit-min-device-pixel-ratio: 2) { [class^="icon-"], [class*=" icon-"], [class^="img-"], [class*=" img-"] { background-image: url("./img/icons-2x.png"); -webkit-background-size: 700px 300px; -moz-background-size: 700px 300px; background-size: 700px 300px; } } [class^="icon-"], [class*=" icon-"] { display: inline-block; vertical-align: text-top; width: 16px; height: 16px; line-height: 16px; } .icon-empty { background-position: -1000px -1000px; } .icon-plus { background-position: -16px -16px; } .icon-minus { background-position: -368px -112px; } .icon-remove-white { background-position: -48px -16px; } .icon-ok { background-position: -80px -16px; } .icon-time { background-position: -112px -16px; } .icon-file { background-position: -144px -16px; } .icon-messages { background-position: -176px -16px; } .icon-play { background-position: -208px -16px; } .icon-pause { background-position: -240px -16px; } .icon-down { background-position: -272px -16px; } .icon-up { background-position: -304px -16px; } .icon-bottom { background-position: -336px -16px; } .icon-top { background-position: -368px -16px; } .icon-back { background-position: -304px -80px; } .icon-forward { background-position: -336px -80px; } .icon-nextpage { background-position: -368px -80px; } .icon-refresh { background-position: -16px -48px; } .icon-edit { background-position: -48px -48px; } .icon-trash { background-position: -80px -48px; } .icon-settings { background-position: -112px -48px; } .icon-downloads { background-position: -144px -48px; } .icon-plane { background-position: -176px -48px; } .icon-truck { background-position: -208px -48px; } .icon-history { background-position: -240px -48px; } .icon-remove, .icon-close { background-position: -272px -48px; } .icon-merge { background-position: -304px -48px; } .icon-rss { background-position: -304px -112px; } .icon-trash-white { background-position: -336px -48px; } .icon-downloads-white { background-position: -368px -48px; } .icon-history-white { background-position: -400px -48px; } .icon-settings-white { background-position: -432px -48px; } .icon-messages-white { background-position: -464px -48px; } .icon-time-orange { background-position: -400px -80px; } .icon-split { background-position: -432px -80px; } .img-checkmark { background-position: -432px -16px; } .img-checkminus { background-position: -400px -16px; } .icon-postcard { background-position: -432px -112px; } .icon-link { background-position: -400px -112px; } .icon-alert { background-position: -336px -112px; } .icon-process { background-position: -304px -144px; } .icon-process-auto { background-position: -336px -144px; } .icon-duplicates { background-position: -368px -144px; } .icon-mask { background-position: -400px -144px; } .icon-mask-white { background-position: -432px -144px; } /* END: Icons */ .btn-toolbar { margin-top: 6px; margin-bottom: 0px; } .section-toolbar, .modal-toolbar { margin-top: 0; margin-bottom: 7px; } .section-title { margin-right: 10px; } .label { /* -ms-padding-bottom: 1px; */ } .label-status { text-transform: uppercase; } .label-inline { display: inline-block; margin-bottom: -4px; overflow-x: hidden; text-overflow: ellipsis; } .controls .label-status { line-height: 22px; } /* links in black color */ .table a { color: #000000; } /* links in black color */ .table a:hover { color: #000000; } table.datatable > tbody > tr > td { word-wrap: break-word; } #MainTabContent { margin-top: 0; overflow: visible; /* fix problem with dropdown menus */ } /* top toolbox (length-combo and pager) for tables */ .toolbox-top { margin-bottom: 8px; } /* combobox with page length for tables */ div.toolbox-length select { width: 75px; height: 28px; } .toolbox-info { margin-top: 10px; } .pagination { height: 32px; margin: 0; } .pagination a { padding: 0 10px; line-height: 26px; } .modal-tab .pagination { margin-bottom: 10px; } .padded-tab { padding-left: 20px; padding-right: 20px; } h1 { font-size: 24px; line-height: 36px; margin-bottom: 8px; margin-right: 20px; } h2 { font-size: 20px; line-height: 26px; margin-bottom: 8px; margin-right: 20px; } .alert-heading { margin-bottom: 10px; } /* remove focus border */ .nav-tabs > .active > a, .nav-tabs > .active > a:hover, .nav-pills > .active > a, .nav-pills > .active > a:hover, .nav-list > .active > a, .nav-list > .active > a:hover, .btn, .btn-group .btn, .pagination a, .btn-toolbar .btn , .control-group .btn, .modal-footer .btn, .form-search .btn, .btn:focus { outline: 0; } form { margin-bottom: 0px; } #DownloadsEdit_PostParamData, #HistoryEdit_PostParamData { padding-bottom: 1px; } #DownloadsEdit_FileTable_filter, #DownloadsEdit_LogTable_filter, #FeedDialog_ItemTable_filter { width: 180px; } .loading-block { position: absolute; left: 0; top: 0; right: 0; bottom: 0; text-align: center; } .loading-block img { position: absolute; top: 50%; left: 50%; } .modal-body { max-height: 360px; } .modal.no-footer .modal-body { max-height: 400px; } .modal-footer .btn-primary { min-width: 40px; } .modal-body .alert { margin-bottom: 0px; padding: 6px; } .modal-max { margin: 15px; left: 0; top: 0; bottom: 0; right: 0; width: auto; height: auto; } .modal-max .modal-body { position: absolute; left: 0; right: 0; max-height: inherit; /* top: 46px; // must be calculated at runtime */ /* bottom: 58px; // must be calculated at runtime */ } .modal-max .modal-inner-scroll { top: 54px; } .modal-max .modal-footer { position: absolute; left: 0; right: 0; bottom: 0; } .modal-inner-scroll { bottom: 0; left: 15px; overflow-y: auto; position: absolute; right: 0; padding-right: 15px; } .modal-inner-scroll .toolbox-info { margin-bottom: 10px; } .badge-active { background-color: #FFFFFF; color: #000000; text-shadow: none; } /* BEGIN: Tables */ .table { margin-bottom: 0px; } /* text-align for tables */ td.text-right,th.text-right { text-align: right; } /* text-align for tables */ td.text-center,th.text-center { text-align: center; } .table-striped tbody tr:nth-child(odd) { background-color: #f9f9f9; } .table tbody tr:hover { background-color: #f5f5f5; } .table th, .table td { padding: 5px; } .table-condensed th, .table-condensed td { padding: 2px; } .table-nonbordered, .table-nonbordered td { border: none; } /* END: Tables */ /* BEGIN: Checkmarks in the table */ table.table-check > thead > tr > th:first-child, table.table-check > tbody > tr > td:first-child { width: 14px; height: 14px; } div.check { width: 12px; height: 12px; border-color: #DDDDDD; border: 1px solid #DDDDDD; margin-top: 2px; margin-bottom: 3px; -webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; } div.check:hover { border-color: #0088cc; border: 1px solid #0088cc; } table.table-cancheck tr div.img-check { background-position: 10px 10px; } table.table-cancheck tr.checked div.img-check { background-position: -434px -18px; } table.table-cancheck tr.checkremove div.img-check { background-position: -402px -18px; } tr.checked, tr.checked td { background-color: #FFFFE1; } .table-striped tbody tr.checked:nth-child(odd) td { background-color: #FFFFE1; } .table tbody tr.checked:hover, .table tbody tr.checked:hover td { background-color: #FFFFCC; } .check-simple tr.checked, .check-simple tr.checked td, .table-striped.check-simple tbody tr.checked:nth-child(odd) td { background-color: inherit; } .table.check-simple tbody tr.checked:hover, .table.check-simple tbody tr.checked:hover td { background-color: #f5f5f5; } table.table-hidecheck thead > tr > th:first-child, table.table-hidecheck tbody > tr > td:first-child { display: none; } .checked .progress { background-color: #FFFFE1; } /* END: Checkmarks in the table */ /* BEGIN: Progress bars */ .progress-block { position: relative; width: 120px; } .progress { margin-bottom: 0px; background: #f0f0f0; } /* style for queued downloads, based on ".progress-success.progress-striped .bar" from bootstrap.css */ .progress-none.progress-striped .bar { background-color: #c0c0c0; background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); background-image: -webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: -moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: -ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: -o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); filter: progid:dximagetransform.microsoft.gradient(startColorstr='#d0d0d0', endColorstr='#c0c0c0', GradientType=0); } /* text on left side of progress bar */ .bar-text-left { position: absolute; top: 0; left: 5px; text-align: left; } /* text on right side of progress bar */ .bar-text-right { position: absolute; top: 0; right: 5px; text-align: right; } /* text on left side of progress bar */ .bar-text-center { position: absolute; top: 0; left: 5px; width: 100%; text-align: center; } /* END: Progress bars */ .input-prepend .add-on-small, .input-append .add-on-small { font-size: 11px; } .toolbtn { min-width: 56px; } /* BEGIN: override bootstrap styles for modals */ .modal-header h3 { text-align: center; } .modal-header .close { margin-top: 6px; margin-left: 10px; opacity: 0.6; filter: alpha(opacity=60); outline: 0; } .modal-header .close:hover { opacity: 0.9; filter: alpha(opacity=90); } .modal-header .back { float: left; margin-top: 6px; margin-right: 10px; font-size: 20px; font-weight: bold; line-height: 18px; opacity: 0.6; filter: alpha(opacity=60); outline: 0; } .modal-header .back:hover { cursor: pointer; opacity: 0.9; filter: alpha(opacity=90); } .modal-header .back-hidden:hover { cursor: inherit; } .form-horizontal .control-group { margin-bottom: 12px; } .modal .form-horizontal .control-group:last-child { margin-bottom: 0; } .modal .form-horizontal .retain-margin .control-group:last-child { margin-bottom: 15px; } .form-horizontal .control-group-last { margin-bottom: 0; } .form-horizontal .help-block { margin-top: 6px; line-height: 14px; } .form-horizontal .help-block-uneditable { margin-top: 3px; } .modal .input-medium { width: 200px; } .modal .input-xlarge { width: 350px; } .modal.modal-padded .input-xxlarge { width: 470px; } /* END: override bootstrap styles for modals */ .modal-bottom-toolbar .btn { margin-top: 12px; } /* based on uneditable-input */ .uneditable-mulitline-input { display: inline-block; width: 340px; overflow: hidden; cursor: not-allowed; background-color: #ffffff; border-color: #eee; border: 1px solid #eee; padding: 4px; padding-right: 20px; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); } .modal-mini { width: 420px; margin-left:-210px; } .modal-small { width: 480px; margin-left:-240px; } .modal-padded-small .modal-body { padding-left: 25px; padding-right: 25px; } .modal-padded .modal-body { padding-left: 40px; padding-right: 40px; } .dragover, .dragover .table-striped tbody tr:nth-child(odd) td, .dragover .table-striped tbody tr:nth-child(odd) th { background-color: #dff0d8; } .dialog-add-files { } ul.help > li { margin-bottom: 10px; } /* Make "select files" native control invisible */ .hidden-file-input { position: absolute; left: 0; top: 0; width: 0; height: 0; opacity: 0; filter: alpha(opacity=0); } /* BEGIN: Notification alerts */ .alert-inverse { color: #ffffff; text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); background-color: #414141; border-color: #222222; } .alert-center { position: fixed; padding: 20px; top: 50%; left: 50%; z-index: 2000; overflow: auto; text-align: center; opacity: 0.9; filter: alpha(opacity=90); } .alert-center-small { width: 200px; margin: -80px 0 0 -100px; } .alert-center-medium { width: 400px; margin: -80px 0 0 -200px; } .alert-error.alert-center { border-color: #B94A48; } .alert-success.alert-center { border-color: #468847; } .alert-info.alert-center { border-color: #3a87ad; } /* END: Notification alerts */ .confirm-help-block { color: #555555; font-size: 13px; line-height: 16px; margin-bottom: 0; } .table .btn-success, .table .btn-success:hover { color: #ffffff; } .btn-group { margin-right: 9px; } .btn-toolbar .btn-group { margin-right: 4px; } .btn-group + .btn-group { margin-left: 0; } .input-prepend .add-on:first-child { margin-left: 0; } /* important for group of buttons with different colors like toggle switch */ .btn-group > .btn:hover, .btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active { z-index: inherit; } #ErrorAlert, #FirstUpdateInfo, #ConfigReloadInfo, .unsupported-browser { margin-left: 20px; margin-right: 20px; } #ErrorAlert, #FirstUpdateInfo, #ConfigReloadInfo, .unsupported-browser { margin-top: 20px; } #StatisticsTab table { width: 350px; } .confirm-menu { text-align: left; min-width: 10px; right: 0; left: auto; } .footer-button-menu { text-align: left; } .data-statistics-full { width: 364px; } .data-statistics { width: 290px; } .modal-center { margin-top: 0; } /*** CONFIG PAGE */ #ConfigNav.nav-list a { color: #000; text-decoration: none; padding-top: 5px; padding-bottom: 5px; font-size: 12px; } #ConfigNav.nav-list.long-list a { padding-top: 3px; padding-bottom: 3px; } #ConfigNav.nav-list > .active > a, #ConfigNav.nav-list > .active > a:hover { color: #ffffff; *background-color: #505050; } #ConfigNav.nav .nav-header { font-size: 12px; } #ConfigNav { -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; border: 1px solid #eeeeee; padding: 8px 15px; background-color: #F7F7F9; margin-bottom: 15px; } #ConfigContent .config-header { padding: 7px; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; margin-bottom: 20px; padding-right: 0; padding-top: 0; border-bottom: 1px solid #eeeeee; } #ConfigTitle { margin-top: 15px; margin-right: 15px; font-size: 16px; font-weight: bold; } .config-header .btn-group { margin-right: 0; } .config-header .btn { margin-top: 7px; margin-right: 0; background-color: #ffffff; background-image: none; border: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; } span.help-option-title { color: #8D1212; } .failure-message { color: #8D1212; } #ConfigContent p.help-block { margin-top: 6px; line-height: 16px; } #ConfigContent.hide-help-block p.help-block { display: none; } #ConfigContent .control-label { font-weight: bold; } #ConfigContent select { width: inherit; } #ConfigContent .editnumeric { width: 70px; } #ConfigContent .editlarge { width: 95%; } #ConfigContent .editsmall { width: 150px; } #ConfigContent table.editor { width: 97%; } #ConfigContent table.editor td:first-child { width: 100%; padding-right:15px; } #ConfigContent table.editor input { width: 100%; } .ConfigFooter hr { margin: 6px 0 15px; } div.ConfigFooter { padding-bottom: 15px; } #ConfigContent hr { margin: 15px 0; } .config-settitle { font-size: 14px; font-weight: bold; background-color: #505050; color: #ffffff; padding: 7px; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; margin-bottom: 20px; border-bottom: 1px solid #eeeeee; } #ConfigContent.hide-help-block .config-settitle { margin-bottom: 15px; } .config-multicaption { color: #c0c0c0; font-weight: normal; } #DownloadsEdit_ParamTab div.control-group.wants-divider, #HistoryEdit_ParamTab div.control-group.wants-divider, #ConfigContent div.control-group, #ConfigContent.search div.control-group.multiset { border-bottom: 1px solid #eeeeee; margin-bottom: 15px; padding-bottom: 12px; } #ConfigContent.hide-help-block div.control-group, #ConfigContent.hide-help-block div.control-group.multiset { border-bottom: none; margin-bottom: 0px; padding-bottom: 12px; } div.control-group.last-group { margin-bottom: 0; } #ConfigContent div.control-group.last-group, #ConfigContent.search div.control-group.last-group.multiset { border-bottom: none; } #ConfigContent div.control-group.multiset { border-bottom: none; margin-bottom: 12px; padding-bottom: 8px; } #ConfigContent .control-label { width: 170px; } #ConfigContent .form-horizontal .controls { margin-left: 180px; } .btn-switch input:focus { border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; } .btn-switch .btn { text-transform: capitalize; } .option { font-weight: bold; font-style:italic; color: inherit; } .option-name, .option-name:focus, .option-name:hover { color: inherit; outline: 0; cursor: inherit; text-decoration: none; } .search .option-name, .search .option-name:focus { cursor: pointer; } .search .option-name:hover { cursor: pointer; text-decoration: underline; color: #005580; } .config-previewfeed { margin-left: 15px; } #ScriptListDialog_ScriptTable td:nth-child(2) { padding-right: 100px; } #ScriptListDialog_ScriptTable .btn-row-order-block { float: right; width: 100px; margin-right: -115px; display: block; } #ScriptListDialog_ScriptTable .btn-row-order { float: none; width: 20px; display: none; } #ScriptListDialog_ScriptTable tr:hover .btn-row-order { display: inline-block; cursor: pointer; } #ScriptListDialog_ScriptTable tbody > tr:first-child div.btn-row-order:first-child, #ScriptListDialog_ScriptTable tbody > tr:last-child div.btn-row-order:last-child, #ScriptListDialog_ScriptTable tbody > tr:first-child div.btn-row-order:nth-child(2), #ScriptListDialog_ScriptTable tbody > tr:last-child div.btn-row-order:nth-child(3) { opacity: 0.4; } /* UPDATE DIALOG */ .table .update-release-notes { color: #005580; font-size: 11px; height: 10px; outline: none; } #UpdateDialog_InstallStable, #UpdateDialog_InstallTesting, #UpdateDialog_InstallDevel { margin-top: 5px; } .table .update-row-name { font-weight: bold; padding-top: 14px; } #UpdateDialog_Versions #UpdateDialog_AvailRow td:hover { background-color: #f5f5f5; } #UpdateDialog_Versions #UpdateDialog_AvailRow td:first-child:hover, #UpdateDialog_Versions tr:hover td { background-color: #ffffff; } #UpdateProgressDialog { width: 640px; margin-left: -320px; } #UpdateProgressDialog .modal-body { min-height: 280px; position: relative; } #UpdateProgressDialog_Log { min-height: 270px; background-color: #222222; color: #cccccc; padding: 3px 6px; margin-bottom: 0px; position: absolute; top: 15px; bottom: 15px; left: 15px; right: 15px; overflow-y: auto; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; } .update-log-error { color: #ff0000; } /* FEED FILTER DIALOG */ #FeedFilterDialog_FilterBlock { position: absolute; left: 15px; width: 300px; bottom: 0; height: auto; padding-top: 0; margin-bottom: 12px; padding: 0; font-size: 12px; line-height: 18px; border: 1px solid #ccc; border: 1px solid rgba(0, 0, 0, 0.15); -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; } #FeedFilterDialog_FilterHeader { font-size: 13px; font-weight: bold; margin-top: 5px; padding-left: 6px; height: 23px; border-bottom: 1px solid #ccc; border-bottom: 1px solid rgba(0, 0, 0, 0.15); } #FeedFilterDialog_FilterLines { position: absolute; left: 0; top: 29px; bottom: 0px; width: 32px; height: auto; overflow: hidden; background-color: #ffffff; border-right: 1px solid #ccc; border-right: 1px solid rgba(0, 0, 0, 0.15); } #FeedFilterDialog_FilterNumbers { font-family: Menlo, Monaco, Consolas, "Courier New", monospace; } #FeedFilterDialog_FilterNumbers .lineno { color: #3A87AD; padding-right: 5px; padding-top: 0; text-align: right; font-weight: bold; white-space: nowrap; } #FeedFilterDialog_FilterClient { position: absolute; left: 33px; right: 0px; top: 29px; bottom: 0px; width: auto; padding-left: 3px; height: auto; background-color: #ffffff; } #FeedFilterDialog_FilterInput { width: 100%; height: 100%; margin: 0; padding: 0; border: 0; resize: none; outline: none; border: none; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; -webkit-border-radius: 0; -moz-border-radius: 0; border-radius: 0; } #FeedFilterDialog_PreviewBlock { position: absolute; left: 325px; right: 0; bottom: 0; height: auto; width: auto; padding-right: 15px; margin-bottom: 12px; } #FeedFilterDialog_Splitter { position: absolute; left: 319px; width: 5px; padding: 0; right: 5px; bottom: 0; height: auto; cursor: col-resize; } .phone #FeedFilterDialog_FilterBlock, .phone #FeedFilterDialog_PreviewBlock, .phone #FeedFilterDialog_FilterClient { position: static; top: inherit; left: 0; width: inherit; padding-right: 0; } .phone #FeedFilterDialog_FilterInput { height: 380px; } .filter-rule { cursor: pointer; } /****************************************************************************/ /* SMARTPHONE THEME */ body.phone { margin-top: 0; } .phone .navbar-fixed-top { position: static; } .phone #PlayBlock { width: 75px; } .phone #PlayCaretButton { width: 25px; margin-left: 10px; } .phone #InfoBlock { width: 190px; height: 42px; margin-right: 0; } .phone #InfoBlock div { display: inline-block; margin-left: 5px; margin-top: 5px; font-size: 14px; line-height: 32px; height: inherit; } /* GENERAL CLASSES */ .phone-only, .btn-group.phone-only { display: none; } .phone .phone-hide, .phone .btn-group.phone-hide { display: none; } .phone .phone-only { display: block; } .phone .phone-only.inline { display: inline-block; } /* FONTS */ body.phone, .phone p, .phone .form-horizontal .help-block , .phone h4 { font-size: 18px; line-height: 22px; } .phone table td { line-height: 22px; } .phone select, .phone input, .phone textarea, .phone label, .phone button, .phone .btn, .phone .btn-toolbar .btn, .phone .uneditable-input { font-size: 18px; line-height: 24px; height: inherit; } .phone .controls .label-status { line-height: 28px; } .phone .menu-header { font-size: 18px; line-height: 24px; } /* SECTION MARGINGS */ .phone .section-toolbar, .phone .toolbox-top, .phone .toolbox-info, .phone #ConfigTabData { padding-left: 5px; padding-right: 5px; } .phone #ErrorAlert, .phone #FirstUpdateInfo, .phone #ConfigReloadInfo, .phone #DownloadQueueEmpty { margin-left: 5px; margin-right: 5px; } .phone #FirstUpdateInfo, .phone #ConfigReloadInfo { margin-top: 5px; } .phone #MainContent { padding-left: 0px; padding-right: 0px; } .phone .section-toolbar{ margin-top: 8px; margin-bottom: 0; } .phone .toolbox-top { margin-top: 0; margin-bottom: 8px; } /* NAVBAR */ .phone .navbar-fixed-top { margin-bottom: 8px; } .phone .navbar-container { padding-left: 5px; padding-right: 5px; } .phone .navbar-inner .btn-toolbar { margin: 6px 0 0; } .phone ul.nav > li { text-align: center; min-width: 52px; } .phone .menu-header { text-align: left; } .phone .navbar .nav > li > a { padding: 4px 4px 6px; } .phone .navbar .nav > li.active > #DownloadsTabLink > i { /* icon-downloads (black) */ background-position: -144px -48px; } .phone .navbar .nav > li.active > #HistoryTabLink > i { /* icon-history (black) */ background-position: -240px -48px; } .phone .navbar .nav > li.active > #MessagesTabLink > i { /* icon-messages (black) */ background-position: -176px -16px; } .phone .navbar .nav > li.active > #ConfigTabLink > i { /* icon-settings (black) */ background-position: -112px -48px; } .phone .navbar .btn-toolbar .btn { padding: 3px; min-width: 40px; } .phone #RefreshBlockPhone { padding-left: 5px; } .phone .navbar-search .search-query, .phone .navbar-search .search-query:focus, .phone .navbar-search .search-query.focused { width: 160px; padding: 4px 9px; font-size: 16px; -webkit-border-radius: 16px; -moz-border-radius: 16px; border-radius: 16px; margin-bottom: 5px; margin-top: 1px; border: 0; } .phone .search-clear { top: 9px; } /* DATATABLE */ .phone table.datatable , .phone table.datatable > tbody, .phone table.datatable > tbody > tr, .phone table.datatable > tbody > tr > td { display: block; } .phone table.datatable > thead { display: none; } .phone table.datatable > tbody > tr > td { width: inherit; height: inherit; } .phone .datatable td { border: 0; } .phone table.datatable > tbody > tr > td:first-child { border-top: 1px solid #DDDDDD; } .phone table.datatable > tbody > tr:last-child > td:last-child { border-bottom: 1px solid #DDDDDD; } .phone table.datatable > tbody > tr > td:first-child { padding-top: 10px; } .phone table.datatable > tbody > tr > td:last-child { padding-bottom: 10px; } .phone table.table-check tbody td { padding-left: 60px; } /* CHECKMARKS IN DATATABLE */ .phone div.check { margin-top: -2px; margin-left: -48px; width: 30px; height: 30px; display: block; position: absolute; border-width: 2px; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; } .phone table.table-cancheck tr.checked div.img-check { /* icon-OK */ background-position: -74px -10px; } /* SPECIAL TABLE STYLES */ .phone .row-title { font-weight: bold; } .phone .progress-block { margin-top: -2px; } .phone .downloads-progresslabel { margin-top: -8px; margin-bottom: 10px; } .phone .label-inline { display: inline; } /* CONTROLS AROUND DATATABLE */ .phone .records-label { display: none; } /* PAGER */ .phone .toolbox-info div { float: none; width: 100%; text-align: center; display: block; margin-top: 5px; } .phone div.toolbox-length { margin-top: 10px; } .phone .pagination { height: auto; width: 100%; text-align: center; margin-top: 10px; } .phone .pagination a { padding: 0 10px; line-height: 34px; } /* STATUS LABELS */ .phone .label { font-size: 14px; line-height: 18px; vertical-align: middle; } .phone .datatable .label { line-height: 21px; } /* PROGRESS */ .phone .progress-block { font-size: 16px; width: 100%; } .phone .progress, .phone .progress .bar { height: 24px; } .phone .bar-text-left, .phone .bar-text-center, .phone .bar-text-right { padding-top: 1px; margin-top: 0px; } /* STATISTICS TABLE */ .phone #StatisticsTab table { width: 100%; } .phone #StatisticsTab td:first-child { font-weight: bold; } .phone #StatisticsTab td { text-align: left; } /* MENUS */ .phone .dropdown-menu a { padding: 7px 15px; } .phone .dropdown-toggle { position: static; } /* hide arrow */ .phone .navbar .dropdown-menu:before, .phone .navbar .dropdown-menu:after { display: none; } .phone #RefreshMenu { min-width: 200px; } .phone #SettingsMenu { min-width: 230px; } .phone #PlayMenu { min-width: 250px; } .phone #ToolbarOptMenu { min-width: 270px; } /* TOOLBAR AND INPUTS */ .phone .btn-toolbar .btn, .phone .btn-toolbar input { padding: 6px; min-width: 45px; } .phone .btn-toolbar .btn-group { margin-right: 0; } .phone .btn-toolbar .input-prepend .add-on, .phone .btn-toolbar .input-append .add-on { padding: 6px; min-width: 45px; } .phone .btn { min-width: 40px; } .phone input, .phone textarea, .phone .uneditable-input { height: 24px; } .phone input.btn { height: inherit; } .phone .input-prepend .add-on, .phone .input-append .add-on { height: 24px; line-height: 22px; min-width: 16px; } .phone select { height: auto; } .phone [class^="icon-"] { line-height: 24px; vertical-align: baseline; } .phone .caret { line-height: 24px; margin-top: -2px; vertical-align: middle; } /*** MODALS */ .phone .modal-footer > .btn, .phone .modal-footer > .btn-group { display: block; float: none; width: 100%; margin: 10px auto; } .phone .modal-footer .btn { padding: 7px 0; } .phone .modal-footer > .btn-group > .btn { width: 100%; margin: 0; } .phone .modal-footer { padding-top: 5px; padding-bottom: 5px; } .phone .modal-footer .confirm-menu { text-align: center; right: inherit; left: inherit; float: none; width: 100%; } .phone .modal-footer .confirm-menu .menu-header { text-align: center; } .phone .modal-padded .modal-body, .phone .modal-padded-small .modal-body { padding-left: 15px; padding-right: 15px; } .phone .modal-max .modal-body { position: static; } .phone .modal-max .modal-footer { position: static; } .phone .modal-max .modal-inner-scroll { position: static; top: inherit; left: 0; padding-right: 0; } .phone .data-statistics { width: 100%; } .phone .btn-caption { display: none; } .phone div.toolbox-length select { height: 36px; } .phone .navbar .btn-toolbar { position: relative; } .phone #ConfigNav.nav-list a { font-size: 18px; } .phone #ConfigNav.nav .nav-header { font-size: 20px; } .phone #ConfigContent .config-header { font-size: 20px; } .phone .config-settitle { font-size: 18px; } /* MEDIA SMALL SCREENS */ @media (max-width: 568px) { input[type="checkbox"], input[type="radio"] { border: 1px solid #ccc; } [class*="span"], .row-fluid [class*="span"] { display: block; float: none; width: auto; margin-left: 0; } .form-horizontal .control-group > label { float: none; width: auto; padding-top: 0; text-align: left; } .form-horizontal .controls, #ConfigContent .form-horizontal .controls { margin-left: 0; } .form-horizontal .control-list { padding-top: 0; } .form-horizontal .form-actions { padding-right: 10px; padding-left: 10px; } .modal { position: absolute; top: 0px; right: 0px; left: 0px; width: auto; margin: 0; } .modal.fade.in { top: auto; } .modal .input-xlarge , .modal .input-xxlarge, .modal.modal-padded .input-xxlarge, .uneditable-mulitline-input { width: 95%; } .modal-body { max-height: none; } .modal-center { right: 20px; left: 20px; } .alert-center-small, .alert-center-medium { right: 20px; left: 20px; width: auto; margin: -10% 0 0; } } @media (max-width: 700px) { #ConfigContent [class*="span"] { display: block; float: none; width: auto; margin-left: 0; } } @media (max-width: 479px) { #SearchBlock { display: none; } } @media (max-width: 529px) { #Logo { display: none; } } @media (max-width: 480px) { .dialog-transmit { display: none; } } /* END: MEDIA SMALL SCREENS */ nzbget-12.0+dfsg/webui/upload.js000066400000000000000000000250201226450633000166010ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2012-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 836 $ * $Date: 2013-09-23 22:18:54 +0200 (Mon, 23 Sep 2013) $ * */ /* * In this module: * 1) File upload dialog (local files, urls, scan); * 2) Drag-n-drop events handling on main page. */ /*** FILE UPLOAD (DRAG-N-DROP, URLS, SCAN) ************************************************/ var Upload = (new function($) { 'use strict'; // Controls var $AddDialog; // State var dragin = false; var files = []; var filesSuccess = []; var index; var errors = false; var needRefresh = false; var failure_message = null; var url = ''; this.init = function() { var target = $('#DownloadsTab')[0]; target.addEventListener('dragenter', bodyDragOver); target.addEventListener('dragover', bodyDragOver); target.addEventListener('dragleave', bodyDragLeave); target.addEventListener('drop', bodyDrop, false); target = $('#AddDialog_Target')[0]; target.addEventListener('dragenter', dialogDragOver); target.addEventListener('dragover', dialogDragOver); target.addEventListener('dragleave', dialogDragLeave); target.addEventListener('drop', dialogDrop, false); $AddDialog = $('#AddDialog'); $AddDialog.on('hidden', function () { Refresher.resume(); files = []; filesSuccess = []; if (needRefresh) { Refresher.update(); } }); if (UISettings.setFocus) { $AddDialog.on('shown', function () { if (files.length === 0) { $('#AddDialog_URL').focus(); } }); } $('#AddDialog_Select').click(selectFiles); $('#AddDialog_Submit').click(submit); $('#AddDialog_Input')[0].addEventListener("change", fileSelectHandler, false); $('#AddDialog_Scan').click(scan); } function bodyDragOver(event) { if ((event.dataTransfer.types.contains && event.dataTransfer.types.contains('Files')) || (event.dataTransfer.types.indexOf && event.dataTransfer.types.indexOf('Files') > -1) || (event.dataTransfer.files && event.dataTransfer.files.length > 0)) { event.stopPropagation(); event.preventDefault(); if (!dragin) { dragin = true; $('body').addClass('dragover'); } } } function bodyDragLeave(event) { dragin = false; $('body').removeClass('dragover'); } function bodyDrop(event) { event.preventDefault(); bodyDragLeave(); if (!event.dataTransfer.files) { showDnDUnsupportedAlert(); return; } showModal(event.dataTransfer.files); } function dialogDragOver(event) { event.stopPropagation(); event.preventDefault(); if (!dragin) { dragin = true; $('#AddDialog_Target').addClass('dragover'); } } function dialogDragLeave(event) { dragin = false; $('#AddDialog_Target').removeClass('dragover'); } function dialogDrop(event) { event.preventDefault(); dialogDragLeave(); if (!event.dataTransfer.files) { showDnDUnsupportedAlert(); return; } addFiles(event.dataTransfer.files); } function showDnDUnsupportedAlert() { setTimeout(function() { alert("Unfortunately your browser doesn't support drag and drop for files.\n\nPlease use alternative ways to add files to queue:\nadd via URL or put the files directly into incoming nzb-directory."); }, 50); } function selectFiles() { var inp = $('#AddDialog_Input'); // Reset file input control; needed for IE10 but produce problems with opera (the old non-webkit one). if ($.browser.msie) { inp.wrap('').closest('form').get(0).reset(); inp.unwrap(); } inp.click(); } function fileSelectHandler(event) { if (!event.target.files) { alert("Unfortunately your browser doesn't support direct access to local files.\n\nPlease use alternative ways to add files to queue:\nadd via URL or put the files directly into incoming nzb-directory."); return; } addFiles(event.target.files); } function addFiles(selectedFiles) { var list = ''; for (var i = 0; i'; $('#AddDialog_Files').append(html); files.push(file); } $('#AddDialog_Files').show(); $('#AddDialog_FilesHelp').hide(); } this.addClick = function() { showModal(); } function showModal(droppedFiles) { Refresher.pause(); $('#AddDialog_Files').empty(); $('#AddDialog_URL').val(''); $('#AddDialog_FilesHelp').show(); $('#AddDialog_URLLabel img').hide(); $('#AddDialog_URLLabel i').hide(); $('#AddDialog_Paused').prop('checked', false); $('#AddDialog_DupeForce').prop('checked', false); enableAllButtons(); var v = $('#AddDialog_Priority'); DownloadsUI.fillPriorityCombo(v); v.val(0); DownloadsUI.fillCategoryCombo($('#AddDialog_Category')); files = []; filesSuccess = []; if (droppedFiles) { addFiles(droppedFiles); } $AddDialog.modal({backdrop: 'static'}); } function disableAllButtons() { $('#AddDialog .modal-footer .btn, #AddDialog_Select, #AddDialog_Scan').attr('disabled', 'disabled'); } function enableAllButtons() { $('#AddDialog .modal-footer .btn, #AddDialog_Select, #AddDialog_Scan').removeAttr('disabled'); $('#AddDialog_Transmit').hide(); } function submit() { disableAllButtons(); if (files.length > 0) { if (!window.FileReader) { $AddDialog.modal('hide'); alert("Unfortunately your browser doesn't support FileReader API.\n\nPlease use alternative ways to add files to queue:\nadd via URL or put the files directly into incoming nzb-directory."); return; } var testreader = new FileReader(); if (!testreader.readAsBinaryString && !testreader.readAsDataURL) { $AddDialog.modal('hide'); alert("Unfortunately your browser doesn't support neither \"readAsBinaryString\" nor \"readAsDataURL\" functions of FileReader API.\n\nPlease use alternative ways to add files to queue:\nadd via URL or put the files directly into incoming nzb-directory."); return; } } needRefresh = false; errors = false; failure_message = null; index = 0; url = $('#AddDialog_URL').val(); /* setTimeout(function(){ $('#AddDialog_Transmit').show(); }, 500); */ if (url.length > 0) { urlNext(); } else { fileNext(); } } function fileNext() { if (index === files.length) { allCompleted(); return; } var file = files[index]; if (filesSuccess.indexOf(file) > -1) { // file already uploaded index++; setTimeout(next, 50); return; } $('#AddDialog_Files table:eq(' + index + ') img').show(); $('#AddDialog_Files table:eq(' + index + ') i').hide(); var reader = new FileReader(); reader.onload = function (event) { var base64str; if (reader.readAsBinaryString) { base64str = window.btoa(event.target.result); } else { base64str = event.target.result.replace(/^data:[^,]+,/, ''); } var category = $('#AddDialog_Category').val(); var priority = parseInt($('#AddDialog_Priority').val()); var filename = file.name.replace(/\.queued$/g, ''); var addPaused = $('#AddDialog_Paused').is(':checked'); var dupeMode = $('#AddDialog_DupeForce').is(':checked') ? "FORCE" : "SCORE"; RPC.call('append', [filename, category, priority, false, base64str, addPaused, '', 0, dupeMode], fileCompleted, fileFailure); }; if (reader.readAsBinaryString) { reader.readAsBinaryString(file); } else { reader.readAsDataURL(file); } } function fileCompleted(result) { errors |= !result; needRefresh |= result; if (result) { filesSuccess.push(files[index]); } $('#AddDialog_Files table:eq(' + index + ') img').hide(); $('#AddDialog_Files table:eq(' + index + ') i').removeClass('icon-file').addClass( result ? 'icon-ok' : 'icon-remove').show(); index++; fileNext(); } function fileFailure(res) { failure_message = res; fileCompleted(false); } function urlNext() { $('#AddDialog_URLLabel img').show(); $('#AddDialog_URLLabel i').hide(); var category = $('#AddDialog_Category').val(); var priority = parseInt($('#AddDialog_Priority').val()); var addPaused = $('#AddDialog_Paused').is(':checked'); var dupeMode = $('#AddDialog_DupeForce').is(':checked') ? "FORCE" : "SCORE"; RPC.call('appendurl', ['', category, priority, false, url, addPaused, '', 0, dupeMode], urlCompleted, urlFailure); } function urlCompleted(result) { errors |= !result; needRefresh |= result; if (result) { $('#AddDialog_URL').empty(); } $('#AddDialog_URLLabel img').hide(); $('#AddDialog_URLLabel i').removeClass('icon-ok').removeClass('icon-remove').addClass( result ? 'icon-ok' : 'icon-remove').show(); fileNext(); } function urlFailure(res) { failure_message = res; urlCompleted(false); } function allCompleted() { if (errors) { enableAllButtons(); // using timeout for browser to update UI (buttons) before showing the alert setTimeout(function() { if (failure_message) { alert((index > 1 ? 'One or more files' : 'The file') + ' could not be added to the queue:\n' + failure_message); } else { alert((index > 1 ? 'One or more files' : 'The file') + ' could not be added to the queue.\nPlease check the messages tab for any error messages.'); } needRefresh = true; }, 100); } else { $AddDialog.modal('hide'); if (index > 0) { Notification.show('#Notif_AddFiles'); } } } function scan() { disableAllButtons(); setTimeout(function(){ $('#AddDialog_Transmit').show(); }, 500); RPC.call('scan', [true], function() { needRefresh = true; $AddDialog.modal('hide'); Notification.show('#Notif_Scan'); }); } }(jQuery)); nzbget-12.0+dfsg/webui/util.js000066400000000000000000000300511226450633000162720ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2012-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 881 $ * $Date: 2013-10-17 21:35:43 +0200 (Thu, 17 Oct 2013) $ * */ /* * In this module: * 1) Common utilitiy functions (format time, size, etc); * 2) Slideable tab dialog extension; * 3) Communication via JSON-RPC. */ /*** UTILITY FUNCTIONS *********************************************************/ var Util = (new function($) { 'use strict'; this.formatTimeHMS = function(sec) { var hms = ''; var days = Math.floor(sec / 86400); if (days > 0) { hms = days + 'd '; } var hours = Math.floor((sec % 86400) / 3600); hms = hms + hours + ':'; var minutes = Math.floor((sec / 60) % 60); if (minutes < 10) { hms = hms + '0'; } hms = hms + minutes + ':'; var seconds = Math.floor(sec % 60); if (seconds < 10) { hms = hms + '0'; } hms = hms + seconds; return hms; } this.formatTimeLeft = function(sec) { var hms = ''; var days = Math.floor(sec / 86400); var hours = Math.floor((sec % 86400) / 3600); var minutes = Math.floor((sec / 60) % 60); var seconds = Math.floor(sec % 60); if (days > 10) { return days + 'd'; } if (days > 0) { return days + 'd ' + hours + 'h'; } if (hours > 0) { return hours + 'h ' + (minutes < 10 ? '0' : '') + minutes + 'm'; } if (minutes > 0) { return minutes + 'm ' + (seconds < 10 ? '0' : '') + seconds + 's'; } return seconds + 's'; } this.formatDateTime = function(unixTime) { var dt = new Date(unixTime * 1000); var h = dt.getHours(); var m = dt.getMinutes(); var s = dt.getSeconds(); return dt.toDateString() + ' ' + (h < 10 ? '0' : '') + h + ':' + (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s; } this.formatSizeMB = function(sizeMB, sizeLo) { if (sizeLo !== undefined && sizeMB < 100) { sizeMB = sizeLo / 1024 / 1024; } if (sizeMB > 10240) { return this.round1(sizeMB / 1024.0) + ' GB'; } else if (sizeMB > 1024) { return this.round2(sizeMB / 1024.0) + ' GB'; } else if (sizeMB > 100) { return this.round0(sizeMB) + ' MB'; } else if (sizeMB > 10) { return this.round1(sizeMB) + ' MB'; } else { return this.round2(sizeMB) + ' MB'; } } this.formatAge = function(time) { if (time == 0) { return ''; } var diff = new Date().getTime() / 1000 - time; if (diff > 60*60*24) { return this.round0(diff / (60*60*24)) +' d'; } else { return this.round0(diff / (60*60)) +' h'; } } this.round0 = function(arg) { return Math.round(arg); } this.round1 = function(arg) { return arg.toFixed(1); } this.round2 = function(arg) { return arg.toFixed(2); } this.formatNZBName = function(NZBName) { return NZBName.replace(/\./g, ' ') .replace(/_/g, ' '); } this.textToHtml = function(str) { return str.replace(/&/g, '&') .replace(//g, '>'); } this.textToAttr = function(str) { return str.replace(/&/g, '&') .replace(/ 0 ? top : 0; $elem.css({ top: top}); } else { $elem.css({ top: '' }); } } }(jQuery)); /*** MODAL DIALOG WITH SLIDEABLE TABS *********************************************************/ var TabDialog = (new function($) { 'use strict'; this.extend = function(dialog) { dialog.restoreTab = restoreTab; dialog.switchTab = switchTab; dialog.maximize = maximize; } function maximize(options) { var bodyPadding = 15; var dialog = this; var body = $('.modal-body', dialog); var footer = $('.modal-footer', dialog); var header = $('.modal-header', dialog); body.css({top: header.outerHeight(), bottom: footer.outerHeight()}); if (options.mini) { var scrollheader = $('.modal-scrollheader', dialog); var scroll = $('.modal-inner-scroll', dialog); scroll.css('min-height', dialog.height() - header.outerHeight() - footer.outerHeight() - scrollheader.height() - bodyPadding*2); } } function restoreTab() { var dialog = this; var body = $('.modal-body', dialog); var footer = $('.modal-footer', dialog); var header = $('.modal-header', dialog); dialog.css({margin: '', left: '', top: '', bottom: '', right: '', width: '', height: ''}); body.css({position: '', height: '', left: '', right: '', top: '', bottom: '', 'max-height': ''}); footer.css({position: '', left: '', right: '', bottom: ''}); } function switchTab(fromTab, toTab, duration, options) { var dialog = this; var sign = options.back ? -1 : 1; var fullscreen = options.fullscreen && !options.back; var bodyPadding = 15; var dialogMargin = options.mini ? 0 : 15; var dialogBorder = 2; var body = $('.modal-body', dialog); var footer = $('.modal-footer', dialog); var header = $('.modal-header', dialog); var oldBodyHeight = body.height(); var oldWinHeight = dialog.height(); var windowWidth = $(window).width(); var windowHeight = $(window).height(); var oldTabWidth = fromTab.width(); var dialogStyleFS, bodyStyleFS, footerStyleFS; if (options.fullscreen && options.back) { // save fullscreen state for later use dialogStyleFS = dialog.attr('style'); bodyStyleFS = body.attr('style'); footerStyleFS = footer.attr('style'); // restore non-fullscreen state to calculate proper destination sizes dialog.restoreTab(); } fromTab.hide(); toTab.show(); // CONTROL POINT: at this point the destination dialog size is active // store destination positions and sizes var newBodyHeight = fullscreen ? windowHeight - header.outerHeight() - footer.outerHeight() - dialogMargin*2 - bodyPadding*2 : body.height(); var newTabWidth = fullscreen ? windowWidth - dialogMargin*2 - dialogBorder - bodyPadding*2 : toTab.width(); var leftPos = toTab.position().left; var newDialogPosition = dialog.position(); var newDialogWidth = dialog.width(); var newDialogHeight = dialog.height(); var newDialogMarginLeft = dialog.css('margin-left'); var newDialogMarginTop = dialog.css('margin-top'); // restore source dialog size if (options.fullscreen && options.back) { // restore fullscreen state dialog.attr('style', dialogStyleFS); body.attr('style', bodyStyleFS); footer.attr('style', footerStyleFS); } body.css({position: '', height: oldBodyHeight}); dialog.css('overflow', 'hidden'); fromTab.css({position: 'absolute', left: leftPos, width: oldTabWidth, height: oldBodyHeight}); toTab.css({position: 'absolute', width: newTabWidth, height: oldBodyHeight, left: sign * ((options.back ? newTabWidth : oldTabWidth) + bodyPadding*2)}); fromTab.show(); // animate dialog to destination position and sizes if (options.fullscreen && options.back) { body.css({position: 'absolute'}); dialog.animate({ 'margin-left': newDialogMarginLeft, 'margin-top': newDialogMarginTop, left: newDialogPosition.left, top: newDialogPosition.top, right: newDialogPosition.left + newDialogWidth, bottom: newDialogPosition.top + newDialogHeight, width: newDialogWidth, height: newDialogHeight }, duration); body.animate({height: newBodyHeight, 'max-height': newBodyHeight}, duration); } else if (options.fullscreen) { dialog.css({height: dialog.height()}); footer.css({position: 'absolute', left: 0, right: 0, bottom: 0}); dialog.animate({ margin: dialogMargin, left: '0%', top: '0%', bottom: '0%', right: '0%', width: windowWidth - dialogMargin*2, height: windowHeight - dialogMargin*2 }, duration); body.animate({height: newBodyHeight, 'max-height': newBodyHeight}, duration); } else { body.animate({height: newBodyHeight}, duration); } fromTab.animate({left: sign * -((options.back ? newTabWidth : oldTabWidth) + bodyPadding*2), height: newBodyHeight + bodyPadding}, duration); toTab.animate({left: leftPos, height: newBodyHeight + bodyPadding}, duration, function() { fromTab.hide(); fromTab.css({position: '', width: '', height: '', left: ''}); toTab.css({position: '', width: '', height: '', left: ''}); dialog.css({overflow: '', width: (fullscreen ? 'auto' : ''), height: (fullscreen ? 'auto' : '')}); if (fullscreen) { body.css({position: 'absolute', height: '', left: 0, right: 0, top: header.outerHeight(), bottom: footer.outerHeight(), 'max-height': 'inherit'}); } else { body.css({position: '', height: ''}); } if (options.fullscreen && options.back) { // restore non-fullscreen state dialog.restoreTab(); } if (options.complete) { options.complete(); } }); } }(jQuery)); /*** REMOTE PROCEDURE CALLS VIA JSON-RPC *************************************************/ var RPC = (new function($) { 'use strict'; // Properties this.rpcUrl; this.defaultFailureCallback; this.connectErrorMessage = 'Cannot establish connection'; this.call = function(method, params, completed_callback, failure_callback, timeout) { var _this = this; var request = JSON.stringify({nocache: new Date().getTime(), method: method, params: params}); var xhr = new XMLHttpRequest(); xhr.open('post', this.rpcUrl); if (timeout) { xhr.timeout = timeout; } // Example for cross-domain access: //xhr.open('post', 'http://localhost:6789/jsonrpc'); //xhr.withCredentials = 'true'; //xhr.setRequestHeader('Authorization', 'Basic ' + window.btoa('myusername:mypassword')); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { var res = 'Unknown error'; var result; if (xhr.status === 200) { if (xhr.responseText != '') { try { result = JSON.parse(xhr.responseText); } catch (e) { res = e; } if (result) { if (result.error == null) { res = result.result; completed_callback(res); return; } else { res = result.error.message + '

    Request: ' + request; } } } else { res = 'No response received.'; } } else if (xhr.status === 0) { res = _this.connectErrorMessage; } else { res = 'Invalid Status: ' + xhr.status; } if (failure_callback) { failure_callback(res, result); } else { _this.defaultFailureCallback(res, result); } } }; xhr.send(request); } }(jQuery)); nzbget-12.0+dfsg/win32.h000066400000000000000000000053011226450633000147570ustar00rootroot00000000000000/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision: 929 $ * $Date: 2014-01-02 21:32:11 +0100 (Thu, 02 Jan 2014) $ * */ /* win32.h - Defines and standard includes for MS Windows / Visual C++ 2005 */ /* Define to 1 to not use curses */ //#define DISABLE_CURSES /* Define to 1 to disable smart par-verification and restoration */ //#define DISABLE_PARCHECK /* Define to 1 to disable TLS/SSL-support. */ //#define DISABLE_TLS #ifndef DISABLE_TLS /* Define to 1 to use GnuTLS library for TLS/SSL-support */ #define HAVE_LIBGNUTLS /* Define to 1 to use OpenSSL library for TLS/SSL-support */ //#define HAVE_OPENSSL #endif /* Define to the name of macro which returns the name of function being compiled */ #define FUNCTION_MACRO_NAME __FUNCTION__ /* Define to 1 if ctime_r takes 2 arguments */ #undef HAVE_CTIME_R_2 /* Define to 1 if ctime_r takes 3 arguments */ #define HAVE_CTIME_R_3 /* Define to 1 if getopt_long is supported */ #undef HAVE_GETOPT_LONG /* Define to 1 if variadic macros are supported */ #define HAVE_VARIADIC_MACROS /* Define to 1 if libpar2 supports cancelling (needs a special patch) */ #define HAVE_PAR2_CANCEL /* Define to 1 if libpar2 has bugfixes applied (needs a special patch) */ #define HAVE_PAR2_BUGFIXES_V2 /* Define to 1 if function GetAddrInfo is supported */ #define HAVE_GETADDRINFO /* Determine what socket length (socklen_t) data type is */ #define SOCKLEN_T socklen_t /* Define to 1 if you have the header file. */ #define HAVE_REGEX_H 1 /* Define to 1 if spinlocks are supported */ #define HAVE_SPINLOCK #define VERSION "12.0" /* Suppress warnings */ #define _CRT_SECURE_NO_DEPRECATE /* Suppress warnings */ #define _CRT_NONSTDC_NO_WARNINGS #define _USE_32BIT_TIME_T #ifdef _DEBUG // detection of memory leaks #define _CRTDBG_MAP_ALLOC #include #include #endif #ifndef SKIP_DEFAULT_WINDOWS_HEADERS #include #include #endif