导出数据转spine

This commit is contained in:
tianmo committed 2026-09-07 21:29:15 +08:00
1 parent d258d4f435
commit 3a7c0b4092
65 files changed
+6000

No files matched your search

@@ -0,0 +1,35 @@
#pragma once
#include <chrono>
#include <filesystem>
#include <memory>
#include <string>
#include <vector>
namespace dts {
struct ProcessRequest {
std::filesystem::path program;
std::vector<std::string> arguments;
std::filesystem::path workingDirectory;
std::chrono::milliseconds timeout{10000};
};
struct ProcessResult {
bool started = false;
bool timedOut = false;
int exitCode = -1;
std::string output;
std::string error;
};
class IProcessRunner {
public:
virtual ~IProcessRunner() = default;
[[nodiscard]] virtual ProcessResult run(const ProcessRequest& request) const = 0;
};
[[nodiscard]] std::unique_ptr<IProcessRunner> createDefaultProcessRunner();
} // namespace dts
@@ -0,0 +1,134 @@
#include "platform/ProcessRunner.hpp"
#include <cerrno>
#include <chrono>
#include <csignal>
#include <cstring>
#include <fcntl.h>
#include <memory>
#include <poll.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <thread>
#include <unistd.h>
namespace dts {
namespace {
void appendAvailableOutput(int descriptor, std::string& output) {
char buffer[4096];
while (true) {
const auto count = ::read(descriptor, buffer, sizeof(buffer));
if (count > 0) {
output.append(buffer, static_cast<std::size_t>(count));
continue;
}
if (count < 0 && errno == EINTR) {
continue;
}
break;
}
}
class PosixProcessRunner final : public IProcessRunner {
public:
ProcessResult run(const ProcessRequest& request) const override {
ProcessResult result;
int outputPipe[2];
if (::pipe(outputPipe) != 0) {
result.error = std::strerror(errno);
return result;
}
const pid_t child = ::fork();
if (child < 0) {
result.error = std::strerror(errno);
::close(outputPipe[0]);
::close(outputPipe[1]);
return result;
}
if (child == 0) {
::setpgid(0, 0);
::close(outputPipe[0]);
::dup2(outputPipe[1], STDOUT_FILENO);
::dup2(outputPipe[1], STDERR_FILENO);
::close(outputPipe[1]);
if (!request.workingDirectory.empty()) {
::chdir(request.workingDirectory.c_str());
}
std::vector<std::string> values;
values.reserve(request.arguments.size() + 1);
values.push_back(request.program.string());
values.insert(values.end(), request.arguments.begin(), request.arguments.end());
std::vector<char*> arguments;
arguments.reserve(values.size() + 1);
for (auto& value : values) {
arguments.push_back(value.data());
}
arguments.push_back(nullptr);
::execv(request.program.c_str(), arguments.data());
::_exit(127);
}
result.started = true;
::setpgid(child, child);
::close(outputPipe[1]);
const auto flags = ::fcntl(outputPipe[0], F_GETFL, 0);
::fcntl(outputPipe[0], F_SETFL, flags | O_NONBLOCK);
const auto deadline = std::chrono::steady_clock::now() + request.timeout;
int status = 0;
bool finished = false;
while (!finished) {
appendAvailableOutput(outputPipe[0], result.output);
const auto waitResult = ::waitpid(child, &status, WNOHANG);
if (waitResult == child) {
finished = true;
break;
}
if (waitResult < 0) {
result.error = std::strerror(errno);
break;
}
if (std::chrono::steady_clock::now() >= deadline) {
result.timedOut = true;
::kill(-child, SIGTERM);
for (int attempt = 0; attempt < 25; ++attempt) {
if (::waitpid(child, &status, WNOHANG) == child) {
finished = true;
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
if (!finished) {
::kill(-child, SIGKILL);
::waitpid(child, &status, 0);
}
break;
}
pollfd descriptor{outputPipe[0], POLLIN, 0};
::poll(&descriptor, 1, 25);
}
appendAvailableOutput(outputPipe[0], result.output);
::close(outputPipe[0]);
if (WIFEXITED(status)) {
result.exitCode = WEXITSTATUS(status);
} else if (WIFSIGNALED(status)) {
result.exitCode = 128 + WTERMSIG(status);
}
return result;
}
};
} // namespace
std::unique_ptr<IProcessRunner> createDefaultProcessRunner() {
return std::make_unique<PosixProcessRunner>();
}
} // namespace dts
@@ -0,0 +1,132 @@
#include "platform/ProcessRunner.hpp"
#define NOMINMAX
#include <windows.h>
#include <memory>
#include <sstream>
namespace dts {
namespace {
std::wstring widen(const std::string& value) {
if (value.empty()) {
return {};
}
const int size = MultiByteToWideChar(CP_UTF8, 0, value.data(),
static_cast<int>(value.size()), nullptr, 0);
std::wstring output(static_cast<std::size_t>(size), L'\0');
MultiByteToWideChar(CP_UTF8, 0, value.data(), static_cast<int>(value.size()),
output.data(), size);
return output;
}
std::wstring quote(const std::wstring& value) {
if (value.find_first_of(L" \t\"") == std::wstring::npos) {
return value;
}
std::wstring output = L"\"";
std::size_t slashes = 0;
for (const auto character : value) {
if (character == L'\\') {
++slashes;
} else if (character == L'\"') {
output.append(slashes * 2 + 1, L'\\');
output += character;
slashes = 0;
} else {
output.append(slashes, L'\\');
output += character;
slashes = 0;
}
}
output.append(slashes * 2, L'\\');
return output + L"\"";
}
std::wstring commandLine(const ProcessRequest& request) {
std::wstring value = quote(request.program.wstring());
for (const auto& argument : request.arguments) {
value += L" " + quote(widen(argument));
}
return value;
}
class WindowsProcessRunner final : public IProcessRunner {
public:
ProcessResult run(const ProcessRequest& request) const override {
ProcessResult result;
SECURITY_ATTRIBUTES attributes{sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE};
HANDLE readPipe = nullptr;
HANDLE writePipe = nullptr;
if (!CreatePipe(&readPipe, &writePipe, &attributes, 0)) {
result.error = "CreatePipe failed.";
return result;
}
SetHandleInformation(readPipe, HANDLE_FLAG_INHERIT, 0);
STARTUPINFOW startup{};
startup.cb = sizeof(startup);
startup.dwFlags = STARTF_USESTDHANDLES;
startup.hStdOutput = writePipe;
startup.hStdError = writePipe;
startup.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
PROCESS_INFORMATION process{};
auto command = commandLine(request);
const auto directory = request.workingDirectory.wstring();
const BOOL created = CreateProcessW(request.program.wstring().c_str(), command.data(),
nullptr, nullptr, TRUE, CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP, nullptr,
directory.empty() ? nullptr : directory.c_str(), &startup, &process);
CloseHandle(writePipe);
if (!created) {
CloseHandle(readPipe);
result.error = "CreateProcessW failed: " + std::to_string(GetLastError());
return result;
}
result.started = true;
HANDLE job = CreateJobObjectW(nullptr, nullptr);
if (job) {
JOBOBJECT_EXTENDED_LIMIT_INFORMATION limits{};
limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
SetInformationJobObject(job, JobObjectExtendedLimitInformation, &limits, sizeof(limits));
AssignProcessToJobObject(job, process.hProcess);
}
const DWORD waitTime = static_cast<DWORD>(request.timeout.count());
if (WaitForSingleObject(process.hProcess, waitTime) == WAIT_TIMEOUT) {
result.timedOut = true;
if (job) {
TerminateJobObject(job, 1);
} else {
TerminateProcess(process.hProcess, 1);
}
WaitForSingleObject(process.hProcess, 1000);
}
char buffer[4096];
DWORD count = 0;
while (ReadFile(readPipe, buffer, sizeof(buffer), &count, nullptr) && count > 0) {
result.output.append(buffer, count);
}
DWORD exitCode = 1;
GetExitCodeProcess(process.hProcess, &exitCode);
result.exitCode = static_cast<int>(exitCode);
CloseHandle(readPipe);
if (job) {
CloseHandle(job);
}
CloseHandle(process.hThread);
CloseHandle(process.hProcess);
return result;
}
};
} // namespace
std::unique_ptr<IProcessRunner> createDefaultProcessRunner() {
return std::make_unique<WindowsProcessRunner>();
}
} // namespace dts