#include <iostream>
#include <string>
#include <vector>
#include <filesystem>
#include <chrono>
#include <iomanip>
#include <sstream>
#include <cstdlib>
#include <io.h>
#include <direct.h>namespace fs = std::filesystem;
using namespace std::chrono;// 执行外部命令(Windows)
int exec_cmd(const std::string& cmd) {std::cout << "[CMD] " << cmd << std::endl;return std::system(cmd.c_str());
}// 获取当前本地时间字符串
std::string current_local_time_str(const char* fmt = "%Y-%m-%d %H:%M:%S") {auto now = system_clock::now();std::time_t tt = system_clock::to_time_t(now);std::tm tm{};localtime_s(&tm, &tt);std::ostringstream oss;oss << std::put_time(&tm, fmt);return oss.str();
}// 获取当前日期(%Y-%m-%d)
std::string get_date() {return current_local_time_str("%Y-%m-%d");
}// 获取星期几(0=Sunday, 6=Saturday)
int get_weekday() {auto now = system_clock::now();std::time_t tt = system_clock::to_time_t(now);std::tm tm{};localtime_s(&tm, &tt);return tm.tm_wday;
}// 获取 N 天前的日期(字符串)
std::string get_date_offset(int days) {auto now = system_clock::now();auto past = now - hours(24 * days);std::time_t tt = system_clock::to_time_t(past);std::tm tm{};localtime_s(&tm, &tt);std::ostringstream oss;oss << std::put_time(&tm, "%Y-%m-%d");return oss.str();
}// 递归计算目录大小(字节)
uint64_t get_directory_size(const fs::path& dir) {uint64_t size = 0;for (const auto& entry : fs::recursive_directory_iterator(dir)) {if (entry.is_regular_file()) {size += entry.file_size();}}return size;
}// 将字节数转为人类可读格式(KB, MB, GB)
std::string human_readable_size(uint64_t bytes) {const char* units[] = {"B", "KB", "MB", "GB", "TB"};int unit = 0;double size = static_cast<double>(bytes);while (size >= 1024.0 && unit < 4) {size /= 1024.0;++unit;}std::ostringstream oss;if (unit == 0) {oss << static_cast<int>(size) << " " << units[unit];} else {oss << std::fixed << std::setprecision(1) << size << " " << units[unit];}return oss.str();
}// ========== 备份逻辑 ==========
void do_backup() {set_pg_env();std::string BAK_BASE = "C:\\backup"; // Windows 路径std::string DATE = get_date();std::string YESTERDAY = get_date_offset(1);int WEEK_DAY = get_weekday();std::string BAK_DIR = BAK_BASE + "\\" + DATE + "-" + std::to_string(WEEK_DAY);std::cout << std::endl;std::string START_TIME = current_local_time_str();std::cout << "############## Physical backup start at " << START_TIME << " ##############" << std::endl;std::cout << std::endl;// 确保备份根目录存在fs::create_directories(BAK_BASE);std::string TYPE;int ret = -1;// 构建 pg_basebackup 路径(可配置)std::string PG_BASEBACKUP = "\"C:\\Program Files\\PostgreSQL\\15\\bin\\pg_basebackup.exe\"";if (WEEK_DAY == 6) {// Full backup on Saturdaystd::string cmd = PG_BASEBACKUP + " -Fp -D \"" + BAK_DIR + "\" -v";ret = exec_cmd(cmd);TYPE = "FULL";} else {std::string INCRE_BASE;if (WEEK_DAY == 0) {INCRE_BASE = BAK_BASE + "\\" + YESTERDAY + "-6";} else {INCRE_BASE = BAK_BASE + "\\" + YESTERDAY + "-" + std::to_string(WEEK_DAY - 1);}std::string manifest = INCRE_BASE + "\\backup_manifest";if (!fs::exists(manifest)) {std::cerr << "ERROR: Incremental base manifest not found: " << manifest << std::endl;return;}std::string cmd = PG_BASEBACKUP + " -Fp -D \"" + BAK_DIR + "\" -v -i \"" + manifest + "\"";ret = exec_cmd(cmd);TYPE = "INCR";}std::cout << std::endl;std::string END_TIME = current_local_time_str();std::cout << "############## Physical backup end at " << END_TIME << " ##############" << std::endl;std::cout << std::endl;if (ret == 0 && fs::exists(BAK_DIR)) {uint64_t size_bytes = get_directory_size(BAK_DIR);std::string SIZE = human_readable_size(size_bytes);std::cout << "Backup completed successfully." << std::endl;std::cout << "Backup is available at: " << BAK_DIR << "\\backup_label" << std::endl;} else {std::cerr << "Backup failed." << std::endl;}
}// ========== 清理逻辑 ==========
void do_cleanup() {std::string BAK_BASE = "C:\\backup";std::cout << std::endl;std::string START_TIME = current_local_time_str();std::cout << "############## clean up start at " << START_TIME << " ##############" << std::endl;std::cout << std::endl;auto now = file_time_type::clock::now();auto cutoff = now - days(30); // 30天前std::vector<fs::path> to_delete;for (const auto& entry : fs::directory_iterator(BAK_BASE)) {if (entry.is_directory()) {auto last_write = entry.last_write_time();if (last_write < cutoff) {std::cout << "Will delete: " << entry.path().string() << std::endl;to_delete.push_back(entry.path());}}}// 执行删除for (const auto& dir : to_delete) {try {fs::remove_all(dir);std::cout << "Deleted: " << dir.string() << std::endl;} catch (const fs::filesystem_error& e) {std::cerr << "Failed to delete " << dir << ": " << e.what() << std::endl;}}std::cout << std::endl;std::string END_TIME = current_local_time_str();std::cout << "############## clean up end at " << END_TIME << " ##############" << std::endl;std::cout << std::endl;
}// ========== 主函数 ==========
int main(int argc, char* argv[]) {if (argc != 2) {std::cerr << "Usage: " << argv[0] << " [backup|cleanup]" << std::endl;return 1;}std::string mode = argv[1];if (mode == "backup") {do_backup();} else if (mode == "cleanup") {do_cleanup();} else {std::cerr << "Unknown mode: " << mode << std::endl;return 1;}return 0;
}