/* * This file is part of nzbget. See . * * Copyright (C) 2013-2018 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, see . */ #include "nzbget.h" #include "Unpack.h" #include "Log.h" #include "Util.h" #include "FileSystem.h" #include "ParParser.h" #include "Options.h" bool UnpackController::FileList::Exists(const char* filename) { return std::find(begin(), end(), filename) != end(); } bool UnpackController::ParamList::Exists(const char* param) { return std::find(begin(), end(), param) != end(); } void UnpackController::StartJob(PostInfo* postInfo) { UnpackController* unpackController = new UnpackController(); unpackController->m_postInfo = postInfo; unpackController->SetAutoDestroy(false); postInfo->SetPostThread(unpackController); unpackController->Start(); } void UnpackController::Run() { time_t start = Util::CurrentTime(); m_unpackOk = true; bool unpack; { GuardedDownloadQueue guard = DownloadQueue::Guard(); m_destDir = m_postInfo->GetNzbInfo()->GetDestDir(); m_finalDir = m_postInfo->GetNzbInfo()->GetFinalDir(); m_name = m_postInfo->GetNzbInfo()->GetName(); NzbParameter* parameter = m_postInfo->GetNzbInfo()->GetParameters()->Find("*Unpack:"); unpack = !(parameter && !strcasecmp(parameter->GetValue(), "no")); parameter = m_postInfo->GetNzbInfo()->GetParameters()->Find("*Unpack:Password"); if (parameter) { m_password = parameter->GetValue(); } } m_infoName.Format("unpack for %s", *m_name); m_infoNameUp.Format("Unpack for %s", *m_name); // first letter in upper case m_hasParFiles = ParParser::FindMainPars(m_destDir, nullptr); if (unpack) { CheckArchiveFiles(); } SetInfoName(m_infoName); SetWorkingDir(m_destDir); bool hasFiles = m_hasRarFiles || m_hasSevenZipFiles || m_hasSevenZipMultiFiles || m_hasSplittedFiles; if (m_postInfo->GetUnpackTried() && !m_postInfo->GetParRepaired() && (!m_password.Empty() || Util::EmptyStr(g_Options->GetUnpackPassFile()) || m_postInfo->GetPassListTried())) { PrintMessage(Message::mkInfo, "Second unpack attempt skipped for %s due to par-check not repaired anything", *m_name); PrintMessage(Message::mkError, m_postInfo->GetLastUnpackStatus() == (int)NzbInfo::usPassword ? "%s failed: checksum error in the encrypted file. Corrupt file or wrong password." : "%s failed.", *m_infoNameUp); m_postInfo->GetNzbInfo()->SetUnpackStatus((NzbInfo::EPostUnpackStatus)m_postInfo->GetLastUnpackStatus()); } else if (unpack && hasFiles) { PrintMessage(Message::mkInfo, "Unpacking %s", *m_name); CreateUnpackDir(); if (m_hasRarFiles) { if (m_hasNotUnpackedRarFiles || m_unpackDirCreated) { if (m_postInfo->GetNzbInfo()->GetDirectUnpackStatus() == NzbInfo::nsSuccess) { if (m_unpackDirCreated) { PrintMessage(Message::mkWarning, "Could not find files unpacked by direct unpack, unpacking all files again"); } else { PrintMessage(Message::mkInfo, "Found archive files not processed by direct unpack, unpacking all files again"); } } // Discard info about extracted archives to prevent reusing on next unpack attempt m_postInfo->GetExtractedArchives()->clear(); UnpackArchives(upUnrar, false); } else { PrintMessage(Message::mkInfo, "Using directly unpacked files"); } } if (m_hasSevenZipFiles && m_unpackOk) { UnpackArchives(upSevenZip, false); } if (m_hasSevenZipMultiFiles && m_unpackOk) { UnpackArchives(upSevenZip, true); } if (m_hasSplittedFiles && m_unpackOk) { JoinSplittedFiles(); } Completed(); m_joinedFiles.clear(); } else if (unpack) { #ifndef DISABLE_PARCHECK if (m_postInfo->GetNzbInfo()->GetParStatus() <= NzbInfo::psSkipped && m_postInfo->GetNzbInfo()->GetParRenameStatus() <= NzbInfo::rsSkipped && m_hasParFiles) { PrintMessage(Message::mkInfo, "Nothing to unpack for %s", *m_name); RequestParCheck(false); } else #endif if (m_hasRenamedArchiveFiles) { PrintMessage(Message::mkError, "Could not unpack %s due to renamed archive files", *m_name); m_postInfo->GetNzbInfo()->SetUnpackStatus(NzbInfo::usFailure); } else { PrintMessage(Message::mkInfo, "Nothing to unpack for %s", *m_name); m_postInfo->GetNzbInfo()->SetUnpackStatus(NzbInfo::usSkipped); } } else { PrintMessage(Message::mkInfo, "Unpack for %s skipped", *m_name); m_postInfo->GetNzbInfo()->SetUnpackStatus(NzbInfo::usSkipped); } int unpackSec = (int)(Util::CurrentTime() - start); m_postInfo->GetNzbInfo()->SetUnpackSec(m_postInfo->GetNzbInfo()->GetUnpackSec() + unpackSec); m_postInfo->SetWorking(false); } void UnpackController::UnpackArchives(EUnpacker unpacker, bool multiVolumes) { if (!m_postInfo->GetUnpackTried() || m_postInfo->GetParRepaired()) { ExecuteUnpack(unpacker, m_password, multiVolumes); if (!m_unpackOk && m_hasParFiles && !m_unpackPasswordError && m_postInfo->GetNzbInfo()->GetParStatus() <= NzbInfo::psSkipped) { debug("For rar4- or 7z-archives try par-check first, before trying password file"); return; } } else { m_unpackOk = false; m_unpackDecryptError = m_postInfo->GetLastUnpackStatus() == (int)NzbInfo::usPassword; } if (!m_unpackOk && !m_unpackStartError && !m_unpackSpaceError && (m_unpackDecryptError || m_unpackPasswordError) && (!GetTerminated() || m_autoTerminated) && m_password.Empty() && !Util::EmptyStr(g_Options->GetUnpackPassFile())) { DiskFile infile; if (!infile.Open(g_Options->GetUnpackPassFile(), DiskFile::omRead)) { PrintMessage(Message::mkError, "Could not open file %s", g_Options->GetUnpackPassFile()); return; } char password[512]; while (!m_unpackOk && !m_unpackStartError && !m_unpackSpaceError && (m_unpackDecryptError || m_unpackPasswordError) && infile.ReadLine(password, sizeof(password) - 1)) { debug("Password line: %s", password); // trim trailing and char* end = password + strlen(password) - 1; while (end >= password && (*end == '\n' || *end == '\r')) *end-- = '\0'; if (!Util::EmptyStr(password)) { if (IsStopped() && m_autoTerminated) { ScriptController::Resume(); Thread::Resume(); } m_unpackDecryptError = false; m_unpackPasswordError = false; m_autoTerminated = false; PrintMessage(Message::mkInfo, "Trying password %s for %s", password, *m_name); ExecuteUnpack(unpacker, password, multiVolumes); } } infile.Close(); m_passListTried = !IsStopped() || m_autoTerminated; } } void UnpackController::ExecuteUnpack(EUnpacker unpacker, const char* password, bool multiVolumes) { switch (unpacker) { case upUnrar: ExecuteUnrar(password); break; case upSevenZip: ExecuteSevenZip(password, multiVolumes); break; } } void UnpackController::ExecuteUnrar(const char* password) { // Format: // unrar x -y -p- -o+ *.rar ./_unpack/ ParamList params; if (!PrepareCmdParams(g_Options->GetUnrarCmd(), ¶ms, "unrar")) { return; } if (!params.Exists("x") && !params.Exists("e")) { params.emplace_back("x"); } params.emplace_back("-y"); if (!Util::EmptyStr(password)) { params.push_back(CString::FormatStr("-p%s", password)); } else { params.emplace_back("-p-"); } if (!params.Exists("-o+") && !params.Exists("-o-")) { params.emplace_back("-o+"); } params.emplace_back("*.rar"); m_unpackExtendedDir = FileSystem::MakeExtendedPath(m_unpackDir, true); params.push_back(*BString<1024>("%s%c", *m_unpackExtendedDir, PATH_SEPARATOR)); SetArgs(std::move(params)); SetLogPrefix("Unrar"); ResetEnv(); m_allOkMessageReceived = false; m_unpacker = upUnrar; SetProgressLabel(""); int exitCode = Execute(); SetLogPrefix(nullptr); SetProgressLabel(""); m_unpackOk = exitCode == 0 && m_allOkMessageReceived && !GetTerminated(); m_unpackStartError = exitCode == -1 && !m_autoTerminated; m_unpackSpaceError = exitCode == 5; m_unpackPasswordError |= exitCode == 11; // only for rar5-archives if (!m_unpackOk && exitCode > 0) { PrintMessage(Message::mkError, "Unrar error code: %i", exitCode); } } void UnpackController::ExecuteSevenZip(const char* password, bool multiVolumes) { // Format: // 7z x -y -p- -o./_unpack *.7z // OR // 7z x -y -p- -o./_unpack *.7z.001 ParamList params; if (!PrepareCmdParams(g_Options->GetSevenZipCmd(), ¶ms, "7-Zip")) { return; } if (!params.Exists("x") && !params.Exists("e")) { params.emplace_back("x"); } params.emplace_back("-y"); if (!Util::EmptyStr(password)) { params.push_back(CString::FormatStr("-p%s", password)); } else { params.emplace_back("-p-"); } params.push_back(CString::FormatStr("-o%s", *m_unpackDir)); params.emplace_back(multiVolumes ? "*.7z.001" : "*.7z"); SetArgs(std::move(params)); ResetEnv(); m_allOkMessageReceived = false; m_unpacker = upSevenZip; PrintMessage(Message::mkInfo, "Executing 7-Zip"); SetLogPrefix("7-Zip"); SetProgressLabel(""); int exitCode = Execute(); SetLogPrefix(nullptr); SetProgressLabel(""); m_unpackOk = exitCode == 0 && m_allOkMessageReceived && !GetTerminated(); m_unpackStartError = exitCode == -1 && !m_autoTerminated; if (!m_unpackOk && exitCode > 0) { PrintMessage(Message::mkError, "7-Zip error code: %i", exitCode); } } bool UnpackController::PrepareCmdParams(const char* command, ParamList* params, const char* infoName) { if (FileSystem::FileExists(command)) { params->emplace_back(command); return true; } std::vector cmdArgs = Util::SplitCommandLine(command); if (cmdArgs.empty()) { PrintMessage(Message::mkError, "Could not start %s, failed to parse command line: %s", infoName, command); m_unpackOk = false; m_unpackStartError = true; return false; } std::move(cmdArgs.begin(), cmdArgs.end(), std::back_inserter(*params)); return true; } void UnpackController::JoinSplittedFiles() { SetLogPrefix("Join"); SetProgressLabel(""); m_postInfo->SetStageProgress(0); // determine groups FileList groups; RegEx regExSplitExt(".*\\.[a-z,0-9]{3}\\.001$"); DirBrowser dir(m_destDir); while (const char* filename = dir.Next()) { BString<1024> fullFilename("%s%c%s", *m_destDir, PATH_SEPARATOR, filename); if (!FileSystem::DirectoryExists(fullFilename) && regExSplitExt.Match(filename) && !FileHasRarSignature(fullFilename)) { if (!JoinFile(filename)) { m_unpackOk = false; break; } } } SetLogPrefix(nullptr); SetProgressLabel(""); } bool UnpackController::JoinFile(const char* fragBaseName) { BString<1024> destBaseName = fragBaseName; // trim extension char* extension = strrchr(destBaseName, '.'); *extension = '\0'; BString<1024> fullFilename("%s%c%s", *m_destDir, PATH_SEPARATOR, fragBaseName); int64 firstSegmentSize = FileSystem::FileSize(fullFilename); int64 difSegmentSize = 0; // Validate joinable file: // - fragments have continuous numbers (no holes); // - fragments have the same size (except of the last fragment); // - the last fragment must be smaller than other fragments, // if it has the same size it is probably not the last and there are missing fragments. RegEx regExSplitExt(".*\\.[a-z,0-9]{3}\\.[0-9]{3}$"); int count = 0; int min = -1; int max = -1; int difSizeCount = 0; int difSizeMin = 999999; DirBrowser dir(m_destDir); while (const char* filename = dir.Next()) { fullFilename.Format("%s%c%s", *m_destDir, PATH_SEPARATOR, filename); if (!FileSystem::DirectoryExists(fullFilename) && regExSplitExt.Match(filename)) { const char* segExt = strrchr(filename, '.'); int segNum = atoi(segExt + 1); count++; min = segNum < min || min == -1 ? segNum : min; max = segNum > max ? segNum : max; int64 segmentSize = FileSystem::FileSize(fullFilename); if (segmentSize != firstSegmentSize) { difSizeCount++; difSizeMin = segNum < difSizeMin ? segNum : difSizeMin; difSegmentSize = segmentSize; } } } int correctedCount = count - (min == 0 ? 1 : 0); if ((min > 1) || correctedCount != max || ((difSizeMin != correctedCount || difSizeMin > max) && m_postInfo->GetNzbInfo()->GetParStatus() != NzbInfo::psSuccess)) { PrintMessage(Message::mkWarning, "Could not join splitted file %s: missing fragments detected", *destBaseName); return false; } // Now can join PrintMessage(Message::mkInfo, "Joining splitted file %s", *destBaseName); m_postInfo->SetStageProgress(0); BString<1024> destFilename("%s%c%s", *m_unpackDir, PATH_SEPARATOR, *destBaseName); DiskFile outFile; if (!outFile.Open(destFilename, DiskFile::omWrite)) { PrintMessage(Message::mkError, "Could not create file %s: %s", *destFilename, *FileSystem::GetLastErrorMessage()); return false; } if (g_Options->GetWriteBuffer() > 0) { outFile.SetWriteBuffer(g_Options->GetWriteBuffer() * 1024); } int64 totalSize = firstSegmentSize * (count - 1) + difSegmentSize; int64 written = 0; CharBuffer buffer(1024 * 50); bool ok = true; for (int i = min; i <= max; i++) { PrintMessage(Message::mkInfo, "Joining from %s.%.3i", *destBaseName, i); SetProgressLabel(BString<1024>("Joining from %s.%.3i", *destBaseName, i)); BString<1024> fragFilename("%s%c%s.%.3i", *m_destDir, PATH_SEPARATOR, *destBaseName, i); if (!FileSystem::FileExists(fragFilename)) { break; } DiskFile inFile; if (inFile.Open(fragFilename, DiskFile::omRead)) { int cnt = buffer.Size(); while (cnt == buffer.Size()) { cnt = (int)inFile.Read(buffer, buffer.Size()); outFile.Write(buffer, cnt); written += cnt; m_postInfo->SetStageProgress(int(written * 1000 / totalSize)); } inFile.Close(); CString fragFilename; fragFilename.Format("%s.%.3i", *destBaseName, i); m_joinedFiles.push_back(std::move(fragFilename)); } else { PrintMessage(Message::mkError, "Could not open file %s", *fragFilename); ok = false; break; } } outFile.Close(); return ok; } void UnpackController::Completed() { bool cleanupSuccess = Cleanup(); if (m_unpackOk && cleanupSuccess) { PrintMessage(Message::mkInfo, "%s %s", *m_infoNameUp, "successful"); m_postInfo->GetNzbInfo()->SetUnpackStatus(NzbInfo::usSuccess); m_postInfo->GetNzbInfo()->SetUnpackCleanedUpDisk(m_cleanedUpDisk); if (g_Options->GetParRename()) { //request par-rename check for extracted files m_postInfo->GetNzbInfo()->SetParRenameStatus(NzbInfo::rsNone); } } else { #ifndef DISABLE_PARCHECK if (!m_unpackOk && (m_postInfo->GetNzbInfo()->GetParStatus() <= NzbInfo::psSkipped || !m_postInfo->GetNzbInfo()->GetParFull()) && !m_unpackStartError && !m_unpackSpaceError && !m_unpackPasswordError && (!GetTerminated() || m_autoTerminated) && m_hasParFiles) { RequestParCheck(!m_password.Empty() || Util::EmptyStr(g_Options->GetUnpackPassFile()) || m_passListTried || !(m_unpackDecryptError || m_unpackPasswordError) || m_postInfo->GetNzbInfo()->GetParStatus() > NzbInfo::psSkipped); } else #endif { PrintMessage(Message::mkError, "%s failed", *m_infoNameUp); m_postInfo->GetNzbInfo()->SetUnpackStatus( m_unpackSpaceError ? NzbInfo::usSpace : m_unpackPasswordError || m_unpackDecryptError ? NzbInfo::usPassword : NzbInfo::usFailure); } } } #ifndef DISABLE_PARCHECK void UnpackController::RequestParCheck(bool forceRepair) { PrintMessage(Message::mkInfo, "%s requested %s", *m_infoNameUp, forceRepair ? "par-check with forced repair" : "par-check/repair"); m_postInfo->SetRequestParCheck(true); m_postInfo->SetForceRepair(forceRepair); m_postInfo->SetUnpackTried(true); m_postInfo->SetPassListTried(m_passListTried); m_postInfo->SetLastUnpackStatus((int)(m_unpackSpaceError ? NzbInfo::usSpace : m_unpackPasswordError || m_unpackDecryptError ? NzbInfo::usPassword : NzbInfo::usFailure)); } #endif void UnpackController::CreateUnpackDir() { bool useInterDir = !Util::EmptyStr(g_Options->GetInterDir()) && !strncmp(m_postInfo->GetNzbInfo()->GetDestDir(), g_Options->GetInterDir(), strlen(g_Options->GetInterDir())) && m_postInfo->GetNzbInfo()->GetDestDir()[strlen(g_Options->GetInterDir())] == PATH_SEPARATOR; if (useInterDir && m_finalDir.Empty()) { m_finalDir = m_postInfo->GetNzbInfo()->BuildFinalDirName(); { GuardedDownloadQueue guard = DownloadQueue::Guard(); m_postInfo->GetNzbInfo()->SetFinalDir(m_finalDir); } m_finalDirCreated = !FileSystem::DirectoryExists(m_finalDir); } const char* destDir = !m_finalDir.Empty() ? *m_finalDir : *m_destDir; m_unpackDir.Format("%s%c%s", destDir, PATH_SEPARATOR, "_unpack"); m_unpackDirCreated = !FileSystem::DirectoryExists(m_unpackDir); detail("Unpacking into %s", *m_unpackDir); CString errmsg; if (!FileSystem::ForceDirectories(m_unpackDir, errmsg)) { PrintMessage(Message::mkError, "Could not create directory %s: %s", *m_unpackDir, *errmsg); } } void UnpackController::CheckArchiveFiles() { RegEx regExRar(".*\\.rar$"); RegEx regExRarMultiSeq(".*\\.[r-z][0-9][0-9]$"); RegEx regExSevenZip(".*\\.7z$"); RegEx regExSevenZipMulti(".*\\.7z\\.[0-9]+$"); RegEx regExSplitExt(".*\\.[a-z,0-9]{3}\\.[0-9]{3}$"); DirBrowser dir(m_destDir); while (const char* filename = dir.Next()) { BString<1024> fullFilename("%s%c%s", *m_destDir, PATH_SEPARATOR, filename); if (!FileSystem::DirectoryExists(fullFilename)) { const char* ext = strrchr(filename, '.'); int extNum = ext ? atoi(ext + 1) : -1; if (regExRar.Match(filename)) { m_hasRarFiles = true; m_hasNotUnpackedRarFiles |= std::find( m_postInfo->GetExtractedArchives()->begin(), m_postInfo->GetExtractedArchives()->end(), filename) == m_postInfo->GetExtractedArchives()->end(); } else if (regExSevenZip.Match(filename)) { m_hasSevenZipFiles = true; } else if (regExSevenZipMulti.Match(filename)) { m_hasSevenZipMultiFiles = true; } else if (regExSplitExt.Match(filename) && (extNum == 0 || extNum == 1)) { m_hasSplittedFiles = true; } else if (!m_hasRenamedArchiveFiles && !regExRarMultiSeq.Match(filename) && !Util::MatchFileExt(filename, g_Options->GetUnpackIgnoreExt(), ",;") && FileHasRarSignature(fullFilename)) { m_hasRenamedArchiveFiles = true; } } } } bool UnpackController::FileHasRarSignature(const char* filename) { 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; DiskFile infile; if (infile.Open(filename, DiskFile::omRead)) { cnt = (int)infile.Read(fileSignature, sizeof(fileSignature)); infile.Close(); } bool rar = cnt == sizeof(fileSignature) && (!strcmp(rar4Signature, fileSignature) || !strcmp(rar5Signature, fileSignature)); return rar; } bool UnpackController::Cleanup() { // By success: // - move unpacked files to destination dir; // - remove _unpack-dir; // - delete archive-files. // By failure: // - remove _unpack-dir. bool ok = true; FileList extractedFiles; if (m_unpackOk) { // moving files back DirBrowser dir(m_unpackDir); while (const char* filename = dir.Next()) { BString<1024> srcFile("%s%c%s", *m_unpackDir, PATH_SEPARATOR, filename); BString<1024> dstFile("%s%c%s", !m_finalDir.Empty() ? *m_finalDir : *m_destDir, PATH_SEPARATOR, *FileSystem::MakeValidFilename(filename)); // silently overwrite existing files FileSystem::DeleteFile(dstFile); bool hiddenFile = filename[0] == '.'; if (!FileSystem::MoveFile(srcFile, dstFile) && !hiddenFile) { PrintMessage(Message::mkError, "Could not move file %s to %s: %s", *srcFile, *dstFile, *FileSystem::GetLastErrorMessage()); ok = false; } extractedFiles.push_back(filename); } } CString errmsg; if (ok && !FileSystem::DeleteDirectoryWithContent(m_unpackDir, errmsg)) { PrintMessage(Message::mkError, "Could not delete temporary directory %s: %s", *m_unpackDir, *errmsg); } if (!m_unpackOk && m_finalDirCreated) { FileSystem::DeleteDirectory(m_finalDir); } if (m_unpackOk && ok && g_Options->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 regExSplitExt(".*\\.[a-z,0-9]{3}\\.[0-9]{3}$"); DirBrowser dir(m_destDir); while (const char* filename = dir.Next()) { BString<1024> fullFilename("%s%c%s", *m_destDir, PATH_SEPARATOR, filename); if (!FileSystem::DirectoryExists(fullFilename) && (m_interDir || !extractedFiles.Exists(filename)) && (regExRar.Match(filename) || regExSevenZip.Match(filename) || (regExRarMultiSeq.Match(filename) && FileHasRarSignature(fullFilename)) || (m_hasSplittedFiles && regExSplitExt.Match(filename) && m_joinedFiles.Exists(filename)))) { PrintMessage(Message::mkInfo, "Deleting file %s", filename); if (!FileSystem::DeleteFile(fullFilename)) { PrintMessage(Message::mkError, "Could not delete file %s: %s", *fullFilename, *FileSystem::GetLastErrorMessage()); } } } m_cleanedUpDisk = true; } return ok; } /** * 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* buf, int bufSize, FILE* stream) { bool printed = false; int i = 0; for (; i < bufSize - 1; i++) { int ch = fgetc(stream); buf[i] = ch; buf[i+1] = '\0'; if (ch == EOF) { break; } if (ch == '\n') { i++; break; } char* backspace = strrchr(buf, '\b'); if (backspace) { if (!printed) { BString<1024> tmp = buf; char* tmpPercent = strrchr(tmp, '\b'); if (tmpPercent) { *tmpPercent = '\0'; } if (strncmp(buf, "...", 3)) { ProcessOutput(tmp); } printed = true; } if (strchr(backspace, '%')) { int percent = atoi(backspace + 1); m_postInfo->SetStageProgress(percent * 10); } } } buf[i] = '\0'; if (printed) { buf[0] = '\0'; } return i > 0; } void UnpackController::AddMessage(Message::EKind kind, const char* text) { BString<1024> msgText = text; int len = strlen(text); // Modify unrar messages for better readability: // remove the destination path part from message "Extracting file.xxx" if (m_unpacker == upUnrar && !strncmp(text, "Unrar: Extracting ", 19) && !strncmp(text + 19, m_unpackExtendedDir, strlen(m_unpackExtendedDir))) { msgText.Format("Unrar: Extracting %s", text + 19 + strlen(m_unpackExtendedDir) + 1); } m_postInfo->GetNzbInfo()->AddMessage(kind, msgText); if (m_unpacker == upUnrar && !strncmp(msgText, "Unrar: UNRAR ", 6) && strstr(msgText, " Copyright ") && strstr(msgText, " 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_postInfo->SetStageTime(Util::CurrentTime()); } if (m_unpacker == upUnrar && !strncmp(msgText, "Unrar: Extracting ", 18)) { SetProgressLabel(msgText + 7); } if (m_unpacker == upUnrar && !strncmp(text, "Unrar: Extracting from ", 23)) { #ifdef DEBUG const char *filename = text + 23; debug("Filename: %s", filename); #endif SetProgressLabel(text + 7); } if (m_unpacker == upUnrar && (!strncmp(text, "Unrar: Checksum error in the encrypted file", 42) || !strncmp(text, "Unrar: CRC failed in the encrypted file", 39))) { m_unpackDecryptError = true; } if (m_unpacker == upUnrar && (!strncmp(text, "Unrar: The specified password is incorrect.", 43) || !strncmp(text, "Unrar: Incorrect password for", 29))) { m_unpackPasswordError = true; } if (m_unpacker == upSevenZip && (len > 18 && !strncmp(text + len - 45, "Data Error in encrypted file. Wrong password?", 45))) { m_unpackDecryptError = true; } if (!IsStopped() && (m_unpackDecryptError || m_unpackPasswordError || strstr(text, " : packed data CRC failed in volume") || strstr(text, " : packed data checksum error in volume") || (len > 13 && !strncmp(text + len - 13, " - CRC failed", 13)) || (len > 18 && !strncmp(text + len - 18, " - checksum failed", 18)) || !strncmp(text, "Unrar: WARNING: You need to start extraction from a previous volume", 67))) { m_postInfo->GetNzbInfo()->AddMessage(Message::mkWarning, BString<1024>("Cancelling %s due to errors", *m_infoName)); m_autoTerminated = true; Stop(); } if ((m_unpacker == upUnrar && !strncmp(text, "Unrar: All OK", 13)) || (m_unpacker == upSevenZip && !strncmp(text, "7-Zip: Everything is Ok", 23))) { m_allOkMessageReceived = true; } } void UnpackController::Stop() { debug("Stopping unpack"); Thread::Stop(); Terminate(); } void UnpackController::SetProgressLabel(const char* progressLabel) { GuardedDownloadQueue guard = DownloadQueue::Guard(); m_postInfo->SetProgressLabel(progressLabel); } bool UnpackController::HasCompletedArchiveFiles(NzbInfo* nzbInfo) { RegEx regExRar(".*\\.rar$"); RegEx regExSevenZip(".*\\.7z$"); RegEx regExSevenZipMulti(".*\\.7z\\.[0-9]+$"); for (CompletedFile& completedFile: nzbInfo->GetCompletedFiles()) { const char* filename = completedFile.GetFilename(); if (regExRar.Match(filename) || regExSevenZip.Match(filename) || regExSevenZipMulti.Match(filename)) { return true; } } return false; }