Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add: better long-paths support on Windows #441

Merged
merged 3 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmake/par2-turbo.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ ExternalProject_add(
par2-turbo
PREFIX par2-turbo
GIT_REPOSITORY https://github.com/nzbgetcom/par2cmdline-turbo.git
GIT_TAG v1.1.1-nzbget
GIT_TAG v1.1.1-nzbget-20241128
TLS_VERIFY TRUE
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
Expand Down
28 changes: 17 additions & 11 deletions daemon/extension/ManifestFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,34 @@

#include "nzbget.h"

#include <fstream>
#include "ManifestFile.h"
#include "Json.h"
#include "FileSystem.h"
#include "Log.h"

namespace ManifestFile
{
const char* MANIFEST_FILE = "manifest.json";
const char* DEFAULT_SECTION_NAME = "options";

bool Load(Manifest& manifest, const char* directory)
{
BString<1024> path("%s%c%s", directory, PATH_SEPARATOR, MANIFEST_FILE);
std::ifstream fs(path);
if (!fs.is_open())
return false;
std::string path = std::string(directory) + PATH_SEPARATOR + MANIFEST_FILE;
DiskFile file;
if (!file.Open(path.c_str(), DiskFile::omRead)) return false;

Json::ErrorCode ec;
Json::JsonValue jsonValue = Json::Deserialize(fs, ec);
if (ec)
std::string jsonStr;
char buffer[BUFFER_SIZE];
while (file.ReadLine(buffer, BUFFER_SIZE))
{
jsonStr += buffer;
}

auto desRes = Json::Deserialize(jsonStr);
if (!desRes.has_value())
{
error("Failed to parse %s. Syntax error.", path.c_str());
return false;
}

Json::JsonValue jsonValue = std::move(desRes.value());
Json::JsonObject json = jsonValue.as_object();

if (!ValidateAndSet(json, manifest))
Expand Down
5 changes: 3 additions & 2 deletions daemon/extension/ManifestFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ namespace ManifestFile
{
using SelectOption = std::variant<double, std::string>;

extern const char* MANIFEST_FILE;
extern const char* DEFAULT_SECTION_NAME;
inline constexpr size_t BUFFER_SIZE = 4096;
inline const char* MANIFEST_FILE = "manifest.json";
inline const char* DEFAULT_SECTION_NAME = "options";

struct Section
{
Expand Down
2 changes: 1 addition & 1 deletion daemon/main/nzbget.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
#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

#define popen _popen
#define popen _wpopen
#define pclose _pclose

#endif
Expand Down
29 changes: 18 additions & 11 deletions daemon/postprocess/ParChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class Repairer final : public Par2::Par2Repairer, public ParChecker::AbstractRep
? g_Options->GetParBuffer()
: 0;
}
Par2::Result PreProcess(const char *parFilename);
Par2::Result PreProcess(const std::string& parFilename);
Par2::Result Process(bool dorepair);
virtual Repairer* GetRepairer() { return this; }

Expand Down Expand Up @@ -108,30 +108,30 @@ class RepairCreatorPacket : public Par2::CreatorPacket
friend class ParChecker;
};

Par2::Result Repairer::PreProcess(const char *parFilename)
Par2::Result Repairer::PreProcess(const std::string& parFilename)
{
std::string memParam = "-m" + std::to_string(m_memToUse);
std::string threadsParam = "-t" + std::to_string(m_threadsToUse);

if (g_Options->GetParScan() == Options::psFull)
{
BString<1024> wildcardParam(parFilename, 1024);
BString<1024> wildcardParam(parFilename.c_str(), 1024);
char* basename = FileSystem::BaseFileName(wildcardParam);
if (basename != wildcardParam && strlen(basename) > 0)
{
basename[0] = '*';
basename[1] = '\0';
}

const char* argv[] = { "par2", "r", "-v", memParam.c_str(), threadsParam.c_str(), parFilename, wildcardParam };
const char* argv[] = { "par2", "r", "-v", memParam.c_str(), threadsParam.c_str(), parFilename.c_str(), wildcardParam };
if (!m_commandLine.Parse(7, (char**)argv))
{
return Par2::eInvalidCommandLineArguments;
}
}
else
{
const char* argv[] = { "par2", "r", "-v", memParam.c_str(), threadsParam.c_str(), parFilename };
const char* argv[] = { "par2", "r", "-v", memParam.c_str(), threadsParam.c_str(), parFilename.c_str() };
if (!m_commandLine.Parse(6, (char**)argv))
{
return Par2::eInvalidCommandLineArguments;
Expand Down Expand Up @@ -294,7 +294,9 @@ ParChecker::EStatus ParChecker::RunParCheckAll()

if (!IsStopped())
{
BString<1024> fullParFilename( "%s%c%s", m_destDir.c_str(), PATH_SEPARATOR, parFilename.c_str());
std::string path = m_destDir + PATH_SEPARATOR + parFilename;
std::string fullParFilename = FileSystem::GetRealPath(path)
.value_or(std::move(path));

int baseLen = 0;
ParParser::ParseParFilename(parFilename.c_str(), true, &baseLen, nullptr);
Expand All @@ -304,7 +306,7 @@ ParChecker::EStatus ParChecker::RunParCheckAll()
BString<1024> parInfoName("%s%c%s", m_nzbName.c_str(), PATH_SEPARATOR, *infoName);
SetInfoName(parInfoName);

EStatus status = RunParCheck(fullParFilename);
EStatus status = RunParCheck(std::move(fullParFilename));

// accumulate total status, the worst status has priority
if (allStatus > status)
Expand All @@ -317,10 +319,10 @@ ParChecker::EStatus ParChecker::RunParCheckAll()
return allStatus;
}

ParChecker::EStatus ParChecker::RunParCheck(const char* parFilename)
ParChecker::EStatus ParChecker::RunParCheck(std::string parFilename)
{
Cleanup();
m_parFilename = parFilename ? parFilename : "";
m_parFilename = std::move(parFilename);
m_stage = ptLoadingPars;
m_processedCount = 0;
m_extraFiles = 0;
Expand Down Expand Up @@ -484,7 +486,7 @@ int ParChecker::PreProcessPar()
m_repairer = std::make_unique<Repairer>(this);
}

res = GetRepairer()->PreProcess(m_parFilename.c_str());
res = GetRepairer()->PreProcess(m_parFilename);
debug("ParChecker: PreProcess-result=%i", res);

if (IsStopped())
Expand Down Expand Up @@ -1081,7 +1083,12 @@ void ParChecker::CheckEmptyFiles()
if (sourcefile && sourcefile->GetDescriptionPacket())
{
// GetDescriptionPacket()->FileName() returns a temp string object, which we need to hold for a while
std::string filenameObj = sourcefile->GetDescriptionPacket()->FileName();
std::string filenameObj = Par2::DescriptionPacket::TranslateFilenameFromPar2ToLocal(
m_parCout,
m_parCerr,
Par2::nlNormal,
sourcefile->GetDescriptionPacket()->FileName()
);
if (!filenameObj.empty() && !IsProcessedFile(filenameObj.c_str()))
{
bool ignore = Util::MatchFileExt(filenameObj.c_str(), g_Options->GetParIgnoreExt(), ",;");
Expand Down
2 changes: 1 addition & 1 deletion daemon/postprocess/ParChecker.h
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ class ParChecker

void Cleanup();
EStatus RunParCheckAll();
EStatus RunParCheck(const char* parFilename);
EStatus RunParCheck(std::string parFilename);
int PreProcessPar();
bool LoadMainParBak();
int ProcessMorePars();
Expand Down
7 changes: 6 additions & 1 deletion daemon/postprocess/ParRenamer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,12 @@ void ParRenamer::LoadParFile(const char* parFilename)
m_hasDamagedParFiles = true;
continue;
}
std::string filename = sourceFile->GetDescriptionPacket()->FileName();
std::string filename = Par2::DescriptionPacket::TranslateFilenameFromPar2ToLocal(
m_nout,
m_nout,
Par2::nlNormal,
sourceFile->GetDescriptionPacket()->FileName()
);
std::string hash = sourceFile->GetDescriptionPacket()->Hash16k().print();

bool exists = std::find_if(m_fileHashList.begin(), m_fileHashList.end(),
Expand Down
4 changes: 4 additions & 0 deletions daemon/postprocess/ParRenamer.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ class ParRenamer
int GetStageProgress() { return m_stageProgress; }

private:
class NullStreamBuf final : public std::streambuf {};
NullStreamBuf m_nullbuf;
std::ostream m_nout{&m_nullbuf};

class FileHash
{
public:
Expand Down
2 changes: 1 addition & 1 deletion daemon/system/SystemInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ namespace System

std::string path = FileSystem::ExtractFilePathFromCmd(cmd);

auto result = FileSystem::GetFileRealPath(path);
auto result = FileSystem::GetRealPath(path);

if (result.has_value() && FileSystem::FileExists(result.value().c_str()))
{
Expand Down
39 changes: 32 additions & 7 deletions daemon/util/FileSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
#include "Util.h"
#include "Log.h"

#ifdef WIN32
#include "utf8.h"
#endif

const char* RESERVED_DEVICE_NAMES[] = { "CON", "PRN", "AUX", "NUL",
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", NULL };
Expand Down Expand Up @@ -56,28 +60,49 @@ void FileSystem::NormalizePathSeparators(char* path)
}
}

std::optional<std::string> FileSystem::GetFileRealPath(const std::string& path)
std::optional<std::string> FileSystem::GetRealPath(const std::string& path)
{
if (path.empty()) return std::nullopt;

#ifdef WIN32
char buffer[MAX_PATH];
DWORD len = GetFullPathName(path.c_str(), MAX_PATH, buffer, nullptr);
if (len != 0)
auto res = Utf8::Utf8ToWide(path);
if (!res.has_value()) return std::nullopt;

std::wstring wpath = std::move(res.value());

if (wpath.size() > MAX_DIR_PATH && std::wcsncmp(wpath.c_str(), L"\\\\", 2) == 0)
{
wpath = L"\\\\?\\UNC" + wpath;
}
else if (wpath.size() > MAX_DIR_PATH)
{
return std::optional{ buffer };
wpath = L"\\\\?\\" + wpath;
}

DWORD len = GetFullPathNameW(wpath.c_str(), 0, nullptr, nullptr);
if (len == 0) return std::nullopt;

std::wstring buffer(len, '\0');
len = GetFullPathNameW(wpath.c_str(), len, &buffer[0], nullptr);
if (len == 0) return std::nullopt;

return Utf8::WideToUtf8(buffer);
#else
if (char* realPath = realpath(path.c_str(), nullptr))
{
std::string res = realPath;
free(realPath);
return std::optional(std::move(res));
return res;
}
#endif

return std::nullopt;
}

#ifdef WIN32

const size_t FileSystem::MAX_DIR_PATH = 248;

bool FileSystem::ForceDirectories(const char* path, CString& errmsg)
{
errmsg.Clear();
Expand Down Expand Up @@ -621,7 +646,7 @@ std::string FileSystem::ExtractFilePathFromCmd(const std::string& path)
{
if (path.empty())
{
return std::string(path);
return path;
}

size_t lastSeparatorPos = path.find_last_of(PATH_SEPARATOR);
Expand Down
4 changes: 3 additions & 1 deletion daemon/util/FileSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class FileSystem
static char* BaseFileName(const char* filename);
static bool SameFilename(const char* filename1, const char* filename2);
static void NormalizePathSeparators(char* path);
static std::optional<std::string> GetFileRealPath(const std::string& path);
static std::optional<std::string> GetRealPath(const std::string& path);
static bool LoadFileIntoBuffer(const char* filename, CharBuffer& buffer, bool addTrailingNull);
static bool SaveBufferIntoFile(const char* filename, const char* buffer, int bufLen);
static bool AllocateFile(const char* filename, int64 size, bool sparse, CString& errmsg);
Expand Down Expand Up @@ -96,6 +96,8 @@ class FileSystem
static CString WidePathToUtfPath(const wchar_t* wpath);
static CString MakeCanonicalPath(const char* filename);
static bool NeedLongPath(const char* path);

static const size_t MAX_DIR_PATH;
#endif
};

Expand Down
31 changes: 18 additions & 13 deletions daemon/util/Json.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* This file is part of nzbget. See <https://nzbget.com>.
*
* Copyright (C) 2023 Denis <denis@nzbget.com>
* Copyright (C) 2023-2024 Denis <denis@nzbget.com>
*
* 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
Expand All @@ -21,32 +21,37 @@

#include "Json.h"

namespace Json {
JsonValue Deserialize(std::istream& is, ErrorCode& ec)
namespace Json
{
std::optional<JsonValue> Deserialize(std::basic_istream<char>& is) noexcept
{
ErrorCode ec;
StreamParser parser;
std::string line;

while (std::getline(is, line))
{
parser.write(line, ec);
if (ec)
{
return nullptr;
}
if (ec) return std::nullopt;
}

parser.finish(ec);

if (ec)
{
return nullptr;
}
if (ec) return std::nullopt;

return parser.release();
}

std::string Serialize(const JsonObject& json)
std::optional<JsonValue> Deserialize(const std::string& jsonStr) noexcept
{
ErrorCode ec;
JsonValue value = parse(jsonStr, ec);

if (ec) return std::nullopt;

return value;
}

std::string Serialize(const JsonObject& json) noexcept
{
return serialize(json);
}
Expand Down
Loading