mirror of
https://github.com/nzbget/nzbget.git
synced 2026-04-18 03:46:52 -04:00
to replace direct calls to “usleep”, with parameter in milliseconds instead of microseconds.
913 lines
23 KiB
C++
913 lines
23 KiB
C++
/*
|
|
* This file is part of nzbget. See <http://nzbget.net>.
|
|
*
|
|
* Copyright (C) 2014-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
#include "nzbget.h"
|
|
#include "ArticleWriter.h"
|
|
#include "DiskState.h"
|
|
#include "Options.h"
|
|
#include "Log.h"
|
|
#include "Util.h"
|
|
#include "FileSystem.h"
|
|
|
|
CachedSegmentData::~CachedSegmentData()
|
|
{
|
|
g_ArticleCache->Free(this);
|
|
}
|
|
|
|
CachedSegmentData& CachedSegmentData::operator=(CachedSegmentData&& other)
|
|
{
|
|
g_ArticleCache->Free(this);
|
|
m_data = other.m_data;
|
|
m_size = other.m_size;
|
|
other.m_data = nullptr;
|
|
other.m_size = 0;
|
|
return *this;
|
|
}
|
|
|
|
|
|
void ArticleWriter::SetWriteBuffer(DiskFile& outFile, int recSize)
|
|
{
|
|
if (g_Options->GetWriteBuffer() > 0)
|
|
{
|
|
outFile.SetWriteBuffer(recSize > 0 && recSize < g_Options->GetWriteBuffer() * 1024 ?
|
|
recSize : g_Options->GetWriteBuffer() * 1024);
|
|
}
|
|
}
|
|
|
|
void ArticleWriter::Prepare()
|
|
{
|
|
BuildOutputFilename();
|
|
m_resultFilename = m_articleInfo->GetResultFilename();
|
|
}
|
|
|
|
bool ArticleWriter::Start(Decoder::EFormat format, const char* filename, int64 fileSize,
|
|
int64 articleOffset, int articleSize)
|
|
{
|
|
m_outFile.Close();
|
|
m_format = format;
|
|
m_articleOffset = articleOffset;
|
|
m_articleSize = articleSize ? articleSize : m_articleInfo->GetSize();
|
|
m_articlePtr = 0;
|
|
|
|
// prepare file for writing
|
|
if (m_format == Decoder::efYenc)
|
|
{
|
|
if (g_Options->GetDupeCheck() &&
|
|
m_fileInfo->GetNzbInfo()->GetDupeMode() != dmForce &&
|
|
!m_fileInfo->GetNzbInfo()->GetManyDupeFiles())
|
|
{
|
|
bool outputInitialized;
|
|
{
|
|
Guard guard = m_fileInfo->GuardOutputFile();
|
|
outputInitialized = m_fileInfo->GetOutputInitialized();
|
|
if (!g_Options->GetDirectWrite())
|
|
{
|
|
m_fileInfo->SetOutputInitialized(true);
|
|
}
|
|
}
|
|
|
|
if (!outputInitialized && filename &&
|
|
FileSystem::FileExists(BString<1024>("%s%c%s", m_fileInfo->GetNzbInfo()->GetDestDir(), PATH_SEPARATOR, filename)))
|
|
{
|
|
m_duplicate = true;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (g_Options->GetDirectWrite())
|
|
{
|
|
Guard guard = m_fileInfo->GuardOutputFile();
|
|
if (!m_fileInfo->GetOutputInitialized())
|
|
{
|
|
if (!CreateOutputFile(fileSize))
|
|
{
|
|
return false;
|
|
}
|
|
m_fileInfo->SetOutputInitialized(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// allocate cache buffer
|
|
if (g_Options->GetArticleCache() > 0 && !g_Options->GetRawArticle() &&
|
|
(!g_Options->GetDirectWrite() || m_format == Decoder::efYenc))
|
|
{
|
|
m_articleData = g_ArticleCache->Alloc(m_articleSize);
|
|
|
|
while (!m_articleData.GetData() && g_ArticleCache->GetFlushing())
|
|
{
|
|
Util::Sleep(5);
|
|
m_articleData = g_ArticleCache->Alloc(m_articleSize);
|
|
}
|
|
|
|
if (!m_articleData.GetData())
|
|
{
|
|
detail("Article cache is full, using disk for %s", *m_infoName);
|
|
}
|
|
}
|
|
|
|
if (!m_articleData.GetData())
|
|
{
|
|
bool directWrite = (g_Options->GetDirectWrite() || m_fileInfo->GetForceDirectWrite()) && m_format == Decoder::efYenc;
|
|
const char* outFilename = directWrite ? m_outputFilename : m_tempFilename;
|
|
if (!m_outFile.Open(outFilename, directWrite ? DiskFile::omReadWrite : DiskFile::omWrite))
|
|
{
|
|
m_fileInfo->GetNzbInfo()->PrintMessage(Message::mkError,
|
|
"Could not %s file %s: %s", directWrite ? "open" : "create", outFilename,
|
|
*FileSystem::GetLastErrorMessage());
|
|
return false;
|
|
}
|
|
SetWriteBuffer(m_outFile, m_articleInfo->GetSize());
|
|
|
|
if (directWrite)
|
|
{
|
|
m_outFile.Seek(m_articleOffset);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ArticleWriter::Write(char* buffer, int len)
|
|
{
|
|
if (!g_Options->GetRawArticle())
|
|
{
|
|
m_articlePtr += len;
|
|
}
|
|
|
|
if (m_articlePtr > m_articleSize)
|
|
{
|
|
// An attempt to write beyond article border is detected.
|
|
// That's an error condition (damaged article).
|
|
// We return 'false' since this isn't a fatal disk error and
|
|
// article size mismatch will be detected in decoder check anyway.
|
|
return true;
|
|
}
|
|
|
|
if (!g_Options->GetRawArticle() && m_articleData.GetData())
|
|
{
|
|
memcpy(m_articleData.GetData() + m_articlePtr - len, buffer, len);
|
|
return true;
|
|
}
|
|
|
|
if (g_Options->GetSkipWrite())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return m_outFile.Write(buffer, len) > 0;
|
|
}
|
|
|
|
void ArticleWriter::Finish(bool success)
|
|
{
|
|
m_outFile.Close();
|
|
|
|
if (!success)
|
|
{
|
|
FileSystem::DeleteFile(m_tempFilename);
|
|
FileSystem::DeleteFile(m_resultFilename);
|
|
return;
|
|
}
|
|
|
|
bool directWrite = (g_Options->GetDirectWrite() || m_fileInfo->GetForceDirectWrite()) && m_format == Decoder::efYenc;
|
|
|
|
if (!g_Options->GetRawArticle())
|
|
{
|
|
if (!directWrite && !m_articleData.GetData())
|
|
{
|
|
if (!FileSystem::MoveFile(m_tempFilename, m_resultFilename))
|
|
{
|
|
m_fileInfo->GetNzbInfo()->PrintMessage(Message::mkError,
|
|
"Could not rename file %s to %s: %s", *m_tempFilename, m_resultFilename,
|
|
*FileSystem::GetLastErrorMessage());
|
|
}
|
|
FileSystem::DeleteFile(m_tempFilename);
|
|
}
|
|
|
|
if (m_articleData.GetData())
|
|
{
|
|
if (m_articleSize != m_articlePtr)
|
|
{
|
|
g_ArticleCache->Realloc(&m_articleData, m_articlePtr);
|
|
}
|
|
Guard contentGuard = g_ArticleCache->GuardContent();
|
|
m_articleInfo->AttachSegment(std::make_unique<CachedSegmentData>(std::move(m_articleData)), m_articleOffset, m_articlePtr);
|
|
m_fileInfo->SetCachedArticles(m_fileInfo->GetCachedArticles() + 1);
|
|
}
|
|
else
|
|
{
|
|
m_articleInfo->SetSegmentOffset(m_articleOffset);
|
|
m_articleInfo->SetSegmentSize(m_articlePtr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// rawmode
|
|
if (!FileSystem::MoveFile(m_tempFilename, m_resultFilename))
|
|
{
|
|
m_fileInfo->GetNzbInfo()->PrintMessage(Message::mkError,
|
|
"Could not move file %s to %s: %s", *m_tempFilename, m_resultFilename,
|
|
*FileSystem::GetLastErrorMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
/* creates output file and subdirectores */
|
|
bool ArticleWriter::CreateOutputFile(int64 size)
|
|
{
|
|
if (FileSystem::FileExists(m_outputFilename))
|
|
{
|
|
if (FileSystem::FileSize(m_outputFilename) == size)
|
|
{
|
|
// keep existing old file from previous program session
|
|
return true;
|
|
}
|
|
// delete existing old file from previous program session
|
|
FileSystem::DeleteFile(m_outputFilename);
|
|
}
|
|
|
|
// ensure the directory exist
|
|
BString<1024> destDir;
|
|
destDir.Set(m_outputFilename, (int)(FileSystem::BaseFileName(m_outputFilename) - m_outputFilename));
|
|
CString errmsg;
|
|
|
|
if (!FileSystem::ForceDirectories(destDir, errmsg))
|
|
{
|
|
m_fileInfo->GetNzbInfo()->PrintMessage(Message::mkError,
|
|
"Could not create directory %s: %s", *destDir, *errmsg);
|
|
return false;
|
|
}
|
|
|
|
if (!FileSystem::AllocateFile(m_outputFilename, size, g_Options->GetArticleCache() == 0, errmsg))
|
|
{
|
|
m_fileInfo->GetNzbInfo()->PrintMessage(Message::mkError,
|
|
"Could not create file %s: %s", *m_outputFilename, *errmsg);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ArticleWriter::BuildOutputFilename()
|
|
{
|
|
BString<1024> filename("%s%c%i.%03i", g_Options->GetTempDir(), PATH_SEPARATOR,
|
|
m_fileInfo->GetId(), m_articleInfo->GetPartNumber());
|
|
|
|
m_articleInfo->SetResultFilename(filename);
|
|
m_tempFilename.Format("%s.tmp", *filename);
|
|
|
|
if (g_Options->GetDirectWrite() || m_fileInfo->GetForceDirectWrite())
|
|
{
|
|
Guard guard = m_fileInfo->GuardOutputFile();
|
|
|
|
if (m_fileInfo->GetOutputFilename())
|
|
{
|
|
filename = m_fileInfo->GetOutputFilename();
|
|
}
|
|
else
|
|
{
|
|
filename.Format("%s%c%i.out.tmp", m_fileInfo->GetNzbInfo()->GetDestDir(),
|
|
PATH_SEPARATOR, m_fileInfo->GetId());
|
|
m_fileInfo->SetOutputFilename(filename);
|
|
}
|
|
|
|
m_outputFilename = *filename;
|
|
}
|
|
}
|
|
|
|
void ArticleWriter::CompleteFileParts()
|
|
{
|
|
debug("Completing file parts");
|
|
debug("ArticleFilename: %s", m_fileInfo->GetFilename());
|
|
|
|
bool directWrite = (g_Options->GetDirectWrite() || m_fileInfo->GetForceDirectWrite()) && m_fileInfo->GetOutputInitialized();
|
|
|
|
BString<1024> nzbName;
|
|
BString<1024> nzbDestDir;
|
|
BString<1024> filename;
|
|
|
|
{
|
|
GuardedDownloadQueue guard = DownloadQueue::Guard();
|
|
nzbName = m_fileInfo->GetNzbInfo()->GetName();
|
|
nzbDestDir = m_fileInfo->GetNzbInfo()->GetDestDir();
|
|
filename = m_fileInfo->GetFilename();
|
|
}
|
|
|
|
BString<1024> infoFilename("%s%c%s", *nzbName, PATH_SEPARATOR, *filename);
|
|
|
|
bool cached = m_fileInfo->GetCachedArticles() > 0;
|
|
|
|
if (g_Options->GetRawArticle())
|
|
{
|
|
detail("Moving articles for %s", *infoFilename);
|
|
}
|
|
else if (directWrite && cached)
|
|
{
|
|
detail("Writing articles for %s", *infoFilename);
|
|
}
|
|
else if (directWrite)
|
|
{
|
|
detail("Checking articles for %s", *infoFilename);
|
|
}
|
|
else
|
|
{
|
|
detail("Joining articles for %s", *infoFilename);
|
|
}
|
|
|
|
// Ensure the DstDir is created
|
|
CString errmsg;
|
|
if (!FileSystem::ForceDirectories(nzbDestDir, errmsg))
|
|
{
|
|
m_fileInfo->GetNzbInfo()->PrintMessage(Message::mkError,
|
|
"Could not create directory %s: %s", *nzbDestDir, *errmsg);
|
|
return;
|
|
}
|
|
|
|
CString ofn;
|
|
if (m_fileInfo->GetForceDirectWrite())
|
|
{
|
|
ofn.Format("%s%c%s", *nzbDestDir, PATH_SEPARATOR, *filename);
|
|
}
|
|
else
|
|
{
|
|
ofn = FileSystem::MakeUniqueFilename(nzbDestDir, *filename);
|
|
}
|
|
|
|
DiskFile outfile;
|
|
BString<1024> tmpdestfile("%s.tmp", *ofn);
|
|
|
|
if (!g_Options->GetRawArticle() && !directWrite)
|
|
{
|
|
FileSystem::DeleteFile(tmpdestfile);
|
|
if (!outfile.Open(tmpdestfile, DiskFile::omWrite))
|
|
{
|
|
m_fileInfo->GetNzbInfo()->PrintMessage(Message::mkError,
|
|
"Could not create file %s: %s", *tmpdestfile, *FileSystem::GetLastErrorMessage());
|
|
return;
|
|
}
|
|
}
|
|
else if (directWrite && cached)
|
|
{
|
|
if (!outfile.Open(m_outputFilename, DiskFile::omReadWrite))
|
|
{
|
|
m_fileInfo->GetNzbInfo()->PrintMessage(Message::mkError,
|
|
"Could not open file %s: %s", *m_outputFilename, *FileSystem::GetLastErrorMessage());
|
|
return;
|
|
}
|
|
tmpdestfile = *m_outputFilename;
|
|
}
|
|
else if (g_Options->GetRawArticle())
|
|
{
|
|
FileSystem::DeleteFile(tmpdestfile);
|
|
if (!FileSystem::CreateDirectory(ofn))
|
|
{
|
|
m_fileInfo->GetNzbInfo()->PrintMessage(Message::mkError,
|
|
"Could not create directory %s: %s", *ofn, *FileSystem::GetLastErrorMessage());
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (outfile.Active())
|
|
{
|
|
SetWriteBuffer(outfile, 0);
|
|
}
|
|
|
|
uint32 crc = 0;
|
|
|
|
{
|
|
std::unique_ptr<ArticleCache::FlushGuard> flushGuard;
|
|
if (cached)
|
|
{
|
|
flushGuard = std::make_unique<ArticleCache::FlushGuard>(g_ArticleCache->GuardFlush());
|
|
}
|
|
|
|
CharBuffer buffer;
|
|
bool firstArticle = true;
|
|
|
|
if (!g_Options->GetRawArticle() && !directWrite)
|
|
{
|
|
buffer.Reserve(1024 * 64);
|
|
}
|
|
|
|
for (ArticleInfo* pa : m_fileInfo->GetArticles())
|
|
{
|
|
if (pa->GetStatus() != ArticleInfo::aiFinished)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!g_Options->GetRawArticle() && !directWrite && pa->GetSegmentOffset() > -1 &&
|
|
pa->GetSegmentOffset() > outfile.Position() && outfile.Position() > -1)
|
|
{
|
|
memset(buffer, 0, buffer.Size());
|
|
if (!g_Options->GetSkipWrite())
|
|
{
|
|
while (pa->GetSegmentOffset() > outfile.Position() && outfile.Position() > -1 &&
|
|
outfile.Write(buffer, std::min((int)(pa->GetSegmentOffset() - outfile.Position()), buffer.Size())));
|
|
}
|
|
}
|
|
|
|
if (pa->GetSegmentContent())
|
|
{
|
|
if (!g_Options->GetSkipWrite())
|
|
{
|
|
outfile.Seek(pa->GetSegmentOffset());
|
|
outfile.Write(pa->GetSegmentContent(), pa->GetSegmentSize());
|
|
}
|
|
pa->DiscardSegment();
|
|
}
|
|
else if (!g_Options->GetRawArticle() && !directWrite && !g_Options->GetSkipWrite())
|
|
{
|
|
DiskFile infile;
|
|
if (pa->GetResultFilename() && infile.Open(pa->GetResultFilename(), DiskFile::omRead))
|
|
{
|
|
int cnt = buffer.Size();
|
|
while (cnt == buffer.Size())
|
|
{
|
|
cnt = (int)infile.Read(buffer, buffer.Size());
|
|
outfile.Write(buffer, cnt);
|
|
}
|
|
infile.Close();
|
|
}
|
|
else
|
|
{
|
|
m_fileInfo->SetFailedArticles(m_fileInfo->GetFailedArticles() + 1);
|
|
m_fileInfo->SetSuccessArticles(m_fileInfo->GetSuccessArticles() - 1);
|
|
m_fileInfo->GetNzbInfo()->PrintMessage(Message::mkError,
|
|
"Could not find file %s for %s [%i/%i]",
|
|
pa->GetResultFilename(), *infoFilename, pa->GetPartNumber(),
|
|
(int)m_fileInfo->GetArticles()->size());
|
|
}
|
|
}
|
|
else if (g_Options->GetRawArticle())
|
|
{
|
|
BString<1024> dstFileName("%s%c%03i", *ofn, PATH_SEPARATOR, pa->GetPartNumber());
|
|
if (!FileSystem::MoveFile(pa->GetResultFilename(), dstFileName))
|
|
{
|
|
m_fileInfo->GetNzbInfo()->PrintMessage(Message::mkError,
|
|
"Could not move file %s to %s: %s", pa->GetResultFilename(),
|
|
*dstFileName, *FileSystem::GetLastErrorMessage());
|
|
}
|
|
}
|
|
|
|
if (m_format == Decoder::efYenc)
|
|
{
|
|
crc = firstArticle ? pa->GetCrc() : Crc32::Combine(crc, pa->GetCrc(), pa->GetSegmentSize());
|
|
firstArticle = false;
|
|
}
|
|
}
|
|
|
|
buffer.Clear();
|
|
}
|
|
|
|
if (outfile.Active())
|
|
{
|
|
outfile.Close();
|
|
if (!directWrite && !FileSystem::MoveFile(tmpdestfile, ofn))
|
|
{
|
|
m_fileInfo->GetNzbInfo()->PrintMessage(Message::mkError,
|
|
"Could not move file %s to %s: %s", *tmpdestfile, *ofn,
|
|
*FileSystem::GetLastErrorMessage());
|
|
}
|
|
}
|
|
|
|
if (directWrite)
|
|
{
|
|
if (!FileSystem::SameFilename(m_outputFilename, ofn) &&
|
|
!FileSystem::MoveFile(m_outputFilename, ofn))
|
|
{
|
|
m_fileInfo->GetNzbInfo()->PrintMessage(Message::mkError,
|
|
"Could not move file %s to %s: %s", *m_outputFilename, *ofn,
|
|
*FileSystem::GetLastErrorMessage());
|
|
}
|
|
|
|
// if destination directory was changed delete the old directory (if empty)
|
|
int len = strlen(nzbDestDir);
|
|
if (!(!strncmp(nzbDestDir, m_outputFilename, len) &&
|
|
(m_outputFilename[len] == PATH_SEPARATOR || m_outputFilename[len] == ALT_PATH_SEPARATOR)))
|
|
{
|
|
debug("Checking old dir for: %s", *m_outputFilename);
|
|
BString<1024> oldDestDir;
|
|
oldDestDir.Set(m_outputFilename, (int)(FileSystem::BaseFileName(m_outputFilename) - m_outputFilename));
|
|
if (FileSystem::DirEmpty(oldDestDir))
|
|
{
|
|
debug("Deleting old dir: %s", *oldDestDir);
|
|
FileSystem::RemoveDirectory(oldDestDir);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!directWrite)
|
|
{
|
|
for (ArticleInfo* pa : m_fileInfo->GetArticles())
|
|
{
|
|
if (pa->GetResultFilename())
|
|
{
|
|
FileSystem::DeleteFile(pa->GetResultFilename());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_fileInfo->GetTotalArticles() == m_fileInfo->GetSuccessArticles())
|
|
{
|
|
m_fileInfo->GetNzbInfo()->PrintMessage(Message::mkInfo, "Successfully downloaded %s", *infoFilename);
|
|
}
|
|
else if (m_fileInfo->GetMissedArticles() + m_fileInfo->GetFailedArticles() > 0)
|
|
{
|
|
m_fileInfo->GetNzbInfo()->PrintMessage(Message::mkWarning,
|
|
"%i of %i article downloads failed for \"%s\"",
|
|
m_fileInfo->GetMissedArticles() + m_fileInfo->GetFailedArticles(),
|
|
m_fileInfo->GetTotalArticles(), *infoFilename);
|
|
}
|
|
else
|
|
{
|
|
m_fileInfo->GetNzbInfo()->PrintMessage(Message::mkInfo, "Partially downloaded %s", *infoFilename);
|
|
}
|
|
|
|
{
|
|
GuardedDownloadQueue guard = DownloadQueue::Guard();
|
|
|
|
m_fileInfo->SetCrc(crc);
|
|
m_fileInfo->SetOutputFilename(ofn);
|
|
|
|
if (strcmp(m_fileInfo->GetFilename(), filename))
|
|
{
|
|
// file was renamed during completion, need to move the file
|
|
ofn = FileSystem::MakeUniqueFilename(nzbDestDir, m_fileInfo->GetFilename());
|
|
if (!FileSystem::MoveFile(m_fileInfo->GetOutputFilename(), ofn))
|
|
{
|
|
m_fileInfo->GetNzbInfo()->PrintMessage(Message::mkError,
|
|
"Could not rename file %s to %s: %s", m_fileInfo->GetOutputFilename(),
|
|
*ofn, *FileSystem::GetLastErrorMessage());
|
|
}
|
|
m_fileInfo->SetOutputFilename(ofn);
|
|
}
|
|
|
|
if (strcmp(m_fileInfo->GetNzbInfo()->GetDestDir(), nzbDestDir))
|
|
{
|
|
// destination directory was changed during completion, need to move the file
|
|
MoveCompletedFiles(m_fileInfo->GetNzbInfo(), nzbDestDir);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ArticleWriter::FlushCache()
|
|
{
|
|
detail("Flushing cache for %s", *m_infoName);
|
|
|
|
bool directWrite = g_Options->GetDirectWrite() && m_fileInfo->GetOutputInitialized();
|
|
DiskFile outfile;
|
|
bool needBufFile = false;
|
|
int flushedArticles = 0;
|
|
int64 flushedSize = 0;
|
|
|
|
{
|
|
ArticleCache::FlushGuard flushGuard = g_ArticleCache->GuardFlush();
|
|
|
|
std::vector<ArticleInfo*> cachedArticles;
|
|
|
|
{
|
|
Guard contentGuard = g_ArticleCache->GuardContent();
|
|
|
|
if (m_fileInfo->GetFlushLocked())
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_fileInfo->SetFlushLocked(true);
|
|
|
|
cachedArticles.reserve(m_fileInfo->GetArticles()->size());
|
|
for (ArticleInfo* pa : m_fileInfo->GetArticles())
|
|
{
|
|
if (pa->GetSegmentContent())
|
|
{
|
|
cachedArticles.push_back(pa);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (ArticleInfo* pa : cachedArticles)
|
|
{
|
|
if (m_fileInfo->GetDeleted() && !m_fileInfo->GetNzbInfo()->GetParking())
|
|
{
|
|
// the file was deleted during flushing: stop flushing immediately
|
|
break;
|
|
}
|
|
|
|
if (directWrite && !outfile.Active())
|
|
{
|
|
if (!outfile.Open(m_fileInfo->GetOutputFilename(), DiskFile::omReadWrite))
|
|
{
|
|
m_fileInfo->GetNzbInfo()->PrintMessage(Message::mkError,
|
|
"Could not open file %s: %s", m_fileInfo->GetOutputFilename(),
|
|
*FileSystem::GetLastErrorMessage());
|
|
// prevent multiple error messages
|
|
pa->DiscardSegment();
|
|
flushedArticles++;
|
|
break;
|
|
}
|
|
needBufFile = true;
|
|
}
|
|
|
|
BString<1024> destFile;
|
|
|
|
if (!directWrite)
|
|
{
|
|
destFile.Format("%s.tmp", pa->GetResultFilename());
|
|
if (!outfile.Open(destFile, DiskFile::omWrite))
|
|
{
|
|
m_fileInfo->GetNzbInfo()->PrintMessage(Message::mkError,
|
|
"Could not create file %s: %s", *destFile,
|
|
*FileSystem::GetLastErrorMessage());
|
|
// prevent multiple error messages
|
|
pa->DiscardSegment();
|
|
flushedArticles++;
|
|
break;
|
|
}
|
|
needBufFile = true;
|
|
}
|
|
|
|
if (outfile.Active() && needBufFile)
|
|
{
|
|
SetWriteBuffer(outfile, 0);
|
|
needBufFile = false;
|
|
}
|
|
|
|
if (directWrite)
|
|
{
|
|
outfile.Seek(pa->GetSegmentOffset());
|
|
}
|
|
|
|
if (!g_Options->GetSkipWrite())
|
|
{
|
|
outfile.Write(pa->GetSegmentContent(), pa->GetSegmentSize());
|
|
}
|
|
|
|
flushedSize += pa->GetSegmentSize();
|
|
flushedArticles++;
|
|
|
|
pa->DiscardSegment();
|
|
|
|
if (!directWrite)
|
|
{
|
|
outfile.Close();
|
|
|
|
if (!FileSystem::MoveFile(destFile, pa->GetResultFilename()))
|
|
{
|
|
m_fileInfo->GetNzbInfo()->PrintMessage(Message::mkError,
|
|
"Could not rename file %s to %s: %s", *destFile, pa->GetResultFilename(),
|
|
*FileSystem::GetLastErrorMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
outfile.Close();
|
|
|
|
{
|
|
Guard contentGuard = g_ArticleCache->GuardContent();
|
|
m_fileInfo->SetCachedArticles(m_fileInfo->GetCachedArticles() - flushedArticles);
|
|
m_fileInfo->SetFlushLocked(false);
|
|
}
|
|
}
|
|
|
|
detail("Saved %i articles (%.2f MB) from cache into disk for %s", flushedArticles,
|
|
(float)(flushedSize / 1024.0 / 1024.0), *m_infoName);
|
|
}
|
|
|
|
bool ArticleWriter::MoveCompletedFiles(NzbInfo* nzbInfo, const char* oldDestDir)
|
|
{
|
|
if (nzbInfo->GetCompletedFiles()->empty())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Ensure the DstDir is created
|
|
CString errmsg;
|
|
if (!FileSystem::ForceDirectories(nzbInfo->GetDestDir(), errmsg))
|
|
{
|
|
nzbInfo->PrintMessage(Message::mkError, "Could not create directory %s: %s", nzbInfo->GetDestDir(), *errmsg);
|
|
return false;
|
|
}
|
|
|
|
// move already downloaded files to new destination
|
|
for (CompletedFile& completedFile : nzbInfo->GetCompletedFiles())
|
|
{
|
|
BString<1024> oldFileName("%s%c%s", oldDestDir, PATH_SEPARATOR, completedFile.GetFilename());
|
|
BString<1024> newFileName("%s%c%s", nzbInfo->GetDestDir(), PATH_SEPARATOR, completedFile.GetFilename());
|
|
|
|
// check if file was not moved already
|
|
if (strcmp(oldFileName, newFileName))
|
|
{
|
|
// prevent overwriting of existing files
|
|
newFileName = FileSystem::MakeUniqueFilename(nzbInfo->GetDestDir(), completedFile.GetFilename());
|
|
|
|
detail("Moving file %s to %s", *oldFileName, *newFileName);
|
|
if (!FileSystem::MoveFile(oldFileName, newFileName))
|
|
{
|
|
nzbInfo->PrintMessage(Message::mkError, "Could not move file %s to %s: %s",
|
|
*oldFileName, *newFileName, *FileSystem::GetLastErrorMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
// delete old directory (if empty)
|
|
if (FileSystem::DirEmpty(oldDestDir))
|
|
{
|
|
// check if there are pending writes into directory
|
|
bool pendingWrites = false;
|
|
for (FileInfo* fileInfo : nzbInfo->GetFileList())
|
|
{
|
|
if (!pendingWrites)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (fileInfo->GetActiveDownloads() > 0)
|
|
{
|
|
Guard guard = fileInfo->GuardOutputFile();
|
|
pendingWrites = fileInfo->GetOutputInitialized() && !Util::EmptyStr(fileInfo->GetOutputFilename());
|
|
}
|
|
else
|
|
{
|
|
pendingWrites = fileInfo->GetOutputInitialized() && !Util::EmptyStr(fileInfo->GetOutputFilename());
|
|
}
|
|
}
|
|
|
|
if (!pendingWrites)
|
|
{
|
|
FileSystem::RemoveDirectory(oldDestDir);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
CachedSegmentData ArticleCache::Alloc(int size)
|
|
{
|
|
Guard guard(m_allocMutex);
|
|
|
|
void* p = nullptr;
|
|
|
|
if (m_allocated + size <= (size_t)g_Options->GetArticleCache() * 1024 * 1024)
|
|
{
|
|
p = malloc(size);
|
|
if (p)
|
|
{
|
|
if (!m_allocated && g_Options->GetServerMode() && g_Options->GetContinuePartial())
|
|
{
|
|
g_DiskState->WriteCacheFlag();
|
|
}
|
|
if (!m_allocated)
|
|
{
|
|
// Resume Run(), the notification arrives later, after releasing m_allocMutex
|
|
m_allocCond.NotifyAll();
|
|
}
|
|
m_allocated += size;
|
|
}
|
|
}
|
|
|
|
return CachedSegmentData((char*)p, p ? size : 0);
|
|
}
|
|
|
|
bool ArticleCache::Realloc(CachedSegmentData* segment, int newSize)
|
|
{
|
|
Guard guard(m_allocMutex);
|
|
|
|
void* p = realloc(segment->m_data, newSize);
|
|
if (p)
|
|
{
|
|
m_allocated += newSize - segment->m_size;
|
|
segment->m_size = newSize;
|
|
segment->m_data = (char*)p;
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
void ArticleCache::Free(CachedSegmentData* segment)
|
|
{
|
|
if (segment->m_size)
|
|
{
|
|
free(segment->m_data);
|
|
|
|
Guard guard(m_allocMutex);
|
|
m_allocated -= segment->m_size;
|
|
if (!m_allocated && g_Options->GetServerMode() && g_Options->GetContinuePartial())
|
|
{
|
|
g_DiskState->DeleteCacheFlag();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ArticleCache::Run()
|
|
{
|
|
// automatically flush the cache if it is filled to 90% (only in DirectWrite mode)
|
|
size_t fillThreshold = (size_t)g_Options->GetArticleCache() * 1024 * 1024 / 100 * 90;
|
|
|
|
int resetCounter = 0;
|
|
bool justFlushed = false;
|
|
while (!IsStopped() || m_allocated > 0)
|
|
{
|
|
if ((justFlushed || resetCounter >= 1000 || IsStopped() ||
|
|
(g_Options->GetDirectWrite() && m_allocated >= fillThreshold)) &&
|
|
m_allocated > 0)
|
|
{
|
|
justFlushed = CheckFlush(m_allocated >= fillThreshold);
|
|
resetCounter = 0;
|
|
}
|
|
else if (!m_allocated)
|
|
{
|
|
Guard guard(m_allocMutex);
|
|
m_allocCond.Wait(m_allocMutex, [&]{ return IsStopped() || m_allocated > 0; });
|
|
resetCounter = 0;
|
|
}
|
|
else
|
|
{
|
|
Util::Sleep(5);
|
|
resetCounter += 5;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ArticleCache::Stop()
|
|
{
|
|
Thread::Stop();
|
|
|
|
// Resume Run() to exit it
|
|
Guard guard(m_allocMutex);
|
|
m_allocCond.NotifyAll();
|
|
}
|
|
|
|
bool ArticleCache::CheckFlush(bool flushEverything)
|
|
{
|
|
debug("Checking cache, Allocated: %i, FlushEverything: %i", (int)m_allocated, (int)flushEverything);
|
|
|
|
BString<1024> infoName;
|
|
|
|
{
|
|
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
|
|
for (NzbInfo* nzbInfo : downloadQueue->GetQueue())
|
|
{
|
|
if (m_fileInfo)
|
|
{
|
|
break;
|
|
}
|
|
|
|
for (FileInfo* fileInfo : nzbInfo->GetFileList())
|
|
{
|
|
if (fileInfo->GetCachedArticles() > 0 && (fileInfo->GetActiveDownloads() == 0 || flushEverything))
|
|
{
|
|
m_fileInfo = fileInfo;
|
|
infoName.Format("%s%c%s", m_fileInfo->GetNzbInfo()->GetName(), PATH_SEPARATOR, m_fileInfo->GetFilename());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_fileInfo)
|
|
{
|
|
ArticleWriter articleWriter;
|
|
articleWriter.SetFileInfo(m_fileInfo);
|
|
articleWriter.SetInfoName(infoName);
|
|
articleWriter.FlushCache();
|
|
m_fileInfo = nullptr;
|
|
return true;
|
|
}
|
|
|
|
debug("Checking cache... nothing to flush");
|
|
|
|
return false;
|
|
}
|
|
|
|
ArticleCache::FlushGuard::FlushGuard(Mutex& mutex) : m_guard(&mutex)
|
|
{
|
|
g_ArticleCache->m_flushing = true;
|
|
}
|
|
|
|
ArticleCache::FlushGuard::~FlushGuard()
|
|
{
|
|
if (m_guard)
|
|
{
|
|
g_ArticleCache->m_flushing = false;
|
|
}
|
|
}
|