From 9cff02f30c1c80cafbf11aefe2e690114ecab71d Mon Sep 17 00:00:00 2001 From: cxh Date: Wed, 26 Nov 2025 09:13:11 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BD=95=E5=83=8F=E7=A9=BA=E9=97=B4=E7=AE=A1?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/record_manager.hpp | 8 ++ src/record_manager.cpp | 150 +++++++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+) diff --git a/include/record_manager.hpp b/include/record_manager.hpp index e3292f9..2cdb8f9 100644 --- a/include/record_manager.hpp +++ b/include/record_manager.hpp @@ -60,6 +60,10 @@ class RecordManager bool loadSrsConfig(); // 解析配置文件,填充 record_dir_ 和 dvr_duration_sec_ + void removeExpiredDays(); + void removeOldestHoursUntilSafe(); + void cleanupStorage(); + // === 文件列表索引 === std::unordered_map> index_; std::mutex index_mutex_; @@ -76,6 +80,10 @@ class RecordManager std::thread scan_thread_; std::atomic running_{false}; int scan_interval_sec_ = 60; + + // ==== Storage management config ==== + int retention_days_ = 14; // 保留最近 N 天 + double usage_threshold_ = 0.90; // 超过 90% 就进行空间清理 }; extern std::shared_ptr g_record_manager; diff --git a/src/record_manager.cpp b/src/record_manager.cpp index 2f1c14d..2d8d0b6 100644 --- a/src/record_manager.cpp +++ b/src/record_manager.cpp @@ -1,5 +1,7 @@ #include "record_manager.hpp" +#include + #include #include #include @@ -123,6 +125,153 @@ bool RecordManager::loadSrsConfig() return true; } +void RecordManager::removeExpiredDays() +{ + namespace fs = std::filesystem; + + auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + + for (auto& streamEntry : fs::directory_iterator(record_dir_)) + { + if (!streamEntry.is_directory()) continue; + + for (auto& dayEntry : fs::directory_iterator(streamEntry)) + { + if (!dayEntry.is_directory()) continue; + + std::string dayName = dayEntry.path().filename().string(); // 2025-11-14 + std::tm tm{}; + + if (sscanf(dayName.c_str(), "%d-%d-%d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday) != 3) continue; + + tm.tm_year -= 1900; + tm.tm_mon -= 1; + time_t dayTime = mktime(&tm); + + double daysOld = difftime(now, dayTime) / 86400.0; + + if (daysOld > retention_days_) + { + LOG_WARN("[RecordManager] Removing expired day: " + dayEntry.path().string()); + fs::remove_all(dayEntry.path()); + } + } + } +} + +void RecordManager::removeOldestHoursUntilSafe() +{ + namespace fs = std::filesystem; + + while (true) + { + // 1) 检查磁盘 + struct statvfs vfs{}; + if (statvfs(record_dir_.c_str(), &vfs) != 0) return; + + double used = 1.0 - (double)vfs.f_bavail / (double)vfs.f_blocks; + if (used < usage_threshold_) break; + + // 2) 找全盘最旧的 day + fs::path oldestDay; + time_t oldestTime = 0; + + for (auto& streamDir : fs::directory_iterator(record_dir_)) + { + if (!streamDir.is_directory()) continue; + + for (auto& dayDir : fs::directory_iterator(streamDir)) + { + if (!dayDir.is_directory()) continue; + + std::string dayName = dayDir.path().filename().string(); + std::tm tm{}; + if (sscanf(dayName.c_str(), "%d-%d-%d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday) != 3) continue; + + tm.tm_year -= 1900; + tm.tm_mon -= 1; + time_t t = mktime(&tm); + + if (oldestDay.empty() || t < oldestTime) + { + oldestTime = t; + oldestDay = dayDir.path(); + } + } + } + + if (oldestDay.empty()) + { + LOG_WARN("[RecordManager] No day folder found for hour cleanup."); + break; + } + + // ---- 找该 day 下最旧的 hour ---- + fs::path oldestHour; + int oldestHourValue = -1; + + for (auto& hourDir : fs::directory_iterator(oldestDay)) + { + if (!hourDir.is_directory()) continue; + + std::string hourName = hourDir.path().filename().string(); + int h = -1; + if (sscanf(hourName.c_str(), "%d", &h) != 1) continue; + + if (oldestHourValue == -1 || h < oldestHourValue) + { + oldestHourValue = h; + oldestHour = hourDir.path(); + } + } + + if (oldestHour.empty()) + { + LOG_WARN("[RecordManager] Day empty, remove whole day: " + oldestDay.string()); + fs::remove_all(oldestDay); + continue; + } + + // ---- 删除最旧 hour ---- + LOG_WARN("[RecordManager] Removing oldest hour: " + oldestHour.string()); + fs::remove_all(oldestHour); + + // 如果此 day 已空 → 删除 day 目录 + if (fs::is_empty(oldestDay)) + { + LOG_WARN("[RecordManager] Removing empty day: " + oldestDay.string()); + fs::remove_all(oldestDay); + } + } +} + +void RecordManager::cleanupStorage() +{ + struct statvfs vfs{}; + if (statvfs(record_dir_.c_str(), &vfs) != 0) + { + LOG_ERROR("[RecordManager] statvfs failed for " + record_dir_); + return; + } + + double used = 1.0 - (double)vfs.f_bavail / (double)vfs.f_blocks; + + // 1) 删除超期天 + removeExpiredDays(); + + // 重新检查磁盘 + if (statvfs(record_dir_.c_str(), &vfs) == 0) + { + used = 1.0 - (double)vfs.f_bavail / (double)vfs.f_blocks; + } + + // 2) 使用率仍超 → 按小时清理 + if (used >= usage_threshold_) + { + removeOldestHoursUntilSafe(); + } +} + void RecordManager::startAutoScan(int interval_sec) { scan_interval_sec_ = interval_sec; @@ -141,6 +290,7 @@ void RecordManager::startAutoScan(int interval_sec) auto t0 = std::chrono::steady_clock::now(); this->scanAll(); + this->cleanupStorage(); LOG_INFO("[RecordManager] scanAll() completed."); // 休眠剩余时间(支持快速退出)