Add helper for adjusting date-to-duration map.

The helper isn't used yet. The goal is to export this on /camera/<uuid>/ as
described in a TODO in design/api.md.

The next step is to keep MoonfireDatabase::CameraData::days up-to-date:

* Init: call on every recording (replacing the current aggregated query with
  a recording-by-recording query)
* InsertRecording, DeleteRecordings: call for added/removed recordings

then return it from GetCamera and pass it along to the client in
WebInterface::HandleJsonCameraDetail.
This commit is contained in:
Scott Lamb 2016-05-01 21:26:17 -07:00
parent a7bfb00083
commit 292bcbaad5
3 changed files with 153 additions and 1 deletions

View File

@ -28,6 +28,8 @@
// //
// moonfire-db-test.cc: tests of the moonfire-db.h interface. // moonfire-db-test.cc: tests of the moonfire-db.h interface.
#include <time.h>
#include <string> #include <string>
#include <gflags/gflags.h> #include <gflags/gflags.h>
@ -232,6 +234,63 @@ class MoonfireDbTest : public testing::Test {
std::unique_ptr<MoonfireDatabase> mdb_; std::unique_ptr<MoonfireDatabase> mdb_;
}; };
TEST(AdjustDaysMapTest, Basic) {
std::map<std::string, int64_t> 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. // Basic test of running some queries on an empty database.
TEST_F(MoonfireDbTest, EmptyDatabase) { TEST_F(MoonfireDbTest, EmptyDatabase) {
std::string error_message; std::string error_message;
@ -337,7 +396,6 @@ TEST_F(MoonfireDbTest, FullLifecycle) {
EXPECT_TRUE(mdb_->ListReservedSampleFiles(&reserved, &error_message)) EXPECT_TRUE(mdb_->ListReservedSampleFiles(&reserved, &error_message))
<< error_message; << error_message;
EXPECT_THAT(reserved, testing::UnorderedElementsAre(uuids[0], uuids[1])); EXPECT_THAT(reserved, testing::UnorderedElementsAre(uuids[0], uuids[1]));
LOG(INFO) << "after delete";
ExpectNoRecordings(camera_uuid); ExpectNoRecordings(camera_uuid);
EXPECT_TRUE(mdb_->MarkSampleFilesDeleted(uuids, &error_message)) EXPECT_TRUE(mdb_->MarkSampleFilesDeleted(uuids, &error_message))
@ -355,5 +413,10 @@ int main(int argc, char **argv) {
google::ParseCommandLineFlags(&argc, &argv, true); google::ParseCommandLineFlags(&argc, &argv, true);
testing::InitGoogleTest(&argc, argv); testing::InitGoogleTest(&argc, argv);
google::InitGoogleLogging(argv[0]); 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(); return RUN_ALL_TESTS();
} }

View File

@ -33,6 +33,8 @@
#include "moonfire-db.h" #include "moonfire-db.h"
#include <time.h>
#include <string> #include <string>
#include <glog/logging.h> #include <glog/logging.h>
@ -43,6 +45,78 @@
namespace moonfire_nvr { namespace moonfire_nvr {
namespace {
// Helper for AdjustDaysMap.
void AdjustDay(const std::string &day, int64_t delta,
std::map<std::string, int64_t> *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<std::string, int64_t> *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<int64_t>(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) { bool MoonfireDatabase::Init(Database *db, std::string *error_message) {
CHECK(db_ == nullptr); CHECK(db_ == nullptr);
db_ = db; db_ = db;

View File

@ -225,6 +225,12 @@ class MoonfireDatabase {
int64_t max_end_time_90k = -1; int64_t max_end_time_90k = -1;
int64_t total_sample_file_bytes = -1; int64_t total_sample_file_bytes = -1;
int64_t total_duration_90k = -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<std::string, int64_t> days;
}; };
enum class ReservationState { kWriting = 0, kDeleting = 1 }; enum class ReservationState { kWriting = 0, kDeleting = 1 };
@ -253,6 +259,15 @@ class MoonfireDatabase {
std::map<int64_t, VideoSampleEntry> video_sample_entries_; std::map<int64_t, VideoSampleEntry> 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<std::string, int64_t> *days);
} // namespace internal
} // namespace moonfire_nvr } // namespace moonfire_nvr
#endif // MOONFIRE_NVR_MOONFIRE_DB_H #endif // MOONFIRE_NVR_MOONFIRE_DB_H