2016-01-01 22:06:47 -08:00
|
|
|
// 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/>.
|
|
|
|
//
|
|
|
|
// filesystem.h: helpers for dealing with the local filesystem.
|
|
|
|
|
|
|
|
#ifndef MOONFIRE_NVR_FILESYSTEM_H
|
|
|
|
#define MOONFIRE_NVR_FILESYSTEM_H
|
|
|
|
|
|
|
|
#include <dirent.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
2016-01-02 10:48:58 -08:00
|
|
|
#include <memory>
|
2016-01-01 22:06:47 -08:00
|
|
|
#include <functional>
|
|
|
|
#include <string>
|
|
|
|
|
|
|
|
#include <event2/buffer.h>
|
|
|
|
#include <event2/http.h>
|
|
|
|
#include <glog/logging.h>
|
2016-01-09 17:16:55 -08:00
|
|
|
#include <gmock/gmock.h>
|
2016-01-01 22:06:47 -08:00
|
|
|
#include <re2/stringpiece.h>
|
|
|
|
|
2016-01-08 21:44:19 -08:00
|
|
|
#include "common.h"
|
2016-01-01 22:06:47 -08:00
|
|
|
|
2016-01-08 21:44:19 -08:00
|
|
|
namespace moonfire_nvr {
|
2016-01-01 22:06:47 -08:00
|
|
|
|
2016-01-09 17:16:55 -08:00
|
|
|
// Represents an open file descriptor. All methods but Close() are thread-safe.
|
2016-01-02 10:48:58 -08:00
|
|
|
class File {
|
|
|
|
public:
|
|
|
|
// Close the file, ignoring the result.
|
|
|
|
virtual ~File() {}
|
|
|
|
|
2016-04-30 08:51:58 -07:00
|
|
|
// A name for the file (typically assigned at open time).
|
|
|
|
virtual const std::string &name() const = 0;
|
|
|
|
|
2016-04-30 08:38:29 -07:00
|
|
|
// faccessat(), returning 0 on success or errno>0 on failure.
|
|
|
|
virtual int Access(const char *path, int mode, int flags) = 0;
|
|
|
|
|
2016-01-02 10:48:58 -08:00
|
|
|
// Close the file, returning 0 on success or errno>0 on failure.
|
|
|
|
// Already closed is considered a success.
|
|
|
|
virtual int Close() = 0;
|
|
|
|
|
2016-01-09 17:16:55 -08:00
|
|
|
// openat(), returning 0 on success or errno>0 on failure.
|
2016-01-31 22:41:30 -08:00
|
|
|
virtual int Open(const char *path, int flags, int *fd) = 0;
|
2016-01-09 17:16:55 -08:00
|
|
|
virtual int Open(const char *path, int flags, std::unique_ptr<File> *f) = 0;
|
2016-01-31 22:41:30 -08:00
|
|
|
virtual int Open(const char *path, int flags, mode_t mode, int *fd) = 0;
|
2016-01-09 17:16:55 -08:00
|
|
|
virtual int Open(const char *path, int flags, mode_t mode,
|
|
|
|
std::unique_ptr<File> *f) = 0;
|
2016-01-06 23:38:46 -08:00
|
|
|
|
2016-01-08 21:39:42 -08:00
|
|
|
// read(), returning 0 on success or errno>0 on failure.
|
|
|
|
// On success, |bytes_read| will be updated.
|
|
|
|
virtual int Read(void *buf, size_t count, size_t *bytes_read) = 0;
|
|
|
|
|
|
|
|
// fstat(), returning 0 on success or errno>0 on failure.
|
|
|
|
virtual int Stat(struct stat *buf) = 0;
|
|
|
|
|
2016-01-09 17:16:55 -08:00
|
|
|
// fsync(), returning 0 on success or errno>0 on failure.
|
|
|
|
virtual int Sync() = 0;
|
|
|
|
|
|
|
|
// ftruncate(), returning 0 on success or errno>0 on failure.
|
|
|
|
virtual int Truncate(off_t length) = 0;
|
|
|
|
|
Write using the shiny new schema
There's a lot of work left to do on this:
* important latency optimization: the recording threads block
while fsync()ing sample files, which can take 250+ ms. This
should be moved to a separate thread to happen asynchronously.
* write cycle optimizations: several SQLite commits per camera per minute.
* test coverage: this drops testing of the file rotation, and
there are several error paths worth testing.
* ffmpeg oddities to investigate:
* the out-of-order first frame's pts
* measurable delay before returning packets
* it sometimes returns an initial packet it calls a "key" frame that actually
has an SEI recovery point NAL but not an IDR-coded slice NAL, even though
in the input these always seem to come together. This makes playback
starting from this recording not work at all on Chrome. The symptom is
that it loads a player-looking thing with the proper dimensions but
playback never actually starts.
I imagine these are all related but haven't taken the time to dig through
ffmpeg code and understand them. The right thing anyway may be to ditch
ffmpeg for RTSP streaming (perhaps in favor of the live555 library), as
it seems to have other omissions like making it hard/impossible to take
advantage of Sender Reports. In the meantime, I attempted to mitigate
problems by decreasing ffmpeg's probesize.
* handling overlapping recordings: right now if there's too much time drift or
a time jump, you can end up with recordings that the UI won't play without
manual database changes. It's not obvious what the right thing to do is.
* easy camera setup: currently you have to manually insert rows in the SQLite
database and restart.
but I think it's best to get something in to iterate from.
This deletes a lot of code, including:
* the ffmpeg video sink code (instead now using a bit of extra code in Stream
on top of the SampleFileWriter, SampleIndexEncoder, and MoonfireDatabase
code that's been around for a while)
* FileManager (in favor of new code using the database)
* the old UI
* RealFile and friends
* the dependency on protocol buffers, which was used for the config file
(though I'll likely have other reasons for using protocol buffers later)
* even some utilities like IsWord that were just for validating the config
2016-02-03 23:22:37 -08:00
|
|
|
// unlink() the specified file, returning 0 on success or errno>0 on failure.
|
|
|
|
virtual int Unlink(const char *path) = 0;
|
|
|
|
|
2016-01-02 10:48:58 -08:00
|
|
|
// Write to the file, returning 0 on success or errno>0 on failure.
|
2016-01-06 23:38:46 -08:00
|
|
|
// On success, |bytes_written| will be updated.
|
|
|
|
virtual int Write(re2::StringPiece data, size_t *bytes_written) = 0;
|
2016-01-02 10:48:58 -08:00
|
|
|
};
|
|
|
|
|
2016-01-09 17:16:55 -08:00
|
|
|
class MockFile : public File {
|
|
|
|
public:
|
2016-04-30 08:51:58 -07:00
|
|
|
MOCK_CONST_METHOD0(name, const std::string &());
|
2016-04-30 08:38:29 -07:00
|
|
|
MOCK_METHOD3(Access, int(const char *, int, int));
|
2016-01-09 17:16:55 -08:00
|
|
|
MOCK_METHOD0(Close, int());
|
|
|
|
|
2016-01-31 22:41:30 -08:00
|
|
|
// The std::unique_ptr<File> variants of Open are wrapped here because gmock's
|
|
|
|
// SetArgPointee doesn't work well with std::unique_ptr.
|
2016-01-09 17:16:55 -08:00
|
|
|
|
|
|
|
int Open(const char *path, int flags, std::unique_ptr<File> *f) final {
|
|
|
|
File *f_tmp = nullptr;
|
|
|
|
int ret = OpenRaw(path, flags, &f_tmp);
|
|
|
|
f->reset(f_tmp);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Open(const char *path, int flags, mode_t mode,
|
|
|
|
std::unique_ptr<File> *f) final {
|
|
|
|
File *f_tmp = nullptr;
|
|
|
|
int ret = OpenRaw(path, flags, mode, &f_tmp);
|
|
|
|
f->reset(f_tmp);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-01-31 22:41:30 -08:00
|
|
|
MOCK_METHOD3(Open, int(const char *, int, int *));
|
|
|
|
MOCK_METHOD4(Open, int(const char *, int, mode_t, int *));
|
2016-01-09 17:16:55 -08:00
|
|
|
MOCK_METHOD3(OpenRaw, int(const char *, int, File **));
|
|
|
|
MOCK_METHOD4(OpenRaw, int(const char *, int, mode_t, File **));
|
|
|
|
MOCK_METHOD3(Read, int(void *, size_t, size_t *));
|
|
|
|
MOCK_METHOD1(Stat, int(struct stat *));
|
|
|
|
MOCK_METHOD0(Sync, int());
|
|
|
|
MOCK_METHOD1(Truncate, int(off_t));
|
Write using the shiny new schema
There's a lot of work left to do on this:
* important latency optimization: the recording threads block
while fsync()ing sample files, which can take 250+ ms. This
should be moved to a separate thread to happen asynchronously.
* write cycle optimizations: several SQLite commits per camera per minute.
* test coverage: this drops testing of the file rotation, and
there are several error paths worth testing.
* ffmpeg oddities to investigate:
* the out-of-order first frame's pts
* measurable delay before returning packets
* it sometimes returns an initial packet it calls a "key" frame that actually
has an SEI recovery point NAL but not an IDR-coded slice NAL, even though
in the input these always seem to come together. This makes playback
starting from this recording not work at all on Chrome. The symptom is
that it loads a player-looking thing with the proper dimensions but
playback never actually starts.
I imagine these are all related but haven't taken the time to dig through
ffmpeg code and understand them. The right thing anyway may be to ditch
ffmpeg for RTSP streaming (perhaps in favor of the live555 library), as
it seems to have other omissions like making it hard/impossible to take
advantage of Sender Reports. In the meantime, I attempted to mitigate
problems by decreasing ffmpeg's probesize.
* handling overlapping recordings: right now if there's too much time drift or
a time jump, you can end up with recordings that the UI won't play without
manual database changes. It's not obvious what the right thing to do is.
* easy camera setup: currently you have to manually insert rows in the SQLite
database and restart.
but I think it's best to get something in to iterate from.
This deletes a lot of code, including:
* the ffmpeg video sink code (instead now using a bit of extra code in Stream
on top of the SampleFileWriter, SampleIndexEncoder, and MoonfireDatabase
code that's been around for a while)
* FileManager (in favor of new code using the database)
* the old UI
* RealFile and friends
* the dependency on protocol buffers, which was used for the config file
(though I'll likely have other reasons for using protocol buffers later)
* even some utilities like IsWord that were just for validating the config
2016-02-03 23:22:37 -08:00
|
|
|
MOCK_METHOD1(Unlink, int(const char *));
|
|
|
|
MOCK_METHOD2(Write, int(re2::StringPiece, size_t *));
|
2016-01-09 17:16:55 -08:00
|
|
|
};
|
|
|
|
|
2016-01-02 10:48:58 -08:00
|
|
|
// Interface to the local filesystem. There's typically one per program,
|
|
|
|
// but it's an abstract class for testability. Thread-safe.
|
|
|
|
class Filesystem {
|
|
|
|
public:
|
|
|
|
virtual ~Filesystem() {}
|
|
|
|
|
|
|
|
// Execute |fn| for each directory entry in |dir_path|, stopping early
|
|
|
|
// (successfully) if the callback returns IterationControl::kBreak.
|
|
|
|
//
|
|
|
|
// On success, returns true.
|
|
|
|
// On failure, returns false and updates |error_msg|.
|
|
|
|
virtual bool DirForEach(const char *dir_path,
|
|
|
|
std::function<IterationControl(const dirent *)> fn,
|
|
|
|
std::string *error_msg) = 0;
|
|
|
|
|
|
|
|
// open() the specified path, returning 0 on success or errno>0 on failure.
|
|
|
|
// On success, |f| is populated with an open file.
|
|
|
|
virtual int Open(const char *path, int flags, std::unique_ptr<File> *f) = 0;
|
|
|
|
virtual int Open(const char *path, int flags, mode_t mode,
|
|
|
|
std::unique_ptr<File> *f) = 0;
|
|
|
|
|
|
|
|
// mkdir() the specified path, returning 0 on success or errno>0 on failure.
|
|
|
|
virtual int Mkdir(const char *path, mode_t mode) = 0;
|
|
|
|
|
|
|
|
// rmdir() the specified path, returning 0 on success or errno>0 on failure.
|
|
|
|
virtual int Rmdir(const char *path) = 0;
|
|
|
|
|
|
|
|
// stat() the specified path, returning 0 on success or errno>0 on failure.
|
|
|
|
virtual int Stat(const char *path, struct stat *buf) = 0;
|
|
|
|
|
|
|
|
// unlink() the specified file, returning 0 on success or errno>0 on failure.
|
|
|
|
virtual int Unlink(const char *path) = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Get the (singleton) real filesystem, which is never deleted.
|
|
|
|
Filesystem *GetRealFilesystem();
|
2016-01-01 22:06:47 -08:00
|
|
|
|
|
|
|
} // namespace moonfire_nvr
|
|
|
|
|
|
|
|
#endif // MOONFIRE_NVR_FILESYSTEM_H
|