Add small sqlite3 wrapper + start of schema.
This commit is contained in:
parent
a46df2c2e5
commit
9af7eb8c14
|
@ -61,6 +61,7 @@ pkg_check_modules(FFMPEG REQUIRED libavutil libavcodec libavformat)
|
|||
pkg_check_modules(LIBEVENT REQUIRED libevent)
|
||||
pkg_check_modules(GLOG REQUIRED libglog)
|
||||
pkg_check_modules(OPENSSL REQUIRED libcrypto)
|
||||
pkg_check_modules(SQLITE REQUIRED sqlite3)
|
||||
|
||||
# Check if ffmpeg support "stimeout".
|
||||
set(CMAKE_REQUIRED_INCLUDES ${FFMPEG_INCLUDES})
|
||||
|
|
|
@ -38,7 +38,8 @@ set(MOONFIRE_DEPS
|
|||
${OPENSSL_LIBRARIES}
|
||||
${PROFILER_LIBRARIES}
|
||||
${PROTOBUF_LIBRARIES}
|
||||
${RE2_LIBRARIES})
|
||||
${RE2_LIBRARIES}
|
||||
${SQLITE_LIBRARIES})
|
||||
|
||||
set(MOONFIRE_NVR_SRCS
|
||||
coding.cc
|
||||
|
@ -49,6 +50,7 @@ set(MOONFIRE_NVR_SRCS
|
|||
moonfire-nvr.cc
|
||||
profiler.cc
|
||||
recording.cc
|
||||
sqlite.cc
|
||||
string.cc
|
||||
time.cc)
|
||||
|
||||
|
@ -63,7 +65,7 @@ install_programs(/bin FILES moonfire-nvr)
|
|||
include_directories(${GTest_INCLUDE_DIR})
|
||||
include_directories(${GMock_INCLUDE_DIR})
|
||||
|
||||
foreach(test coding crypto http moonfire-nvr recording string)
|
||||
foreach(test coding crypto http moonfire-nvr recording sqlite string)
|
||||
add_executable(${test}-test ${test}-test.cc testutil.cc)
|
||||
target_link_libraries(${test}-test GTest GMock moonfire-nvr-lib)
|
||||
add_test(NAME ${test}-test
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
-- This file is part of Moonfire NVR, a security camera digital video recorder.
|
||||
-- Copyright (C) 2016 Scott Lamb <slamb@slamb.org>
|
||||
--
|
||||
-- This program is free software: you can redistribute it and/or modify
|
||||
-- it under the terms of the GNU General Public License as published by
|
||||
-- the Free Software Foundation, either version 3 of the License, or
|
||||
-- (at your option) any later version.
|
||||
--
|
||||
-- In addition, as a special exception, the copyright holders give
|
||||
-- permission to link the code of portions of this program with the
|
||||
-- OpenSSL library under certain conditions as described in each
|
||||
-- individual source file, and distribute linked combinations including
|
||||
-- the two.
|
||||
--
|
||||
-- You must obey the GNU General Public License in all respects for all
|
||||
-- of the code used other than OpenSSL. If you modify file(s) with this
|
||||
-- exception, you may extend this exception to your version of the
|
||||
-- file(s), but you are not obligated to do so. If you do not wish to do
|
||||
-- so, delete this exception statement from your version. If you delete
|
||||
-- this exception statement from all source files in the program, then
|
||||
-- also delete it here.
|
||||
--
|
||||
-- This program is distributed in the hope that it will be useful,
|
||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
-- GNU General Public License for more details.
|
||||
--
|
||||
-- You should have received a copy of the GNU General Public License
|
||||
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
--
|
||||
-- schema.sql: SQLite3 database schema for Moonfire NVR.
|
||||
-- See also design/schema.md.
|
||||
|
||||
create table camera (
|
||||
id integer primary key,
|
||||
uuid blob unique not null,
|
||||
|
||||
-- A short name of the camera, used in log messages.
|
||||
short_name text not null,
|
||||
|
||||
-- A short description of the camera.
|
||||
description text,
|
||||
|
||||
-- The host (or IP address) to use in rtsp:// URLs when accessing the camera.
|
||||
host text,
|
||||
|
||||
-- The username to use when accessing the camera.
|
||||
-- If empty, no username or password will be supplied.
|
||||
username text,
|
||||
|
||||
-- The password to use when accessing the camera.
|
||||
password text,
|
||||
|
||||
-- The path (starting with "/") to use in rtsp:// URLs to reference this
|
||||
-- camera's "main" (full-quality) video stream.
|
||||
main_rtsp_path text,
|
||||
|
||||
-- The path (starting with "/") to use in rtsp:// URLs to reference this
|
||||
-- camera's "sub" (low-bandwidth) video stream.
|
||||
sub_rtsp_path text,
|
||||
|
||||
-- The number of bytes of video to retain, excluding the currently-recording
|
||||
-- file. Older files will be deleted as necessary to stay within this limit.
|
||||
retain_bytes integer
|
||||
);
|
||||
|
||||
-- A single, typically 60-second, recorded segment of video.
|
||||
create table recording (
|
||||
id integer primary key,
|
||||
camera_id integer references camera (id) not null,
|
||||
|
||||
status integer not null, -- 0 (WRITING), 1 (WRITTEN), or 2 (DELETING)
|
||||
|
||||
sample_file_uuid blob unique not null,
|
||||
sample_file_sha1 blob,
|
||||
sample_file_size integer,
|
||||
|
||||
-- The starting and ending time of the recording, in 90 kHz units since
|
||||
-- 1970-01-01 00:00:00 UTC.
|
||||
start_time_90k integer not null,
|
||||
end_time_90k integer,
|
||||
|
||||
video_samples integer,
|
||||
video_sample_entry_sha1 blob references visual_sample_entry (sha1),
|
||||
video_index blob
|
||||
);
|
||||
|
||||
-- A concrete box derived from a ISO/IEC 14496-12 section 8.5.2
|
||||
-- VisualSampleEntry box. Describes the codec, width, height, etc.
|
||||
create table visual_sample_entry (
|
||||
-- A SHA-1 hash of |bytes|.
|
||||
sha1 blob primary key,
|
||||
|
||||
-- The width and height in pixels; must match values within
|
||||
-- |sample_entry_bytes|.
|
||||
width integer,
|
||||
height integer,
|
||||
|
||||
-- A serialized SampleEntry box, including the leading length and box
|
||||
-- type (avcC in the case of H.264).
|
||||
bytes blob
|
||||
);
|
|
@ -0,0 +1,105 @@
|
|||
// This file is part of Moonfire NVR, a security camera network video recorder.
|
||||
// Copyright (C) 2016 Scott Lamb <slamb@slamb.org>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// In addition, as a special exception, the copyright holders give
|
||||
// permission to link the code of portions of this program with the
|
||||
// OpenSSL library under certain conditions as described in each
|
||||
// individual source file, and distribute linked combinations including
|
||||
// the two.
|
||||
//
|
||||
// You must obey the GNU General Public License in all respects for all
|
||||
// of the code used other than OpenSSL. If you modify file(s) with this
|
||||
// exception, you may extend this exception to your version of the
|
||||
// file(s), but you are not obligated to do so. If you do not wish to do
|
||||
// so, delete this exception statement from your version. If you delete
|
||||
// this exception statement from all source files in the program, then
|
||||
// also delete it here.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// sqlite-test.cc: tests of the sqlite.h interface.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <glog/logging.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "sqlite.h"
|
||||
#include "string.h"
|
||||
#include "testutil.h"
|
||||
|
||||
DECLARE_bool(alsologtostderr);
|
||||
|
||||
namespace moonfire_nvr {
|
||||
namespace {
|
||||
|
||||
class SqliteTest : public testing::Test {
|
||||
protected:
|
||||
SqliteTest() {
|
||||
std::string error_message;
|
||||
CHECK(db_.Open(":memory:", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
|
||||
&error_message))
|
||||
<< error_message;
|
||||
|
||||
std::string create_sql = ReadFileOrDie("../src/schema.sql");
|
||||
CHECK(RunStatements(&db_, create_sql, &error_message)) << error_message;
|
||||
}
|
||||
|
||||
Database db_;
|
||||
};
|
||||
|
||||
TEST_F(SqliteTest, JustCreate) {}
|
||||
|
||||
TEST_F(SqliteTest, BindAndColumn) {
|
||||
std::string error_message;
|
||||
auto insert_stmt = db_.Prepare(
|
||||
"insert into camera (uuid, short_name, retain_bytes) "
|
||||
"values (?, ?, ?)",
|
||||
nullptr, &error_message);
|
||||
ASSERT_TRUE(insert_stmt != nullptr) << error_message;
|
||||
const char kBlob[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
|
||||
re2::StringPiece blob_piece = re2::StringPiece(kBlob, sizeof(kBlob));
|
||||
const char kText[] = "foo";
|
||||
const int64_t kInt64 = INT64_C(0xdeadbeeffeedface);
|
||||
ASSERT_TRUE(insert_stmt->BindBlob(1, blob_piece, &error_message))
|
||||
<< error_message;
|
||||
ASSERT_TRUE(insert_stmt->BindText(2, kText, &error_message)) << error_message;
|
||||
ASSERT_TRUE(insert_stmt->BindInt64(3, kInt64, &error_message))
|
||||
<< error_message;
|
||||
ASSERT_EQ(SQLITE_DONE, insert_stmt->Step());
|
||||
|
||||
auto select_stmt =
|
||||
db_.Prepare("select uuid, short_name, retain_bytes from camera", nullptr,
|
||||
&error_message);
|
||||
ASSERT_TRUE(select_stmt != nullptr) << error_message;
|
||||
ASSERT_EQ(SQLITE_ROW, select_stmt->Step());
|
||||
EXPECT_EQ(ToHex(blob_piece), ToHex(select_stmt->ColumnBlob(0)));
|
||||
EXPECT_EQ(kText, select_stmt->ColumnText(1).as_string());
|
||||
EXPECT_EQ(kInt64, select_stmt->ColumnInt64(2));
|
||||
ASSERT_EQ(SQLITE_DONE, select_stmt->Step());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace moonfire_nvr
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
FLAGS_alsologtostderr = true;
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
google::InitGoogleLogging(argv[0]);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
// This file is part of Moonfire NVR, a security camera digital video recorder.
|
||||
// Copyright (C) 2016 Scott Lamb <slamb@slamb.org>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// In addition, as a special exception, the copyright holders give
|
||||
// permission to link the code of portions of this program with the
|
||||
// OpenSSL library under certain conditions as described in each
|
||||
// individual source file, and distribute linked combinations including
|
||||
// the two.
|
||||
//
|
||||
// You must obey the GNU General Public License in all respects for all
|
||||
// of the code used other than OpenSSL. If you modify file(s) with this
|
||||
// exception, you may extend this exception to your version of the
|
||||
// file(s), but you are not obligated to do so. If you do not wish to do
|
||||
// so, delete this exception statement from your version. If you delete
|
||||
// this exception statement from all source files in the program, then
|
||||
// also delete it here.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// sqlite.cc: implementation of the sqlite.h interface.
|
||||
|
||||
#include "sqlite.h"
|
||||
|
||||
#include <glog/logging.h>
|
||||
|
||||
#include "string.h"
|
||||
|
||||
namespace moonfire_nvr {
|
||||
|
||||
bool Statement::BindBlob(int param, re2::StringPiece value,
|
||||
std::string *error_message) {
|
||||
int err = sqlite3_bind_blob64(me_, param, value.data(), value.size(),
|
||||
SQLITE_TRANSIENT);
|
||||
if (err != SQLITE_OK) {
|
||||
*error_message = sqlite3_errstr(err);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Statement::BindInt64(int param, int64_t value,
|
||||
std::string *error_message) {
|
||||
int err = sqlite3_bind_int64(me_, param, value);
|
||||
if (err != SQLITE_OK) {
|
||||
*error_message = sqlite3_errstr(err);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Statement::BindText(int param, re2::StringPiece value,
|
||||
std::string *error_message) {
|
||||
int err = sqlite3_bind_text64(me_, param, value.data(), value.size(),
|
||||
SQLITE_TRANSIENT, SQLITE_UTF8);
|
||||
if (err != SQLITE_OK) {
|
||||
*error_message = sqlite3_errstr(err);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
re2::StringPiece Statement::ColumnBlob(int col) {
|
||||
// Order matters: call _blob first, then _bytes.
|
||||
const void *data = sqlite3_column_blob(me_, col);
|
||||
size_t len = sqlite3_column_bytes(me_, col);
|
||||
return re2::StringPiece(reinterpret_cast<const char *>(data), len);
|
||||
}
|
||||
|
||||
int64_t Statement::ColumnInt64(int col) {
|
||||
return sqlite3_column_int64(me_, col);
|
||||
}
|
||||
|
||||
re2::StringPiece Statement::ColumnText(int col) {
|
||||
// Order matters: call _text first, then _bytes.
|
||||
const unsigned char *data = sqlite3_column_text(me_, col);
|
||||
size_t len = sqlite3_column_bytes(me_, col);
|
||||
return re2::StringPiece(reinterpret_cast<const char *>(data), len);
|
||||
}
|
||||
|
||||
int Statement::Step() { return sqlite3_step(me_); }
|
||||
|
||||
Database::~Database() {
|
||||
int err = sqlite3_close(me_);
|
||||
CHECK_EQ(SQLITE_OK, err) << "sqlite3_close: " << sqlite3_errstr(err);
|
||||
}
|
||||
|
||||
bool Database::Open(const char *filename, int flags,
|
||||
std::string *error_message) {
|
||||
int err = sqlite3_open_v2(filename, &me_, flags, nullptr);
|
||||
if (err != SQLITE_OK) {
|
||||
*error_message = sqlite3_errstr(err);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<Statement> Database::Prepare(re2::StringPiece sql, size_t *used,
|
||||
std::string *error_message) {
|
||||
std::unique_ptr<Statement> statement(new Statement);
|
||||
const char *tail;
|
||||
int err =
|
||||
sqlite3_prepare_v2(me_, sql.data(), sql.size(), &statement->me_, &tail);
|
||||
if (err != SQLITE_OK) {
|
||||
*error_message = sqlite3_errstr(err);
|
||||
statement.release();
|
||||
}
|
||||
if (used != nullptr) {
|
||||
*used = tail - sql.data();
|
||||
}
|
||||
if (statement->me_ == nullptr) {
|
||||
error_message->clear();
|
||||
statement.release();
|
||||
}
|
||||
return statement;
|
||||
}
|
||||
|
||||
bool RunStatements(Database *db, re2::StringPiece stmts,
|
||||
std::string *error_message) {
|
||||
while (true) {
|
||||
size_t used;
|
||||
auto stmt = db->Prepare(stmts, &used, error_message);
|
||||
if (stmt == nullptr) {
|
||||
// Statement didn't parse. If |error_message| is empty, there are just no
|
||||
// more statements. Otherwise this is due to an error. Either way, return.
|
||||
return error_message->empty();
|
||||
}
|
||||
VLOG(1) << "Running statement:\n" << stmts.substr(0, used).as_string();
|
||||
stmts.remove_prefix(used);
|
||||
int ret = stmt->Step();
|
||||
if (ret != SQLITE_DONE) {
|
||||
*error_message =
|
||||
StrCat("Unexpected status \"", sqlite3_errstr(ret),
|
||||
"\" from statement: \"", stmts.substr(0, used), "\"");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace moonfire_nvr
|
|
@ -0,0 +1,128 @@
|
|||
// This file is part of Moonfire NVR, a security camera digital video recorder.
|
||||
// Copyright (C) 2016 Scott Lamb <slamb@slamb.org>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// In addition, as a special exception, the copyright holders give
|
||||
// permission to link the code of portions of this program with the
|
||||
// OpenSSL library under certain conditions as described in each
|
||||
// individual source file, and distribute linked combinations including
|
||||
// the two.
|
||||
//
|
||||
// You must obey the GNU General Public License in all respects for all
|
||||
// of the code used other than OpenSSL. If you modify file(s) with this
|
||||
// exception, you may extend this exception to your version of the
|
||||
// file(s), but you are not obligated to do so. If you do not wish to do
|
||||
// so, delete this exception statement from your version. If you delete
|
||||
// this exception statement from all source files in the program, then
|
||||
// also delete it here.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// sqlite.h: a quick C++ wrapper interface around the SQLite3 C API.
|
||||
// This provides RAII and takes advantage of some types like re2::StringPiece.
|
||||
// It makes no attempt to hide how the underlying API works, so read
|
||||
// alongside: <https://www.sqlite.org/capi3ref.html>
|
||||
|
||||
#ifndef MOONFIRE_NVR_SQLITE_H
|
||||
#define MOONFIRE_NVR_SQLITE_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <re2/stringpiece.h>
|
||||
#include <sqlite3.h>
|
||||
|
||||
namespace moonfire_nvr {
|
||||
|
||||
// Thread-compatible (not thread-safe).
|
||||
class Statement {
|
||||
public:
|
||||
~Statement() { sqlite3_finalize(me_); }
|
||||
|
||||
// Bind a value (of various types).
|
||||
// |param| is 1-indexed.
|
||||
// In the case of BindBlob, |value| will be copied immediately using
|
||||
// SQLITE_TRANSIENT.
|
||||
// In the case of BindText, UTF-8 is assumed.
|
||||
bool BindBlob(int param, re2::StringPiece value, std::string *error_message);
|
||||
bool BindInt64(int param, int64_t value, std::string *error_message);
|
||||
bool BindText(int param, re2::StringPiece value, std::string *error_message);
|
||||
|
||||
bool Reset(std::string *error_message);
|
||||
|
||||
// Evaluate the statement.
|
||||
// Returns a SQLite3 result code or extended result code.
|
||||
// (Notably including SQLITE_ROW and SQLITE_DONE.)
|
||||
int Step();
|
||||
|
||||
// Retrieve a value of various types.
|
||||
// StringPiece values are only valid until a type conversion or a following
|
||||
// call to Step(), Reset(), or Statement's destructor.
|
||||
// Note that these don't have any real way to report error.
|
||||
// In particular, on error a default value is returned, but that's a valid
|
||||
// value. The error code is set on the database, but it's not guaranteed to
|
||||
// not be set otherwise.
|
||||
re2::StringPiece ColumnBlob(int col);
|
||||
int64_t ColumnInt64(int col);
|
||||
re2::StringPiece ColumnText(int col);
|
||||
|
||||
private:
|
||||
friend class Database;
|
||||
Statement() {}
|
||||
Statement(const Statement &) = delete;
|
||||
Statement &operator=(const Statement &) = delete;
|
||||
|
||||
sqlite3_stmt *me_ = nullptr;
|
||||
};
|
||||
|
||||
// Thread-safe; the database is used in the default SQLITE_CONFIG_SERIALIZED.
|
||||
class Database {
|
||||
public:
|
||||
Database() {}
|
||||
Database(const Database &) = delete;
|
||||
Database &operator=(const Database &) = delete;
|
||||
|
||||
// PRE: there are no unfinalized prepared statements or unfinished backup
|
||||
// objects.
|
||||
~Database();
|
||||
|
||||
bool Open(const char *filename, int flags, std::string *error_message);
|
||||
|
||||
// Prepare a statement.
|
||||
//
|
||||
// |used|, if non-null, will be updated with the number of bytes used from
|
||||
// |sql| on success. (Only the first statement is parsed.)
|
||||
//
|
||||
// Returns the statement, or nullptr if there is no valid statement.
|
||||
// |error_message| will be empty if there is simply no statement to parse.
|
||||
std::unique_ptr<Statement> Prepare(re2::StringPiece sql, size_t *used,
|
||||
std::string *error_message);
|
||||
|
||||
// Return the number of rows modified/inserted/deleted by the last DML
|
||||
// statement executed.
|
||||
int Changes() { return sqlite3_changes(me_); }
|
||||
|
||||
private:
|
||||
sqlite3 *me_ = nullptr;
|
||||
};
|
||||
|
||||
// Convenience routines below.
|
||||
|
||||
// Run through all the statements in |stmts|.
|
||||
// Return error if any do not parse or return something other than SQLITE_DONE
|
||||
// when stepped.
|
||||
bool RunStatements(Database *db, re2::StringPiece stmts,
|
||||
std::string *error_message);
|
||||
|
||||
} // namespace moonfire_nvr
|
||||
|
||||
#endif // MOONFIRE_NVR_SQLITE_H
|
Loading…
Reference in New Issue