diff --git a/src/moonfire-db-test.cc b/src/moonfire-db-test.cc index ddcaf4c..b3bd1bf 100644 --- a/src/moonfire-db-test.cc +++ b/src/moonfire-db-test.cc @@ -28,6 +28,8 @@ // // moonfire-db-test.cc: tests of the moonfire-db.h interface. +#include + #include #include @@ -232,6 +234,63 @@ class MoonfireDbTest : public testing::Test { std::unique_ptr mdb_; }; +TEST(AdjustDaysMapTest, Basic) { + std::map days; + + // Create a day. + const int64_t kTestTime = INT64_C(130647162600000); // 2015-12-31 23:59:00 + moonfire_nvr::internal::AdjustDaysMap( + kTestTime, kTestTime + 60 * kTimeUnitsPerSecond, 1, &days); + EXPECT_THAT(days, testing::ElementsAre(std::make_pair( + "2015-12-31", 60 * kTimeUnitsPerSecond))); + + // Add to a day. + moonfire_nvr::internal::AdjustDaysMap( + kTestTime, kTestTime + 60 * kTimeUnitsPerSecond, 1, &days); + EXPECT_THAT(days, testing::ElementsAre(std::make_pair( + "2015-12-31", 120 * kTimeUnitsPerSecond))); + + // Subtract from a day. + moonfire_nvr::internal::AdjustDaysMap( + kTestTime, kTestTime + 60 * kTimeUnitsPerSecond, -1, &days); + EXPECT_THAT(days, testing::ElementsAre(std::make_pair( + "2015-12-31", 60 * kTimeUnitsPerSecond))); + + // Remove a day. + moonfire_nvr::internal::AdjustDaysMap( + kTestTime, kTestTime + 60 * kTimeUnitsPerSecond, -1, &days); + EXPECT_THAT(days, testing::ElementsAre()); + + // Create two days. + moonfire_nvr::internal::AdjustDaysMap( + kTestTime, kTestTime + 3 * 60 * kTimeUnitsPerSecond, 1, &days); + EXPECT_THAT(days, + testing::ElementsAre( + std::make_pair("2015-12-31", 1 * 60 * kTimeUnitsPerSecond), + std::make_pair("2016-01-01", 2 * 60 * kTimeUnitsPerSecond))); + + // Add to two days. + moonfire_nvr::internal::AdjustDaysMap( + kTestTime, kTestTime + 3 * 60 * kTimeUnitsPerSecond, 1, &days); + EXPECT_THAT(days, + testing::ElementsAre( + std::make_pair("2015-12-31", 2 * 60 * kTimeUnitsPerSecond), + std::make_pair("2016-01-01", 4 * 60 * kTimeUnitsPerSecond))); + + // Subtract from two days. + moonfire_nvr::internal::AdjustDaysMap( + kTestTime, kTestTime + 3 * 60 * kTimeUnitsPerSecond, -1, &days); + EXPECT_THAT(days, + testing::ElementsAre( + std::make_pair("2015-12-31", 1 * 60 * kTimeUnitsPerSecond), + std::make_pair("2016-01-01", 2 * 60 * kTimeUnitsPerSecond))); + + // Remove two days. + moonfire_nvr::internal::AdjustDaysMap( + kTestTime, kTestTime + 3 * 60 * kTimeUnitsPerSecond, -1, &days); + EXPECT_THAT(days, testing::ElementsAre()); +} + // Basic test of running some queries on an empty database. TEST_F(MoonfireDbTest, EmptyDatabase) { std::string error_message; @@ -337,7 +396,6 @@ TEST_F(MoonfireDbTest, FullLifecycle) { EXPECT_TRUE(mdb_->ListReservedSampleFiles(&reserved, &error_message)) << error_message; EXPECT_THAT(reserved, testing::UnorderedElementsAre(uuids[0], uuids[1])); - LOG(INFO) << "after delete"; ExpectNoRecordings(camera_uuid); EXPECT_TRUE(mdb_->MarkSampleFilesDeleted(uuids, &error_message)) @@ -355,5 +413,10 @@ int main(int argc, char **argv) { google::ParseCommandLineFlags(&argc, &argv, true); testing::InitGoogleTest(&argc, argv); google::InitGoogleLogging(argv[0]); + + // The calendar day math assumes this timezone. + CHECK_EQ(0, setenv("TZ", "America/Los_Angeles", 1)) << strerror(errno); + tzset(); + return RUN_ALL_TESTS(); } diff --git a/src/moonfire-db.cc b/src/moonfire-db.cc index 5d83d8a..96da195 100644 --- a/src/moonfire-db.cc +++ b/src/moonfire-db.cc @@ -33,6 +33,8 @@ #include "moonfire-db.h" +#include + #include #include @@ -43,6 +45,78 @@ namespace moonfire_nvr { +namespace { + +// Helper for AdjustDaysMap. +void AdjustDay(const std::string &day, int64_t delta, + std::map *days) { + auto it = days->find(day); + if (it != days->end()) { + it->second += delta; + DCHECK_GE(it->second, 0) << day << ", " << delta; + if (it->second == 0) { + days->erase(it); + } + } else { + days->insert(it, std::make_pair(day, delta)); + } +} + +} // namespace + +namespace internal { + +void AdjustDaysMap(int64_t start_time_90k, int64_t end_time_90k, int sign, + std::map *days) { + // There will always be at most two days adjusted, because + // kMaxRecordingDuration is less than a day (even during spring forward). + DCHECK_LT(start_time_90k, end_time_90k); + DCHECK_LE(end_time_90k - start_time_90k, kMaxRecordingDuration); + static_assert(kMaxRecordingDuration <= 23 * 60 * kTimeUnitsPerSecond, + "max duration should be less than a (spring-forward) day"); + + // Fill |buf| with the first day. + struct tm mytm; + memset(&mytm, 0, sizeof(mytm)); + time_t start_ts = start_time_90k / kTimeUnitsPerSecond; + localtime_r(&start_ts, &mytm); + const char kFmt[] = "%Y-%m-%d"; + char buf[sizeof("YYYY-mm-DD")]; + strftime(buf, sizeof(buf), kFmt, &mytm); + + // Determine the start of the next day. + // Note that mktime(3) normalizes tm_mday, so this should work on the last + // day of the month/year. + mytm.tm_isdst = -1; + mytm.tm_hour = 0; + mytm.tm_min = 0; + mytm.tm_sec = 0; + ++mytm.tm_mday; + auto boundary_90k = kTimeUnitsPerSecond * static_cast(mktime(&mytm)); + + // Adjust the first day. + auto first_day_delta = + sign * (std::min(end_time_90k, boundary_90k) - start_time_90k); + DCHECK_NE(first_day_delta, 0) << "start=" << start_time_90k + << ", end=" << end_time_90k; + AdjustDay(buf, first_day_delta, days); + + if (end_time_90k <= boundary_90k) { + return; // no second day. + } + + // Fill |buf| with the second day. + strftime(buf, sizeof(buf), kFmt, &mytm); + + // Adjust the second day. + auto second_day_delta = sign * (end_time_90k - boundary_90k); + DCHECK_NE(second_day_delta, 0) << "start=" << start_time_90k + << ", end=" << end_time_90k; + AdjustDay(buf, second_day_delta, days); +} + +} // namespace internal + bool MoonfireDatabase::Init(Database *db, std::string *error_message) { CHECK(db_ == nullptr); db_ = db; diff --git a/src/moonfire-db.h b/src/moonfire-db.h index ce57132..aed7f65 100644 --- a/src/moonfire-db.h +++ b/src/moonfire-db.h @@ -225,6 +225,12 @@ class MoonfireDatabase { int64_t max_end_time_90k = -1; int64_t total_sample_file_bytes = -1; int64_t total_duration_90k = -1; + + // A map of calendar days (in the local timezone, "YYYY-mm-DD") to the + // total duration (in 90k units) of recorded data in the day. A day is + // present in the map ff the value is non-zero. + // TODO: actually fill this. + std::map days; }; enum class ReservationState { kWriting = 0, kDeleting = 1 }; @@ -253,6 +259,15 @@ class MoonfireDatabase { std::map video_sample_entries_; }; +namespace internal { + +// Adjust a day-to-duration map (see MoonfireDatabase::CameraData::days_) +// to reflect a recording. +void AdjustDaysMap(int64_t start_time_90k, int64_t end_time_90k, int sign, + std::map *days); + +} // namespace internal + } // namespace moonfire_nvr #endif // MOONFIRE_NVR_MOONFIRE_DB_H