mirror of
https://github.com/nzbget/nzbget.git
synced 2026-04-19 20:36:52 -04:00
473 lines
11 KiB
C++
473 lines
11 KiB
C++
/*
|
|
* This file is part of nzbget
|
|
*
|
|
* Copyright (C) 2013-2015 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, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* $Revision$
|
|
* $Date$
|
|
*
|
|
*/
|
|
|
|
|
|
#include "nzbget.h"
|
|
|
|
#ifndef DISABLE_PARCHECK
|
|
|
|
#include "par2cmdline.h"
|
|
#include "par2repairer.h"
|
|
#include "md5.h"
|
|
|
|
#include "ParRenamer.h"
|
|
#include "ParParser.h"
|
|
#include "Log.h"
|
|
#include "Options.h"
|
|
#include "Util.h"
|
|
|
|
class ParRenamerRepairer : public Par2Repairer
|
|
{
|
|
public:
|
|
friend class ParRenamer;
|
|
};
|
|
|
|
ParRenamer::FileHash::FileHash(const char* filename, const char* hash)
|
|
{
|
|
m_filename = strdup(filename);
|
|
m_hash = strdup(hash);
|
|
m_fileExists = false;
|
|
}
|
|
|
|
ParRenamer::FileHash::~FileHash()
|
|
{
|
|
free(m_filename);
|
|
free(m_hash);
|
|
}
|
|
|
|
ParRenamer::ParRenamer()
|
|
{
|
|
debug("Creating ParRenamer");
|
|
|
|
m_status = psFailed;
|
|
m_destDir = NULL;
|
|
m_infoName = NULL;
|
|
m_progressLabel = (char*)malloc(1024);
|
|
m_stageProgress = 0;
|
|
m_cancelled = false;
|
|
m_hasMissedFiles = false;
|
|
m_detectMissing = false;
|
|
}
|
|
|
|
ParRenamer::~ParRenamer()
|
|
{
|
|
debug("Destroying ParRenamer");
|
|
|
|
free(m_destDir);
|
|
free(m_infoName);
|
|
free(m_progressLabel);
|
|
|
|
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 * destDir)
|
|
{
|
|
free(m_destDir);
|
|
m_destDir = strdup(destDir);
|
|
}
|
|
|
|
void ParRenamer::SetInfoName(const char * infoName)
|
|
{
|
|
free(m_infoName);
|
|
m_infoName = strdup(infoName);
|
|
}
|
|
|
|
void ParRenamer::Cancel()
|
|
{
|
|
m_cancelled = true;
|
|
}
|
|
|
|
void ParRenamer::Run()
|
|
{
|
|
Cleanup();
|
|
m_cancelled = false;
|
|
m_fileCount = 0;
|
|
m_curFile = 0;
|
|
m_renamedCount = 0;
|
|
m_hasMissedFiles = false;
|
|
m_status = psFailed;
|
|
|
|
snprintf(m_progressLabel, 1024, "Checking renamed files for %s", m_infoName);
|
|
m_progressLabel[1024-1] = '\0';
|
|
m_stageProgress = 0;
|
|
UpdateProgress();
|
|
|
|
BuildDirList(m_destDir);
|
|
|
|
for (DirList::iterator it = m_dirList.begin(); it != m_dirList.end(); it++)
|
|
{
|
|
char* destDir = *it;
|
|
debug("Checking %s", destDir);
|
|
ClearHashList();
|
|
LoadParFiles(destDir);
|
|
|
|
if (m_fileHashList.empty())
|
|
{
|
|
int savedCurFile = m_curFile;
|
|
CheckFiles(destDir, true);
|
|
m_curFile = savedCurFile; // restore progress indicator
|
|
LoadParFiles(destDir);
|
|
}
|
|
|
|
CheckFiles(destDir, false);
|
|
|
|
if (m_detectMissing)
|
|
{
|
|
CheckMissing();
|
|
}
|
|
}
|
|
|
|
if (m_cancelled)
|
|
{
|
|
PrintMessage(Message::mkWarning, "Renaming cancelled for %s", m_infoName);
|
|
}
|
|
else if (m_renamedCount > 0)
|
|
{
|
|
PrintMessage(Message::mkInfo, "Successfully renamed %i file(s) for %s", m_renamedCount, m_infoName);
|
|
m_status = psSuccess;
|
|
}
|
|
else
|
|
{
|
|
PrintMessage(Message::mkInfo, "No renamed files found for %s", m_infoName);
|
|
}
|
|
|
|
Cleanup();
|
|
Completed();
|
|
}
|
|
|
|
void ParRenamer::BuildDirList(const char* destDir)
|
|
{
|
|
m_dirList.push_back(strdup(destDir));
|
|
|
|
char* fullFilename = (char*)malloc(1024);
|
|
DirBrowser* dirBrowser = new DirBrowser(destDir);
|
|
|
|
while (const char* filename = dirBrowser->Next())
|
|
{
|
|
if (strcmp(filename, ".") && strcmp(filename, "..") && !m_cancelled)
|
|
{
|
|
snprintf(fullFilename, 1024, "%s%c%s", destDir, PATH_SEPARATOR, filename);
|
|
fullFilename[1024-1] = '\0';
|
|
|
|
if (Util::DirectoryExists(fullFilename))
|
|
{
|
|
BuildDirList(fullFilename);
|
|
}
|
|
else
|
|
{
|
|
m_fileCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
free(fullFilename);
|
|
delete dirBrowser;
|
|
}
|
|
|
|
void ParRenamer::LoadParFiles(const char* destDir)
|
|
{
|
|
ParParser::ParFileList parFileList;
|
|
ParParser::FindMainPars(destDir, &parFileList);
|
|
|
|
for (ParParser::ParFileList::iterator it = parFileList.begin(); it != parFileList.end(); it++)
|
|
{
|
|
char* parFilename = *it;
|
|
|
|
char fullParFilename[1024];
|
|
snprintf(fullParFilename, 1024, "%s%c%s", destDir, PATH_SEPARATOR, parFilename);
|
|
fullParFilename[1024-1] = '\0';
|
|
|
|
LoadParFile(fullParFilename);
|
|
|
|
free(*it);
|
|
}
|
|
}
|
|
|
|
void ParRenamer::LoadParFile(const char* parFilename)
|
|
{
|
|
ParRenamerRepairer* repairer = new ParRenamerRepairer();
|
|
|
|
if (!repairer->LoadPacketsFromFile(parFilename))
|
|
{
|
|
PrintMessage(Message::mkWarning, "Could not load par2-file %s", parFilename);
|
|
delete repairer;
|
|
return;
|
|
}
|
|
|
|
for (map<MD5Hash, Par2RepairerSourceFile*>::iterator it = repairer->sourcefilemap.begin(); it != repairer->sourcefilemap.end(); it++)
|
|
{
|
|
if (m_cancelled)
|
|
{
|
|
break;
|
|
}
|
|
|
|
Par2RepairerSourceFile* sourceFile = (*it).second;
|
|
if (!sourceFile || !sourceFile->GetDescriptionPacket())
|
|
{
|
|
PrintMessage(Message::mkWarning, "Damaged par2-file detected: %s", parFilename);
|
|
continue;
|
|
}
|
|
m_fileHashList.push_back(new FileHash(sourceFile->GetDescriptionPacket()->FileName().c_str(),
|
|
sourceFile->GetDescriptionPacket()->Hash16k().print().c_str()));
|
|
RegisterParredFile(sourceFile->GetDescriptionPacket()->FileName().c_str());
|
|
}
|
|
|
|
delete repairer;
|
|
}
|
|
|
|
void ParRenamer::CheckFiles(const char* destDir, bool renamePars)
|
|
{
|
|
DirBrowser dir(destDir);
|
|
while (const char* filename = dir.Next())
|
|
{
|
|
if (strcmp(filename, ".") && strcmp(filename, "..") && !m_cancelled)
|
|
{
|
|
char fullFilename[1024];
|
|
snprintf(fullFilename, 1024, "%s%c%s", destDir, PATH_SEPARATOR, filename);
|
|
fullFilename[1024-1] = '\0';
|
|
|
|
if (!Util::DirectoryExists(fullFilename))
|
|
{
|
|
snprintf(m_progressLabel, 1024, "Checking file %s", filename);
|
|
m_progressLabel[1024-1] = '\0';
|
|
m_stageProgress = m_curFile * 1000 / m_fileCount;
|
|
UpdateProgress();
|
|
m_curFile++;
|
|
|
|
if (renamePars)
|
|
{
|
|
CheckParFile(destDir, fullFilename);
|
|
}
|
|
else
|
|
{
|
|
CheckRegularFile(destDir, fullFilename);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ParRenamer::CheckMissing()
|
|
{
|
|
for (FileHashList::iterator it = m_fileHashList.begin(); it != m_fileHashList.end(); it++)
|
|
{
|
|
FileHash* fileHash = *it;
|
|
if (!fileHash->GetFileExists())
|
|
{
|
|
if (Util::MatchFileExt(fileHash->GetFilename(), g_Options->GetParIgnoreExt(), ",;") ||
|
|
Util::MatchFileExt(fileHash->GetFilename(), g_Options->GetExtCleanupDisk(), ",;"))
|
|
{
|
|
PrintMessage(Message::mkInfo, "File %s is missing, ignoring", fileHash->GetFilename());
|
|
}
|
|
else
|
|
{
|
|
PrintMessage(Message::mkInfo, "File %s is missing", fileHash->GetFilename());
|
|
m_hasMissedFiles = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ParRenamer::IsSplittedFragment(const char* filename, const char* correctName)
|
|
{
|
|
bool splittedFragement = false;
|
|
const char* diskBasename = Util::BaseFileName(filename);
|
|
const char* extension = strrchr(diskBasename, '.');
|
|
int baseLen = strlen(correctName);
|
|
if (extension && !strncasecmp(diskBasename, correctName, baseLen))
|
|
{
|
|
const char* p = diskBasename + baseLen;
|
|
if (*p == '.')
|
|
{
|
|
for (p++; *p && strchr("0123456789", *p); p++) ;
|
|
splittedFragement = !*p;
|
|
splittedFragement = splittedFragement && atoi(diskBasename + baseLen + 1) <= 1; // .000 or .001
|
|
}
|
|
}
|
|
|
|
return splittedFragement;
|
|
}
|
|
|
|
void ParRenamer::CheckRegularFile(const char* destDir, const char* filename)
|
|
{
|
|
debug("Computing hash for %s", filename);
|
|
|
|
const int blockSize = 16*1024;
|
|
|
|
FILE* file = fopen(filename, FOPEN_RB);
|
|
if (!file)
|
|
{
|
|
PrintMessage(Message::mkError, "Could not open file %s", filename);
|
|
return;
|
|
}
|
|
|
|
// load first 16K of the file into buffer
|
|
|
|
void* buffer = malloc(blockSize);
|
|
|
|
int readBytes = fread(buffer, 1, blockSize, file);
|
|
int error = ferror(file);
|
|
if (readBytes != blockSize && error)
|
|
{
|
|
PrintMessage(Message::mkError, "Could not read file %s", filename);
|
|
return;
|
|
}
|
|
|
|
fclose(file);
|
|
|
|
MD5Hash hash16k;
|
|
MD5Context context;
|
|
context.Update(buffer, readBytes);
|
|
context.Final(hash16k);
|
|
|
|
free(buffer);
|
|
|
|
debug("file: %s; hash16k: %s", Util::BaseFileName(filename), hash16k.print().c_str());
|
|
|
|
for (FileHashList::iterator it = m_fileHashList.begin(); it != m_fileHashList.end(); it++)
|
|
{
|
|
FileHash* fileHash = *it;
|
|
if (!strcmp(fileHash->GetHash(), hash16k.print().c_str()))
|
|
{
|
|
debug("Found correct filename: %s", fileHash->GetFilename());
|
|
fileHash->SetFileExists(true);
|
|
|
|
char dstFilename[1024];
|
|
snprintf(dstFilename, 1024, "%s%c%s", destDir, PATH_SEPARATOR, fileHash->GetFilename());
|
|
dstFilename[1024-1] = '\0';
|
|
|
|
if (!Util::FileExists(dstFilename) && !IsSplittedFragment(filename, fileHash->GetFilename()))
|
|
{
|
|
RenameFile(filename, dstFilename);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* For files not having par2-extensions: checks if the file is a par2-file and renames
|
|
* it according to its set-id.
|
|
*/
|
|
void ParRenamer::CheckParFile(const char* destDir, const char* filename)
|
|
{
|
|
debug("Checking par2-header for %s", filename);
|
|
|
|
const char* basename = Util::BaseFileName(filename);
|
|
const char* extension = strrchr(basename, '.');
|
|
if (extension && !strcasecmp(extension, ".par2"))
|
|
{
|
|
// do not process files already having par2-extension
|
|
return;
|
|
}
|
|
|
|
FILE* file = fopen(filename, FOPEN_RB);
|
|
if (!file)
|
|
{
|
|
PrintMessage(Message::mkError, "Could not open file %s", filename);
|
|
return;
|
|
}
|
|
|
|
// load par2-header
|
|
PACKET_HEADER header;
|
|
|
|
int readBytes = fread(&header, 1, sizeof(header), file);
|
|
int error = ferror(file);
|
|
if (readBytes != sizeof(header) && error)
|
|
{
|
|
PrintMessage(Message::mkError, "Could not read file %s", filename);
|
|
return;
|
|
}
|
|
|
|
fclose(file);
|
|
|
|
// Check the packet header
|
|
if (packet_magic != header.magic || // not par2-file
|
|
sizeof(PACKET_HEADER) > header.length || // packet length is too small
|
|
0 != (header.length & 3) || // packet length is not a multiple of 4
|
|
Util::FileSize(filename) < (int)header.length) // packet would extend beyond the end of the file
|
|
{
|
|
// not par2-file or damaged header, ignoring the file
|
|
return;
|
|
}
|
|
|
|
char setId[33];
|
|
strncpy(setId, header.setid.print().c_str(), sizeof(setId));
|
|
setId[33-1] = '\0';
|
|
for (char* p = setId; *p; p++) *p = tolower(*p); // convert string to lowercase
|
|
|
|
debug("Renaming: %s; setid: %s", Util::BaseFileName(filename), setId);
|
|
|
|
char destFileName[1024];
|
|
int num = 1;
|
|
while (num == 1 || Util::FileExists(destFileName))
|
|
{
|
|
snprintf(destFileName, 1024, "%s%c%s.vol%03i+01.PAR2", destDir, PATH_SEPARATOR, setId, num);
|
|
destFileName[1024-1] = '\0';
|
|
num++;
|
|
}
|
|
|
|
RenameFile(filename, destFileName);
|
|
}
|
|
|
|
void ParRenamer::RenameFile(const char* srcFilename, const char* destFileName)
|
|
{
|
|
PrintMessage(Message::mkInfo, "Renaming %s to %s", Util::BaseFileName(srcFilename), Util::BaseFileName(destFileName));
|
|
if (!Util::MoveFile(srcFilename, destFileName))
|
|
{
|
|
char errBuf[256];
|
|
PrintMessage(Message::mkError, "Could not rename %s to %s: %s", srcFilename, destFileName,
|
|
Util::GetLastErrorMessage(errBuf, sizeof(errBuf)));
|
|
return;
|
|
}
|
|
|
|
m_renamedCount++;
|
|
|
|
// notify about new file name
|
|
RegisterRenamedFile(Util::BaseFileName(srcFilename), Util::BaseFileName(destFileName));
|
|
}
|
|
|
|
#endif
|