Files
nzbget/daemon/postprocess/RarRenamer.cpp
2019-01-06 14:43:41 +01:00

394 lines
9.5 KiB
C++

/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2016-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "nzbget.h"
#include "RarRenamer.h"
#include "Log.h"
#include "Util.h"
#include "FileSystem.h"
void RarRenamer::Execute()
{
m_progressLabel.Format("Checking renamed rar-files for %s", *m_infoName);
m_stageProgress = 0;
UpdateProgress();
BuildDirList(m_destDir);
for (CString& destDir : m_dirList)
{
debug("Checking %s", *destDir);
CheckFiles(destDir);
}
}
void RarRenamer::BuildDirList(const char* destDir)
{
m_dirList.push_back(destDir);
DirBrowser dirBrowser(destDir);
while (const char* filename = dirBrowser.Next())
{
if (!IsStopped())
{
BString<1024> fullFilename("%s%c%s", destDir, PATH_SEPARATOR, filename);
if (FileSystem::DirectoryExists(fullFilename))
{
BuildDirList(fullFilename);
}
else
{
m_fileCount++;
}
}
}
}
void RarRenamer::CheckFiles(const char* destDir)
{
DirBrowser dir(destDir);
while (const char* filename = dir.Next())
{
if (!IsStopped())
{
BString<1024> fullFilename("%s%c%s", destDir, PATH_SEPARATOR, filename);
if (!FileSystem::DirectoryExists(fullFilename))
{
m_progressLabel.Format("Checking file %s", filename);
m_stageProgress = m_fileCount > 0 ? m_curFile * 1000 / m_fileCount : 1000;
UpdateProgress();
m_curFile++;
CheckOneFile(fullFilename);
}
}
}
if (!m_volumes.empty())
{
RenameFiles(destDir);
}
}
void RarRenamer::CheckOneFile(const char* filename)
{
if (m_ignoreExt && Util::MatchFileExt(FileSystem::BaseFileName(filename), m_ignoreExt, ",;"))
{
return;
}
RarVolume volume(filename);
volume.SetPassword(m_password);
if (volume.Read())
{
m_volumes.push_back(std::move(volume));
}
}
void RarRenamer::RenameFile(const char* srcFilename, const char* destFileName)
{
PrintMessage(Message::mkInfo, "Renaming %s to %s", FileSystem::BaseFileName(srcFilename), FileSystem::BaseFileName(destFileName));
if (!FileSystem::MoveFile(srcFilename, destFileName))
{
PrintMessage(Message::mkError, "Could not rename %s to %s: %s", srcFilename, destFileName,
*FileSystem::GetLastErrorMessage());
return;
}
m_renamedCount++;
// notify about new file name
RegisterRenamedFile(FileSystem::BaseFileName(srcFilename), FileSystem::BaseFileName(destFileName));
}
void RarRenamer::RenameFiles(const char* destDir)
{
MakeSets();
for (RarVolumeSet& set : m_sets)
{
if (!IsSetProperlyNamed(set))
{
RarFile* mainFile = FindMainFile(set);
BString<1024> mainBasename = FileSystem::BaseFileName(mainFile->GetFilename());
char* ext = strrchr(mainBasename, '.');
// strip extension if its length is 3 chars
if (ext && strlen(ext) == 4)
{
*ext = '\0';
}
BString<1024> newBasename = *mainBasename;
int num = 0;
bool willOverwrite = true;
while (willOverwrite)
{
if (num++)
{
newBasename.Format("%s-%i", *mainBasename, num);
}
for (RarVolume* volume : set)
{
CString destfilename = GenNewVolumeFilename(destDir, newBasename, volume);
willOverwrite = strcmp(volume->GetFilename(), destfilename) && FileSystem::FileExists(destfilename);
if (willOverwrite)
{
break;
}
}
}
for (RarVolume* volume : set)
{
CString destfilename = GenNewVolumeFilename(destDir, newBasename, volume);
if (strcmp(volume->GetFilename(), destfilename))
{
RenameFile(volume->GetFilename(), destfilename);
}
}
}
}
}
CString RarRenamer::GenNewVolumeFilename(const char* destDir, const char* newBasename, RarVolume* volume)
{
CString extension = volume->GetNewNaming() ? GenNewExtension(volume->GetVolumeNo()) : GenOldExtension(volume->GetVolumeNo());
return CString::FormatStr("%s%c%s.%s", destDir, PATH_SEPARATOR, newBasename, *extension);
}
CString RarRenamer::GenNewExtension(int volumeNo)
{
return CString::FormatStr("part%04i.rar", volumeNo + 1);
}
CString RarRenamer::GenOldExtension(int volumeNo)
{
if (volumeNo == 0)
{
return "rar";
}
else
{
unsigned char ch = 'r' + (volumeNo - 1) / 100;
return CString::FormatStr("%c%02d", ch, (volumeNo - 1) % 100);
}
}
void RarRenamer::MakeSets()
{
m_sets.clear();
// find first volumes and create initial incomplete sets
for (RarVolume& volume : m_volumes)
{
if (!volume.GetFiles()->empty() && volume.GetVolumeNo() == 0 &&
!volume.GetFiles()->front().GetSplitBefore())
{
m_sets.push_back({&volume});
}
}
// complete sets, discard sets which cannot be completed
m_sets.erase(std::remove_if(m_sets.begin(), m_sets.end(),
[volumes = &m_volumes](RarVolumeSet& set)
{
debug("*** Building set %s", FileSystem::BaseFileName(set[0]->GetFilename()));
bool found = true;
while (found)
{
found = false;
std::vector<RarVolume*> candidates;
RarVolume* lastVolume = set.back();
for (RarVolume& volume : *volumes)
{
if (!volume.GetFiles()->empty() && volume.GetMultiVolume() &&
volume.GetVolumeNo() == lastVolume->GetVolumeNo() + 1 &&
volume.GetVersion() == lastVolume->GetVersion() &&
lastVolume->GetHasNextVolume() &&
((volume.GetFiles()->front().GetSplitBefore() &&
lastVolume->GetFiles()->back().GetSplitAfter() &&
!strcmp(volume.GetFiles()->front().GetFilename(), lastVolume->GetFiles()->back().GetFilename())) ||
(!volume.GetFiles()->front().GetSplitBefore() && !lastVolume->GetFiles()->back().GetSplitAfter())))
{
debug(" found candidate %s", FileSystem::BaseFileName(volume.GetFilename()));
candidates.push_back(&volume);
}
}
RarVolume* nextVolume = nullptr;
if (candidates.size() > 1)
{
for (RarVolume* volume : candidates)
{
if (SameArchiveName(FileSystem::BaseFileName(set[0]->GetFilename()),
FileSystem::BaseFileName(volume->GetFilename()), set[0]->GetNewNaming()))
{
nextVolume = volume;
break;
}
}
}
if (!nextVolume && !candidates.empty())
{
nextVolume = candidates.front();
}
if (nextVolume)
{
debug(" adding %s", FileSystem::BaseFileName(nextVolume->GetFilename()));
set.push_back(nextVolume);
found = true;
}
}
RarVolume* lastVolume = set.back();
bool completed = !lastVolume->GetHasNextVolume() &&
(lastVolume->GetFiles()->empty() || !lastVolume->GetFiles()->back().GetSplitAfter());
return !completed;
}),
m_sets.end());
#ifdef DEBUG
// debug log
for (RarVolumeSet& set : m_sets)
{
debug("*** Set ***");
for (RarVolume* volume : set)
{
debug(" %s", FileSystem::BaseFileName(volume->GetFilename()));
}
}
#endif
}
bool RarRenamer::SameArchiveName(const char* filename1, const char* filename2, bool newNaming)
{
if (strlen(filename1) != strlen(filename2))
{
return false;
}
const char* ext1 = strrchr(filename1, '.');
const char* ext2 = strrchr(filename2, '.');
if (!(ext1 && ext2 && strlen(ext1) == strlen(ext2)))
{
return false;
}
if (newNaming)
{
if (ext1 == filename1 || ext2 == filename2)
{
return false;
}
BString<1024> name1, name2;
name1.Set(filename1, (int)(ext1 - filename1));
name2.Set(filename2, (int)(ext2 - filename2));
ext1 = strrchr(name1, '.');
ext2 = strrchr(name2, '.');
return ext1 && ext2 && strlen(ext1) == strlen(ext2) &&
!strncmp(ext1, ".part", 5) && !strncmp(ext2, ".part", 5) &&
!strncmp(name1, name2, ext1 - name1);
}
else
{
return !strncmp(filename1, filename2, ext1 - filename1);
}
}
bool RarRenamer::IsSetProperlyNamed(RarVolumeSet& set)
{
RegEx regExPart(".*.part([0-9]+)\\.rar$");
const char* setBasename = FileSystem::BaseFileName(set[0]->GetFilename());
int setPartLen = 0;
for (RarVolume* volume : set)
{
const char* filename = FileSystem::BaseFileName(volume->GetFilename());
if (strlen(setBasename) != strlen(filename))
{
return false;
}
if (volume->GetNewNaming())
{
if (!regExPart.Match(filename))
{
return false;
}
BString<1024> partNo(filename + regExPart.GetMatchStart(1), regExPart.GetMatchLen(1));
if (setPartLen == 0)
{
setPartLen = partNo.Length();
}
bool ok = (uint32)atoi(partNo) == volume->GetVolumeNo() + 1 &&
partNo.Length() == setPartLen &&
!strncmp(setBasename, filename, regExPart.GetMatchStart(1));
if (!ok)
{
return false;
}
}
else
{
const char* ext = strrchr(filename, '.');
if (!ext || strcmp(ext + 1, GenOldExtension(volume->GetVolumeNo())) ||
strncmp(setBasename, filename, ext - filename))
{
return false;
}
}
}
return true;
}
RarFile* RarRenamer::FindMainFile(RarVolumeSet& set)
{
std::deque<RarFile*> allFiles;
for (RarVolume* volume : set)
{
for (RarFile& file : *volume->GetFiles())
{
allFiles.push_back(&file);
}
}
std::deque<RarFile*>::iterator it = std::max_element(allFiles.begin(), allFiles.end(),
[](RarFile* file1, RarFile* file2)
{
return file1->GetSize() < file2->GetSize();
});
return *it;
}