You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

219 lines
7.7 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// io_utils.cpp - 通用 IO 工具实现
#include "io_utils.h"
#include "config.h"
#include <fstream>
#include <sstream>
#include <iomanip>
#include <ctime>
#include <algorithm>
#include <cstdio>
// stb single-header JPEG 编码器(实现仅保留在本文件,全工程只定义一次)
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "PAVO2_SDK/include/stb_image_write.h"
#ifdef _WIN32
#include <direct.h> // _mkdir
#include <windows.h>
#include <io.h>
#include <iostream>
#include <cerrno>
#else
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <cerrno>
#endif
std::string trim_str(const std::string& s)
{
size_t b = s.find_first_not_of(" \t\r\n");
if (b == std::string::npos) return "";
size_t e = s.find_last_not_of(" \t\r\n");
return s.substr(b, e - b + 1);
}
std::string gbk_to_utf8(const std::string& s)
{
#ifdef _WIN32
if (s.empty()) return s;
int wlen = MultiByteToWideChar(CP_ACP, 0, s.c_str(), (int)s.size(), nullptr, 0);
if (wlen <= 0) return s;
std::wstring wbuf(wlen, L'\0');
MultiByteToWideChar(CP_ACP, 0, s.c_str(), (int)s.size(), &wbuf[0], wlen);
int ulen = WideCharToMultiByte(CP_UTF8, 0, wbuf.c_str(), wlen, nullptr, 0, nullptr, nullptr);
if (ulen <= 0) return s;
std::string ubuf(ulen, '\0');
WideCharToMultiByte(CP_UTF8, 0, wbuf.c_str(), wlen, &ubuf[0], ulen, nullptr, nullptr);
return ubuf;
#else
return s;
#endif
}
#ifdef _WIN32
static bool mkdir_one(const std::string& path) { return _mkdir(path.c_str()) == 0 || errno == EEXIST; }
#else
static bool mkdir_one(const std::string& path) { return mkdir(path.c_str(), 0777) == 0 || errno == EEXIST; }
#endif
bool make_dirs(const std::string& path)
{
if (path.empty()) return false;
std::string cur;
size_t pos = 0;
while (pos <= path.size()) {
size_t slash = path.find_first_of("/\\", pos);
cur += path.substr(pos, (slash == std::string::npos ? path.size() : slash) - pos);
if (!cur.empty() && cur != ".") {
if (!mkdir_one(cur) && errno != EEXIST) return false;
}
if (slash == std::string::npos) break;
cur += '/';
pos = slash + 1;
}
return true;
}
std::string make_task_dir(const std::string& task_id)
{
std::string base = "data";
make_dirs(base);
time_t rawtime = std::time(nullptr);
struct tm* t = std::localtime(&rawtime);
char date_buf[16], time_buf[16];
std::strftime(date_buf, sizeof(date_buf), "%Y%m%d", t);
std::strftime(time_buf, sizeof(time_buf), "%H%M%S", t);
std::string day_dir = base + "/" + date_buf;
make_dirs(day_dir);
std::string task_dir = day_dir + "/" + time_buf;
if (!task_id.empty()) task_dir += "_" + task_id;
else task_dir += "_";
make_dirs(task_dir);
return task_dir;
}
// Windows 递归删除目录(含只读文件),直接用 API,不弹 cmd 黑窗
#ifdef _WIN32
static bool remove_dir_recursive(const std::wstring& dir)
{
std::wstring pattern = dir + L"\\*";
WIN32_FIND_DATAW fd;
HANDLE h = FindFirstFileW(pattern.c_str(), &fd);
if (h == INVALID_HANDLE_VALUE)
return RemoveDirectoryW(dir.c_str()) != 0;
do {
std::wstring name = fd.cFileName;
if (name == L"." || name == L"..") continue;
std::wstring full = dir + L"\\" + name;
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
remove_dir_recursive(full); // 子目录删不掉不致命,继续删其它
} else {
DWORD attrs = GetFileAttributesW(full.c_str());
if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_READONLY))
SetFileAttributesW(full.c_str(), attrs & ~FILE_ATTRIBUTE_READONLY);
DeleteFileW(full.c_str());
}
} while (FindNextFileW(h, &fd));
FindClose(h);
return RemoveDirectoryW(dir.c_str()) != 0;
}
#endif
// 清理 data/日期 下早于 cfg.log.retain_days 天的过期日期目录(保留最近 N 天)
// 在程序启动与每日 01:00 调用。目录名 YYYYMMDD 为纯数字,字典序即日期序。
void clean_expired_log_dirs()
{
std::cout << "[清理] 定时检查过期日志目录 (保留最近 " << cfg.log.retain_days << " 天)" << std::endl;
if (cfg.log.retain_days <= 0) { std::cout << "[清理] 已禁用(cfg.log.retain_days<=0), 跳过" << std::endl; return; }
#ifdef _WIN32
// 截止日期 = 今天 - cfg.log.retain_days 天(用文件时间做减法,避免 localtime/mktime
// 修改共享 tm 缓冲区,与 make_task_dir 等其它线程并发调用时产生数据竞争)
SYSTEMTIME st;
GetLocalTime(&st);
FILETIME ft;
SystemTimeToFileTime(&st, &ft);
ULONGLONG t = ((ULONGLONG)ft.dwHighDateTime << 32) | ft.dwLowDateTime;
t -= (ULONGLONG)cfg.log.retain_days * 86400ULL * 10000000ULL; // 100ns 单位
ft.dwHighDateTime = (DWORD)(t >> 32);
ft.dwLowDateTime = (DWORD)t;
FileTimeToSystemTime(&ft, &st);
char cutoff[16];
std::snprintf(cutoff, sizeof(cutoff), "%04u%02u%02u", st.wYear, st.wMonth, st.wDay);
const std::string base = "data";
const std::wstring wbase(base.begin(), base.end());
WIN32_FIND_DATAW fd;
HANDLE h = FindFirstFileW((wbase + L"\\*").c_str(), &fd);
if (h == INVALID_HANDLE_VALUE) { std::cout << "[清理] data 目录不存在, 跳过" << std::endl; return; }
int removed = 0;
do {
if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) continue;
std::wstring wname = fd.cFileName;
if (wname.size() != 8) continue; // 仅处理 YYYYMMDD
bool is_num = true;
for (wchar_t c : wname)
if (c < L'0' || c > L'9') { is_num = false; break; }
if (!is_num) continue;
std::string name(wname.begin(), wname.end()); // 纯数字 ASCII,无损转换
if (name < cutoff) { // 早于截止日期才删
std::wstring full = wbase + L"\\" + wname;
if (remove_dir_recursive(full)) {
std::cout << "[清理] 已删除过期日志目录: data/" << name << std::endl;
++removed;
}
else
std::cout << "[清理] 删除失败(可能正被占用): data/" << name << std::endl;
}
} while (FindNextFileW(h, &fd));
FindClose(h);
std::cout << "[清理] 完成: 本次共删除 " << removed << " 个过期目录" << std::endl;
#endif
}
void save_pcd_ascii(const std::string& filename,
const std::vector<double>& xs,
const std::vector<double>& ys,
const std::vector<double>& zs)
{
size_t n = xs.size();
std::ostringstream oss;
oss << "# .PCD v0.7 - Point Cloud Data file format\n"
<< "VERSION 0.7\n"
<< "FIELDS x y z\n"
<< "SIZE 4 4 4\n"
<< "TYPE F F F\n"
<< "COUNT 1 1 1\n"
<< "WIDTH " << n << "\n"
<< "HEIGHT 1\n"
<< "VIEWPOINT 0 0 0 1 0 0 0\n"
<< "POINTS " << n << "\n"
<< "DATA ascii\n"
<< std::fixed << std::setprecision(1);
for (size_t i = 0; i < n; i++) {
oss << xs[i] << " " << ys[i] << " " << zs[i] << "\n";
}
std::string buf = oss.str();
std::ofstream f(filename, std::ios::binary);
f.write(buf.data(), (std::streamsize)buf.size());
f.close();
}
bool save_jpg_gray(const std::string& filename, int width, int height,
const std::vector<uint8_t>& pixels, int quality)
{
std::vector<uint8_t> rgb(width * height * 3);
for (int i = 0; i < width * height; i++) {
uint8_t v = pixels[i];
rgb[i * 3 + 0] = v;
rgb[i * 3 + 1] = v;
rgb[i * 3 + 2] = v;
}
int ret = stbi_write_jpg(filename.c_str(), width, height, 3, rgb.data(), quality);
return ret != 0;
}