Compare commits

...

13 Commits

Author SHA1 Message Date
zii
045aa25f6d v3.6 - release to master 2025-02-23 09:32:35 -05:00
zii
492fa88585 v3.6.t10
-fixed default magick command to work with new versions that doesn't
 actually have 'magick' in it.
2025-02-23 09:24:35 -05:00
zii
5093ee6c8b v3.6.t9
-updated ramdisk class so the project is compilable on older qt
 builds < v6.5.
-more fixes to build.py to work with newer versions of qt and
 less manual steps to take.
2025-02-02 16:01:48 -05:00
zii
f4b2f0ce72 -updated build.py for proper qt6 support 2025-02-02 09:36:15 -05:00
zii
e0a6294e87 v3.6.t8
-created an internal ram disk to buffer streaming from the cameras
-the internal ram disk now directly send finished files to detect
 loop instead of relying on directory listing
2025-02-01 09:59:19 -05:00
zii
1a76159b91 v3.6.t7
-previous 3.6.tx commits are deemed failures, rolled back to v3.5
 code from master.
2024-11-21 19:06:59 -05:00
zii
bb998da739 v3.6.t6
-ffmpeg stall has re-surfaced, not entirely sure what causes it
 added proper logic that will actually start the restart timer
 when ffmpeg is running.
2024-10-09 20:55:23 -04:00
zii
baa6c0f217 v3.6.t5
-fixed the ffmpmeg cmd line formatting so it stops rewirting into
 the same video file.
2024-10-09 15:48:29 -04:00
zii
69bb706b6b v3.6.t4
-ffmpeg arbitrarily limits %020d. backed it down %014d, not sure if
 that is true limit. 14 was tessted to work.

-added file size checking in detect loop to prevent it from
 processesing unfinished video clips.
2024-10-09 14:39:11 -04:00
zii
7ba06ab92b v3.6.t3
-fixed some more issues with the increment naming.
2024-10-09 13:44:25 -04:00
zii
d9cdd30877 v3.6.t2
-fixed an issue that prevented the name seed value from
 incrementing
2024-10-09 13:00:35 -04:00
zii
ba54b5b44a v3.6.t1
-updated the way the video clips from the live stream are named to
 simple unsigned int number increments. doing this makes the file
 names much more predictable and removes the need for filesystem
 monitoring.
2024-10-09 12:38:49 -04:00
zii
88ea1086f6 v3.6.t1
-all recordings, clips and snapshots are now base on UTC naming
 scheme for easy name prediction for client apps.

-detect loop no longer list dirs for files or monitors for dir
 changes. it will now predict video clip names base on current
 UTC time.
2024-08-04 14:42:13 -04:00
18 changed files with 1311 additions and 297 deletions

View File

@ -3,6 +3,8 @@ QT -= gui
CONFIG += c++11 console
CONFIG -= app_bundle
LIBS += -lfuse3
TARGET = build/jmotion
OBJECTS_DIR = build
MOC_DIR = build
@ -14,6 +16,7 @@ HEADERS += \
src/detect_loop.h \
src/event_loop.h \
src/proc_control.h \
src/ram_disk.h \
src/record_loop.h
SOURCES += \
@ -22,5 +25,6 @@ SOURCES += \
src/detect_loop.cpp \
src/event_loop.cpp \
src/proc_control.cpp \
src/ram_disk.cpp \
src/record_loop.cpp \
src/main.cpp

View File

@ -19,13 +19,33 @@ def get_app_name(text):
def get_qt_path():
try:
return str(subprocess.check_output(["qtpaths", "--binaries-dir"]), 'utf-8').strip()
if os.path.exists("/usr/lib/qt6/bin"):
return "/usr/lib/qt6/bin"
return str(subprocess.check_output(["qtpaths6", "--binaries-dir"]), 'utf-8').strip()
except:
print("A direct call to 'qtpaths' has failed so automatic retrieval of the QT bin folder is not possible.")
print("A direct call to 'qtpaths6' has failed so automatic retrieval of the QT bin folder is not possible.")
return input("Please enter the QT bin path (leave blank to cancel the build): ")
def get_qt_lib_path(qt_bin):
output = str(subprocess.check_output([qt_bin + "/qtpaths6", "--query"]), 'utf-8').strip()
lines = output.split("\n")
ret = ""
for line in lines:
if line.startswith("QT_HOST_LIBS:"):
ret = line[13:]
if ret == "":
print("failed automatic retrieval of the QT lib folder.")
return input("Please enter the QT lib path (leave blank to cancel the build): ")
else:
return ret
def get_qt_from_cli():
for arg in sys.argv:
if arg == "-qt_dir":
@ -90,7 +110,7 @@ def verbose_copy(src, dst):
else:
print("wrn: " + src + " does not exists. skipping.")
def linux_build_app_dir(app_ver, app_name, app_target, qt_bin):
def linux_build_app_dir(app_ver, app_name, app_target, qt_bin, qt_lib):
if not os.path.exists("app_dir/lib"):
os.makedirs("app_dir/lib")
@ -118,12 +138,8 @@ def linux_build_app_dir(app_ver, app_name, app_target, qt_bin):
file_name = os.path.basename(src_file)
verbose_copy(src_file, "app_dir/lib/" + file_name)
if "/usr/lib/x86_64-linux-gnu/qt6/bin" == qt_bin:
verbose_copy(qt_bin + "/../../libQt6DBus.so.6", "app_dir/lib/libQt6DBus.so.6")
else:
verbose_copy(qt_bin + "/../lib/libQt6DBus.so.6", "app_dir/lib/libQt6DBus.so.6")
verbose_copy(qt_lib + "/libQt6DBus.so.6", "app_dir/lib/libQt6DBus.so.6")
verbose_copy("templates/linux_run_script.sh", "app_dir/" + app_target + ".sh")
verbose_copy("templates/linux_uninstall.sh", "app_dir/uninstall.sh")
@ -183,7 +199,7 @@ def platform_setup():
ins_packages = list_installed_packages()
like_distro = get_like_distro()
dep_pkgs_a = ["pkg-config", "make", "g++"]
dep_pkgs_b = ["ffmpeg", "libfuse-dev", "fuse3", "imagemagick"]
dep_pkgs_b = ["ffmpeg", "libfuse3-dev", "libfuse-dev", "fuse3", "imagemagick", "qt6-base-dev"]
if not list_of_words_in_text(dep_pkgs_a, ins_packages) or not list_of_words_in_text(dep_pkgs_b, ins_packages):
if ("ubuntu" in like_distro) or ("debian" in like_distro) or ("linuxmint" in like_distro):
@ -218,10 +234,16 @@ def main():
qt_bin = get_qt_path()
if qt_bin != "":
qt_lib = get_qt_lib_path(qt_bin)
if qt_lib == "":
exit(1)
print("app_target = " + app_target)
print("app_version = " + app_ver)
print("app_name = " + app_name)
print("qt_bin = " + qt_bin)
print("qt_lib = " + qt_lib)
cd()
@ -241,7 +263,7 @@ def main():
info_file.write(app_ver + "\n")
info_file.write(app_name + "\n")
linux_build_app_dir(app_ver, app_name, app_target, qt_bin)
linux_build_app_dir(app_ver, app_name, app_target, qt_bin, qt_lib)
if __name__ == "__main__":

View File

@ -39,7 +39,7 @@ def make_install_dir(path, false_on_fail):
return True
def make_app_dirs(app_target):
return make_install_dir("/etc/" + app_target, True) and make_install_dir("/opt/" + app_target, True) and make_install_dir("/var/buffer", False) and make_install_dir("/var/footage", False)
return make_install_dir("/etc/" + app_target, True) and make_install_dir("/opt/" + app_target, True) and make_install_dir("/var/local/" + app_target, False)
def replace_bin(binary, old_bin, new_bin, offs):
while(True):
@ -123,13 +123,12 @@ def local_install(app_target, app_name):
subprocess.run(["chmod", "755", install_dir + "/uninstall.sh"])
subprocess.run(["chmod", "644", "/lib/systemd/system/" + app_target + ".service"])
subprocess.run(["chmod", "777", "/var/buffer"])
subprocess.run(["chmod", "777", "/var/footage"])
if not user_exists(app_target):
subprocess.run(["sudo", "useradd", "-r", app_target])
subprocess.run(["sudo", "usermod", "-aG", "video", app_target])
subprocess.run(["chown", app_target + ":" + app_target, "/var/local/" + app_target])
subprocess.run(["systemctl", "start", app_target])
subprocess.run(["systemctl", "enable", app_target])

View File

@ -12,13 +12,16 @@
#include "camera.h"
Camera::Camera(const QString &confFile, const QString &statDir, ProcControl *proc, QCoreApplication *parent) : QObject(nullptr)
Camera::Camera(const shared_t &glbShare, const QString &confFile, const QString &statDir, RamDisk *ram, ProcControl *proc, QCoreApplication *parent) : QObject(nullptr)
{
Q_UNUSED(parent);
fsW = new QFileSystemWatcher(this);
statTimer = new QTimer(this);
shared = glbShare;
procCon = proc;
statPath = statDir;
ramDisk = ram;
evtLoop = nullptr;
detLoop = nullptr;
recLoop = nullptr;
@ -27,11 +30,9 @@ Camera::Camera(const QString &confFile, const QString &statDir, ProcControl *pro
statTimer->setInterval(5000);
statTimer->start();
proc->objPlusOne();
connect(fsW, &QFileSystemWatcher::fileChanged, this, &Camera::confChanged);
connect(proc, &ProcControl::prepForClose, this, &Camera::prepForDel);
connect(this, &Camera::destroyed, proc, &ProcControl::objMinusOne);
connect(statTimer, &QTimer::timeout, this, &Camera::updateStat);
start(confFile);
@ -49,6 +50,8 @@ void Camera::objMinusOne()
if (delOnZero)
{
procCon->objMinusOne(shared.conf);
QDir(shared.buffPath).removeRecursively();
deleteLater();
@ -58,6 +61,7 @@ void Camera::objMinusOne()
void Camera::prepForDel()
{
ramDisk->rmCam(shared.camName);
statTimer->blockSignals(true);
delOnZero = true;
@ -76,16 +80,22 @@ void Camera::updateStat()
file.close();
}
int Camera::start(const QString &conf)
void Camera::start(const QString &conf)
{
if (rdConf(conf, &shared))
if (!rdConf(conf, &shared))
{
setupBuffDir(shared.buffPath, true);
deleteLater();
}
else
{
ramDisk->mkdir(QString("/" + shared.camName).toUtf8().data(), 0);
ramDisk->mkdir(QString("/" + shared.camName + "/vid").toUtf8().data(), 0);
ramDisk->mkdir(QString("/" + shared.camName + "/img").toUtf8().data(), 0);
if (!fsW->files().contains(conf))
{
fsW->addPath(conf);
}
ramDisk->addCam(shared.camName);
procCon->objPlusOne(shared.conf);
if (!fsW->files().contains(conf)) fsW->addPath(conf);
thr1 = new QThread(nullptr);
thr2 = new QThread(nullptr);
@ -103,35 +113,29 @@ int Camera::start(const QString &conf)
connect(thr2, &QThread::finished, this, &Camera::objMinusOne);
connect(thr3, &QThread::finished, this, &Camera::objMinusOne);
connect(detLoop, &DetectLoop::starving, recLoop, &RecordLoop::restart);
connect(detLoop, &DetectLoop::starving, recLoop, &RecordLoop::restart);
connect(ramDisk, &RamDisk::watchedFileFlushed, detLoop, &DetectLoop::fileFlushed);
thr1->start();
thr2->start();
thr3->start();
objCount = 3;
}
return shared.retCode;
auto global = QDir::cleanPath(shared.confPath) + "/global";
if (QFileInfo::exists(global))
{
rdConf(global, &shared, false);
}
}
}
void Camera::confChanged(const QString &path)
{
emit stop();
Q_UNUSED(path);
if (!QFileInfo::exists(path))
{
deleteLater();
}
else
{
auto ret = start(path);
if (ret != 0)
{
deleteLater();
}
}
prepForDel();
}
QString Camera::statusLine()

View File

@ -18,6 +18,7 @@
#include "detect_loop.h"
#include "record_loop.h"
#include "proc_control.h"
#include "ram_disk.h"
class Camera : public QObject
{
@ -29,10 +30,12 @@ private:
EventLoop *evtLoop;
DetectLoop *detLoop;
RecordLoop *recLoop;
RamDisk *ramDisk;
QThread *thr1;
QThread *thr2;
QThread *thr3;
QTimer *statTimer;
ProcControl *procCon;
QString statPath;
shared_t shared;
uint objCount;
@ -47,9 +50,9 @@ private slots:
public:
explicit Camera(const QString &confFile, const QString &statDir, ProcControl *proc, QCoreApplication *parent);
explicit Camera(const shared_t &glbShare, const QString &confFile, const QString &statDir, RamDisk *ram, ProcControl *proc, QCoreApplication *parent);
int start(const QString &conf);
void start(const QString &conf);
QString statusLine();
signals:

View File

@ -102,11 +102,11 @@ void enforceMaxImages(shared_t *share)
void enforceMaxClips(shared_t *share)
{
auto names = lsFilesInDir(share->buffPath + "/live", share->streamExt);
auto names = lsFilesInDir(share->buffPath + "/vid", share->streamExt);
while (names.size() > (share->liveSecs / 2))
{
QFile::remove(share->buffPath + "/live/" + names[0]);
QFile::remove(share->buffPath + "/vid/" + names[0]);
names.removeFirst();
}
@ -128,6 +128,14 @@ void rdLine(const QString &param, const QString &line, int *value)
}
}
void rdLine(const QString &param, const QString &line, quint64 *value)
{
if (line.startsWith(param))
{
*value = line.mid(param.size()).trimmed().toULongLong();
}
}
void rdLine(const QString &param, const QString &line, bool *value)
{
if (line.startsWith(param))
@ -158,42 +166,44 @@ bool mkPath(const QString &path)
return ret;
}
bool rdConf(const QString &filePath, shared_t *share)
bool rdConf(const QString &filePath, shared_t *share, bool reset)
{
auto ret = true;
QFile varFile(filePath);
if (!varFile.open(QFile::ReadOnly))
{
share->retCode = ENOENT;
QTextStream(stderr) << "err: config file - " << filePath << " does not exists or lack read permissions." << Qt::endl;
ret = false;
}
else
{
share->recordUri.clear();
share->postCmd.clear();
share->camName.clear();
share->buffPath.clear();
share->recPath.clear();
if (reset)
{
share->recordUri.clear();
share->postCmd.clear();
share->camName.clear();
share->recPath.clear();
share->retCode = 0;
share->imgThresh = 8000;
share->maxEvents = 30;
share->skipCmd = false;
share->postSecs = 60;
share->evMaxSecs = 30;
share->conf = filePath;
share->outputType = "stderr";
share->compCmd = "magick compare -metric FUZZ " + QString(PREV_IMG) + " " + QString(NEXT_IMG) + " /dev/null";
share->vidCodec = "copy";
share->audCodec = "copy";
share->streamExt = ".mkv";
share->recExt = ".mkv";
share->thumbExt = ".jpg";
share->recFps = 30;
share->liveSecs = 80;
share->recScale = "1280:720";
share->imgScale = "320:240";
share->imgThresh = 8000;
share->maxEvents = 30;
share->postSecs = 60;
share->evMaxSecs = 30;
share->conf = filePath;
share->outputType = "stderr";
share->compCmd = "compare -metric FUZZ " + QString(PREV_IMG) + " " + QString(NEXT_IMG) + " /dev/null";
share->vidCodec = "copy";
share->audCodec = "copy";
share->streamExt = ".mkv";
share->recExt = ".mkv";
share->thumbExt = ".jpg";
share->recFps = 30;
share->liveSecs = 80;
share->recScale = "1280:720";
share->imgScale = "320:240";
}
QString line;
@ -205,7 +215,6 @@ bool rdConf(const QString &filePath, shared_t *share)
{
rdLine("cam_name = ", line, &share->camName);
rdLine("recording_uri = ", line, &share->recordUri);
rdLine("buffer_path = ", line, &share->buffPath);
rdLine("rec_path = ", line, &share->recPath);
rdLine("max_event_secs = ", line, &share->evMaxSecs);
rdLine("post_secs = ", line, &share->postSecs);
@ -236,29 +245,27 @@ bool rdConf(const QString &filePath, shared_t *share)
extCorrection(share->recExt);
extCorrection(share->thumbExt);
share->buffPath = QDir::cleanPath(share->varPath) + "/stream/" + share->camName;
if (share->outputType != "stdout" && share->outputType != "stderr")
{
share->outputType = "stderr";
}
if (share->buffPath.isEmpty())
{
share->buffPath = "/var/buffer/" + share->camName;
}
else
{
share->buffPath = QDir::cleanPath(share->buffPath);
}
if (share->recPath.isEmpty())
{
share->recPath = "/var/footage/" + share->camName;
share->recPath = QDir::cleanPath(share->varPath) + "/footage/" + share->camName;
}
else
{
share->recPath = QDir::cleanPath(share->recPath);
}
if (!QFileInfo::exists(share->recPath))
{
mkPath(share->recPath);
}
if (share->liveSecs < 10)
{
share->liveSecs = 10;
@ -270,30 +277,7 @@ bool rdConf(const QString &filePath, shared_t *share)
}
}
return share->retCode == 0;
}
void setupBuffDir(const QString &path, bool del)
{
if (del)
{
QDir(path).removeRecursively();
}
if (!QFileInfo::exists(path))
{
mkPath(path);
}
if (!QFileInfo::exists(path + "/live"))
{
mkPath(path + "/live");
}
if (!QFileInfo::exists(path + "/img"))
{
mkPath(path + "/img");
}
return ret;
}
QString buildThreadCount(int count)

View File

@ -13,6 +13,8 @@
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
#define FUSE_USE_VERSION 31
#include <QCoreApplication>
#include <QProcess>
#include <QTextStream>
@ -28,17 +30,25 @@
#include <QFileSystemWatcher>
#include <QDebug>
#include <QElapsedTimer>
#include <QMutex>
#include <iostream>
#include <fuse3/fuse.h>
#include <fuse3/fuse_lowlevel.h>
#include <fuse3/fuse_common.h>
#include <errno.h>
#include <cstring>
#include <unistd.h>
using namespace std;
#define APP_VERSION "3.5"
#define APP_VERSION "3.6"
#define APP_NAME "JustMotion"
#define APP_TARGET "jmotion"
#define DATETIME_FMT "yyyyMMddhhmmss"
#define STRFTIME_FMT "%Y%m%d%H%M%S"
#define PREV_IMG "&prev&"
#define NEXT_IMG "&next&"
#define BLK_SIZE 262144
enum CmdExeType
{
@ -46,22 +56,41 @@ enum CmdExeType
VID_LOOP
};
class ProcControl;
class Camera;
struct evt_t
{
QList<QString> vidList;
QString timeStamp;
QString imgPath;
float score;
bool inQue;
int queAge;
QStringList vidList;
QString timeStamp;
QString imgPath;
float score;
bool inQue;
int queAge;
};
class file_t
{
public:
quint64 ino;
quint64 ctime;
quint64 mtime;
quint32 mode;
qint32 blks;
QByteArray bytes;
};
struct shared_t
{
QList<evt_t> recList;
QString rdMnt;
QString conf;
QString confPath;
QString recordUri;
QString buffPath;
QString varPath;
QString postCmd;
QString camName;
QString recPath;
@ -74,8 +103,7 @@ struct shared_t
QString thumbExt;
QString recScale;
QString imgScale;
bool singleTenant;
bool skipCmd;
quint64 maxMemSize;
int liveSecs;
int recFps;
int evMaxSecs;
@ -85,6 +113,8 @@ struct shared_t
int retCode;
};
QString buildThreadCount(int count);
QStringList lsFilesInDir(const QString &path, const QString &ext = QString());
QStringList lsDirsInDir(const QString &path);
@ -92,11 +122,11 @@ QStringList listFacingFiles(const QString &path, const QString &ext, const QDate
QStringList backwardFacingFiles(const QString &path, const QString &ext, const QDateTime &stamp, int secs);
QStringList forwardFacingFiles(const QString &path, const QString &ext, const QDateTime &stamp, int secs);
QStringList parseArgs(const QByteArray &data, int maxArgs, int *pos = nullptr);
bool rdConf(const QString &filePath, shared_t *share);
bool rdConf(const QString &filePath, shared_t *share, bool reset = true);
bool mkPath(const QString &path);
void setupBuffDir(const QString &path, bool del = false);
void rdLine(const QString &param, const QString &line, QString *value);
void rdLine(const QString &param, const QString &line, int *value);
void rdLine(const QString &param, const QString &line, quint64 *value);
void rdLine(const QString &param, const QString &line, bool *value);
void enforceMaxEvents(shared_t *share);
void enforceMaxImages(shared_t *share);

View File

@ -12,10 +12,11 @@
#include "detect_loop.h"
DetectLoop::DetectLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : QFileSystemWatcher(parent)
DetectLoop::DetectLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : QObject(parent)
{
pcTimer = 0;
shared = sharedRes;
starved = true;
connect(thr, &QThread::started, this, &DetectLoop::init);
connect(thr, &QThread::finished, this, &DetectLoop::deleteLater);
@ -25,15 +26,15 @@ DetectLoop::DetectLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : QFi
void DetectLoop::init()
{
pcTimer = new QTimer(this);
pcTimer = new QTimer(this);
checkTimer = new QTimer(this);
connect(pcTimer, &QTimer::timeout, this, &DetectLoop::pcBreak);
connect(this, &QFileSystemWatcher::directoryChanged, this, &DetectLoop::updated);
connect(pcTimer, &QTimer::timeout, this, &DetectLoop::pcBreak);
connect(checkTimer, &QTimer::timeout, this, &DetectLoop::eTimeOut);
pcTimer->start(shared->postSecs * 1000);
setupBuffDir(shared->buffPath);
addPath(shared->buffPath + "/live");
checkTimer->start(5000);
checkTimer->setSingleShot(true);
}
void DetectLoop::reset()
@ -47,35 +48,19 @@ void DetectLoop::reset()
eventQue.timeStamp.clear();
}
void DetectLoop::updated(const QString &path)
void DetectLoop::eTimeOut()
{
eTimer.start();
starved = true;
auto clips = lsFilesInDir(path, shared->streamExt);
auto index = clips.indexOf(vidBName);
if (clips.size() - (index + 1) < 3)
{
thread()->sleep(1);
}
else
{
vidAName = clips[clips.size() - 3];
vidBName = clips[clips.size() - 2];
vidAPath = shared->buffPath + "/live/" + vidAName;
vidBPath = shared->buffPath + "/live/" + vidBName;
exec(); thread()->sleep(1);
}
emit starving();
}
void DetectLoop::pcBreak()
{
prevClips.clear();
if (!shared->postCmd.isEmpty())
{
checkTimer->stop();
qInfo() << "---POST_BREAK---";
if (eventQue.inQue)
@ -97,6 +82,8 @@ void DetectLoop::pcBreak()
QProcess::execute(args[0], args.mid(1));
}
}
checkTimer->start();
}
}
@ -161,10 +148,8 @@ QStringList DetectLoop::buildSnapArgs(const QString &vidSrc, const QString &imgP
QString DetectLoop::statusLine()
{
if (eTimer.elapsed() >= 5000)
if (starved)
{
emit starving();
return "STARVED";
}
else
@ -173,81 +158,95 @@ QString DetectLoop::statusLine()
}
}
void DetectLoop::exec()
bool DetectLoop::pathFilter(const QString &path, const QString &subName)
{
auto imgAPath = shared->buffPath + "/img/" + QFileInfo(vidAPath).baseName() + ".bmp";
auto imgBPath = shared->buffPath + "/img/" + QFileInfo(vidBPath).baseName() + ".bmp";
auto snapArgsA = buildSnapArgs(vidAPath, imgAPath);
auto snapArgsB = buildSnapArgs(vidBPath, imgBPath);
auto compArgs = buildArgs(imgAPath, imgBPath);
return path.startsWith("/" + shared->camName + "/" + subName) && path.endsWith(shared->streamExt);
}
if (compArgs.isEmpty())
void DetectLoop::fileFlushed(const QString &path)
{
if (pathFilter(path, "vid"))
{
qCritical() << "err: could not parse a executable name from img_comp_cmd: " << shared->compCmd;
}
else
{
QProcess::execute("ffmpeg", snapArgsA);
QProcess::execute("ffmpeg", snapArgsB);
if (QFile::exists(imgAPath) && QFile::exists(imgBPath))
if (vidAPath.isEmpty())
{
QProcess extComp;
vidAPath = shared->varPath + "/stream" + path;
imgAPath = QDir::cleanPath(shared->buffPath) + QDir::separator() + QString("img") + QDir::separator() + QFileInfo(vidAPath).baseName() + ".bmp";
extComp.start(compArgs[0], compArgs.mid(1));
extComp.waitForFinished();
QProcess::execute("ffmpeg", buildSnapArgs(vidAPath, imgAPath));
}
else
{
auto vidBPath = shared->varPath + "/stream" + path;
auto imgBPath = QDir::cleanPath(shared->buffPath) + QDir::separator() + QString("img") + QDir::separator() + QFileInfo(vidBPath).baseName() + ".bmp";
float score = 0;
QProcess::execute("ffmpeg", buildSnapArgs(vidBPath, imgBPath));
if (shared->outputType == "stdout")
starved = false;
checkTimer->start();
auto compArgs = buildArgs(imgAPath, imgBPath);
if (compArgs.isEmpty())
{
score = getFloatFromExe(extComp.readAllStandardOutput());
qCritical() << "err: could not parse a executable name from img_comp_cmd: " << shared->compCmd;
}
else
{
score = getFloatFromExe(extComp.readAllStandardError());
}
QProcess extComp;
qInfo() << compArgs.join(" ") << " --result: " << QString::number(score);
extComp.start(compArgs[0], compArgs.mid(1));
extComp.waitForFinished();
if (eventQue.inQue)
{
eventQue.queAge += 4;
float score = 0;
if (eventQue.score < score)
if (shared->outputType == "stdout")
{
eventQue.score = score;
eventQue.imgPath = imgBPath;
score = getFloatFromExe(extComp.readAllStandardOutput());
}
else
{
score = getFloatFromExe(extComp.readAllStandardError());
}
eventQue.vidList.append(vidAPath);
eventQue.vidList.append(vidBPath);
qInfo() << compArgs.join(" ") << " --result: " << QString::number(score);
if (eventQue.queAge >= shared->evMaxSecs)
if (eventQue.inQue)
{
eventQue.inQue = false;
eventQue.queAge += 2;
shared->recList.append(eventQue);
if (eventQue.score < score)
{
eventQue.score = score;
eventQue.imgPath = imgBPath;
}
reset();
eventQue.vidList.append(vidBPath);
if (eventQue.queAge >= shared->evMaxSecs)
{
shared->recList.append(eventQue);
reset();
}
}
else if (score >= shared->imgThresh)
{
qInfo() << "--threshold_meet: " << QString::number(shared->imgThresh);
eventQue.score = score;
eventQue.imgPath = imgBPath;
eventQue.inQue = true;
eventQue.queAge = 0;
eventQue.timeStamp = QDateTime::currentDateTime().toString(DATETIME_FMT);
eventQue.vidList.append(vidAPath);
eventQue.vidList.append(vidBPath);
}
}
else if (score >= shared->imgThresh)
{
qInfo() << "--threshold_meet: " << QString::number(shared->imgThresh);
eventQue.score = score;
eventQue.imgPath = imgBPath;
eventQue.inQue = true;
eventQue.queAge = 0;
eventQue.timeStamp = QDateTime::currentDateTime().toString(DATETIME_FMT);
eventQue.vidList.append(vidAPath);
eventQue.vidList.append(vidBPath);
}
vidAPath = vidBPath;
imgAPath = imgBPath;
}
}
vidAPath.clear();
vidBPath.clear();
}

View File

@ -15,7 +15,7 @@
#include "common.h"
class DetectLoop : public QFileSystemWatcher
class DetectLoop : public QObject
{
Q_OBJECT
@ -23,32 +23,37 @@ private:
QString vidAPath;
QString vidBPath;
QString vidAName;
QString vidBName;
QStringList prevClips;
QString imgAPath;
QString imgBPath;
QTimer *pcTimer;
QTimer *checkTimer;
QElapsedTimer eTimer;
evt_t eventQue;
shared_t *shared;
bool starved;
float getFloatFromExe(const QByteArray &line);
QStringList buildArgs(const QString &prev, const QString &next);
QStringList buildSnapArgs(const QString &vidSrc, const QString &imgPath);
bool pathFilter(const QString &path, const QString &subName);
private slots:
void init();
void reset();
void pcBreak();
void updated(const QString &path);
void eTimeOut();
public:
explicit DetectLoop(shared_t *shared, QThread *thr, QObject *parent = nullptr);
void exec();
QString statusLine();
public slots:
void fileFlushed(const QString &path);
signals:
void starving();

View File

@ -28,27 +28,17 @@ void EventLoop::init()
{
loopTimer = new QTimer(this);
connect(loopTimer, &QTimer::timeout, this, &EventLoop::loopSlot);
connect(loopTimer, &QTimer::timeout, this, &EventLoop::exec);
loopTimer->setSingleShot(false);
loopTimer->start(heartBeat * 1000);
loopSlot();
}
void EventLoop::loopSlot()
{
if (!exec())
{
loopTimer->stop(); QCoreApplication::exit(shared->retCode);
}
}
bool EventLoop::wrOutVod(const evt_t &event)
{
auto ret = false;
auto cnt = 0;
auto concat = shared->buffPath + "/live/" + event.timeStamp + ".ctmp";
auto concat = shared->buffPath + "/vid/" + event.timeStamp + ".ctmp";
QFile file(concat, this);
@ -110,7 +100,7 @@ QString EventLoop::statusLine()
}
}
bool EventLoop::exec()
void EventLoop::exec()
{
enforceMaxEvents(shared);
enforceMaxImages(shared);
@ -122,6 +112,8 @@ bool EventLoop::exec()
qInfo() << "attempting write out of event: " << event.timeStamp;
event.vidList.removeDuplicates();
if (wrOutVod(event))
{
QStringList args;
@ -133,6 +125,4 @@ bool EventLoop::exec()
QProcess::execute("magick", args);
}
}
return shared->retCode == 0;
}

View File

@ -22,7 +22,7 @@ class EventLoop : public QObject
private slots:
void init();
void loopSlot();
void exec();
private:
@ -36,7 +36,6 @@ public:
explicit EventLoop(shared_t *shared, QThread *thr, QObject *parent = nullptr);
bool exec();
QString statusLine();
};

View File

@ -38,8 +38,14 @@ int main(int argc, char** argv)
QCoreApplication::setApplicationName(APP_NAME);
QCoreApplication::setApplicationVersion(APP_VERSION);
shared_t globalConf;
globalConf.confPath = "/etc/" + QString(APP_TARGET);
globalConf.varPath = "/var/local/" + QString(APP_TARGET);
globalConf.rdMnt = globalConf.varPath + "/stream";
globalConf.maxMemSize = 4000000000;
auto args = QCoreApplication::arguments();
auto etcDir = "/etc/" + QString(APP_TARGET);
auto staDir = QDir::tempPath() + "/" + APP_TARGET + "-stats";
auto procFile = QDir::tempPath() + "/" + APP_TARGET + "-proc";
auto ret = 0;
@ -49,9 +55,19 @@ int main(int argc, char** argv)
mkPath(staDir);
}
if (!QFileInfo::exists(globalConf.rdMnt))
{
mkPath(globalConf.rdMnt);
}
if (!QFileInfo::exists(globalConf.varPath + "/footage"))
{
mkPath(globalConf.varPath + "/footage");
}
if (args.contains("-h"))
{
showHelp(etcDir);
showHelp(globalConf.confPath);
}
else if (args.contains("-v"))
{
@ -59,22 +75,11 @@ int main(int argc, char** argv)
}
else if (args.contains("-d"))
{
auto confs = lsFilesInDir(etcDir);
auto proc = new ProcControl(procFile, staDir, &app);
auto proc = new ProcControl(procFile, staDir, &globalConf, &app);
if (!proc->init())
{
ret = EACCES;
}
else
{
for (auto &&conf : confs)
{
new Camera(etcDir + "/" + conf, staDir, proc, &app);
}
proc->startApp();
ret = app.exec();
}
ret = app.exec();
}
else if (args.contains("-u"))
{
@ -121,7 +126,7 @@ int main(int argc, char** argv)
}
else
{
showHelp(etcDir);
showHelp(globalConf.confPath);
}
return ret;

View File

@ -12,59 +12,100 @@
#include "proc_control.h"
ProcControl::ProcControl(const QString &procFile, const QString &statDir, QCoreApplication *parent) : QObject(parent)
ProcControl::ProcControl(const QString &procFile, const QString &statDir, shared_t *glbShare, QCoreApplication *parent) : QObject(parent)
{
fsMon = new QFileSystemWatcher(this);
file = procFile;
statPath = statDir;
closeOnZero = false;
objCount = 0;
share = glbShare;
fsMon = new QFileSystemWatcher(this);
rdThread = new QThread(nullptr);
ramDisk = new RamDisk(rdThread, share->maxMemSize, share->rdMnt, nullptr);
checkTimer = new QTimer(this);
file = procFile;
statPath = statDir;
closeOnZero = false;
procFileAdded = false;
objCount = 0;
connect(fsMon, &QFileSystemWatcher::fileChanged, this, &ProcControl::procFileUpdated);
checkTimer->setInterval(3000);
checkTimer->setSingleShot(true);
connect(fsMon, &QFileSystemWatcher::fileChanged, this, &ProcControl::procFileUpdated);
connect(checkTimer, &QTimer::timeout, this, &ProcControl::startApp);
}
bool ProcControl::init()
void ProcControl::createProcFile()
{
auto ret = false;
if (!QFileInfo::exists(file))
if (!procFileAdded)
{
QFile fObj(file);
if (!fObj.open(QFile::WriteOnly))
if (!QFileInfo::exists(file))
{
QTextStream(stderr) << "err: Failed to open process control file for writing: " << file << " reason: " << fObj.errorString();
QFile fObj(file);
if (!fObj.open(QFile::WriteOnly))
{
QTextStream(stderr) << "err: Failed to open process control file for writing: " << file << " reason: " << fObj.errorString() << Qt::endl;
}
else
{
fObj.write("##");
fObj.close();
procFileAdded = fsMon->addPath(file);
}
}
else
{
fObj.write("##");
fObj.close();
QTextStream(stdout) << "wrn: " << file << " already exists so it will be removed, this will cause any other existing instance to close." << Qt::endl;
ret = true;
if (!QFile::remove(file))
{
QTextStream(stderr) << "err: Failed to remove process control file: " << file << " check permissions." << Qt::endl;
}
}
}
else
}
void ProcControl::startRamdisk()
{
if (!rdThread->isRunning())
{
QTextStream(stdout) << "wrn: " << file << " already exists so it will be removed, this will cause any other existing instance to close.";
if (!QFile::remove(file))
{
QTextStream(stderr) << "err: Failed to remove process control file: " << file << " check permissions.";
}
else
{
thread()->sleep(3);
ret = init();
}
rdThread->start();
}
}
if (ret)
void ProcControl::umntThenClose()
{
QDir(statPath).removeRecursively();
if (rdThread->isRunning())
{
fsMon->addPath(file);
for (int i = 0; i < 3; ++i)
{
thread()->sleep(1);
if (QProcess::execute("umount", {share->rdMnt}) == 0)
{
break;
}
}
}
return ret;
QCoreApplication::instance()->quit();
}
void ProcControl::createCamObjs()
{
if (procFileAdded)
{
for (auto &&conf : lsFilesInDir(share->confPath))
{
auto confFilePath = QDir::cleanPath(share->confPath) + "/" + conf;
if (!confList.contains(confFilePath))
{
new Camera(*share, confFilePath, statPath, ramDisk, this, QCoreApplication::instance());
}
}
}
}
void ProcControl::procFileUpdated(const QString &path)
@ -78,11 +119,9 @@ void ProcControl::procFileUpdated(const QString &path)
void ProcControl::closeApp()
{
if (objCount == 0)
if (confList.isEmpty())
{
QDir(statPath).removeRecursively();
QCoreApplication::instance()->quit();
umntThenClose();
}
else
{
@ -92,17 +131,29 @@ void ProcControl::closeApp()
}
}
void ProcControl::objPlusOne()
void ProcControl::startApp()
{
objCount++;
}
void ProcControl::objMinusOne()
{
objCount--;
if (closeOnZero && (objCount == 0))
if (!closeOnZero)
{
closeApp();
startRamdisk();
createProcFile();
createCamObjs();
checkTimer->start();
}
}
void ProcControl::objPlusOne(const QString &conf)
{
confList.append(conf);
}
void ProcControl::objMinusOne(const QString &conf)
{
confList.removeAll(conf);
if (closeOnZero && confList.isEmpty())
{
umntThenClose();
}
}

View File

@ -14,6 +14,8 @@
// GNU General Public License for more details.
#include "common.h"
#include "ram_disk.h"
#include "camera.h"
class ProcControl : public QObject
{
@ -22,26 +24,37 @@ class ProcControl : public QObject
private:
QFileSystemWatcher *fsMon;
QTimer *checkTimer;
QThread *rdThread;
RamDisk *ramDisk;
shared_t *share;
QStringList confList;
QString file;
QString statPath;
bool closeOnZero;
bool procFileAdded;
uint objCount;
void createCamObjs();
void createProcFile();
void startRamdisk();
void umntThenClose();
private slots:
void procFileUpdated(const QString &path);
void closeApp();
public:
explicit ProcControl(const QString &procFile, const QString &statDir, QCoreApplication *parent);
explicit ProcControl(const QString &procFile, const QString &statDir, shared_t *glbShare, QCoreApplication *parent);
void objPlusOne();
bool init();
void objPlusOne(const QString &conf);
public slots:
void objMinusOne();
void objMinusOne(const QString &conf);
void closeApp();
void startApp();
signals:

811
src/ram_disk.cpp Normal file
View File

@ -0,0 +1,811 @@
// This file is part of JustMotion.
// JustMotion 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.
// JustMotion 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.
#include "ram_disk.h"
mode_t fileDefaultMode()
{
return S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH | S_IFREG;
}
mode_t symmDefaultMode()
{
return S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH | S_IFLNK;
}
mode_t dirDefaultMode()
{
return S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH | S_IFDIR;
}
RamDisk *RamDisk::instance = nullptr;
QString RamDisk::mnt = QString();
QStringList RamDisk::flushHist = QStringList();
QStringList RamDisk::bondDirs = QStringList();
QHash<QString, file_t*> RamDisk::data = QHash<QString, file_t*>();
fuse_operations RamDisk::fs_oper = fuse_operations();
quint64 RamDisk::inoSeed = 2;
quint64 RamDisk::rootCtime = 0;
quint64 RamDisk::rootMtime = 0;
quint64 RamDisk::duMax = 0;
quint64 RamDisk::du = 0;
QRecursiveMutex RamDisk::mutex = QRecursiveMutex();
RamDisk::RamDisk(QThread *thr, quint64 maxSize, const QString &mntPnt, QObject *parent) : QObject(parent)
{
duMax = maxSize;
mnt = mntPnt;
instance = this;
rootCtime = QDateTime::currentMSecsSinceEpoch();
rootMtime = rootCtime;
connect(thr, &QThread::started, this, &RamDisk::exec);
moveToThread(thr);
}
void RamDisk::lock()
{
mutex.lock();
}
void RamDisk::unlock()
{
mutex.unlock();
}
void RamDisk::exec()
{
fs_oper.getattr = RamDisk::getattr;
fs_oper.readlink = RamDisk::readlink;
fs_oper.readdir = RamDisk::readdir;
fs_oper.mkdir = RamDisk::mkdir;
fs_oper.symlink = RamDisk::symlink;
fs_oper.unlink = RamDisk::unlink;
fs_oper.rmdir = RamDisk::rmdir;
fs_oper.rename = RamDisk::rename;
fs_oper.truncate = RamDisk::truncate;
fs_oper.open = RamDisk::open;
fs_oper.create = RamDisk::create;
fs_oper.read = RamDisk::read;
fs_oper.write = RamDisk::write;
fs_oper.statfs = RamDisk::statfs;
fs_oper.init = RamDisk::init;
fs_oper.destroy = RamDisk::destroy;
fs_oper.utimens = RamDisk::utimens;
fs_oper.fallocate = RamDisk::fallocate;
fs_oper.flush = RamDisk::flush;
fs_oper.release = RamDisk::release;
fs_oper.chmod = RamDisk::chmod;
fs_oper.chown = RamDisk::chown;
fs_oper.copy_file_range = NULL;
fs_oper.link = NULL;
fs_oper.lock = NULL;
fs_oper.releasedir = NULL;
fs_oper.fsync = NULL;
fs_oper.opendir = NULL;
fs_oper.fsyncdir = NULL;
fs_oper.bmap = NULL;
fs_oper.ioctl = NULL;
fs_oper.flock = NULL;
fs_oper.lseek = NULL;
fs_oper.access = NULL;
fs_oper.write_buf = NULL;
fs_oper.read_buf = NULL;
fs_oper.getxattr = NULL;
fs_oper.setxattr = NULL;
fs_oper.listxattr = NULL;
fs_oper.removexattr = NULL;
QByteArray appName(APP_TARGET);
//QByteArray debug("-d");
//QByteArray singleTh("-s");
QByteArray foreground("-f");
QByteArray option("-o");
//QByteArray dp("default_permissions");
QByteArray other("allow_other");
int argc = 5;
char *argv[] = {appName.data(),
//debug.data(),
//singleTh.data(),
foreground.data(),
//option.data(),
//dp.data(),
option.data(),
other.data(),
mnt.toUtf8().data()};
auto ret = fuse_main(argc, argv, &fs_oper, NULL);
if (ret != 0)
{
qCritical() << "err: the ram disk failed to mount, return code: " << ret;
}
}
bool RamDisk::isFull()
{
return du >= duMax;
}
int RamDisk::calcNumOfBlocks(quint64 len, bool countZero)
{
if ((len == 0) && !countZero)
{
return 0;
}
if (len == 0)
{
return 1;
}
else
{
qreal blks = static_cast<qreal>(len) / static_cast<qreal>(BLK_SIZE);
return static_cast<int>(ceil(blks));
}
}
void RamDisk::fillStatTimes(struct stat *info, quint64 cTimeSec, quint64 mTimeSec)
{
// this converts the UTC epoch stored in the ram disk to
// local epoch using QDateTime.
auto cTime = QDateTime::fromMSecsSinceEpoch(cTimeSec, Qt::LocalTime);
auto mTime = QDateTime::fromMSecsSinceEpoch(mTimeSec, Qt::LocalTime);
auto curr = QDateTime::currentDateTime();
info->st_ctim.tv_sec = cTime.toSecsSinceEpoch();
info->st_mtim.tv_sec = mTime.toSecsSinceEpoch();
info->st_ctim.tv_nsec = cTime.toMSecsSinceEpoch();
info->st_mtim.tv_nsec = mTime.toMSecsSinceEpoch();
info->st_atim.tv_nsec = curr.toMSecsSinceEpoch();
info->st_atim.tv_sec = curr.toSecsSinceEpoch();
}
int RamDisk::getattr(const char *path, struct stat *info, struct fuse_file_info *fi)
{
Q_UNUSED(fi);
lock();
auto cleanedPath = QDir::cleanPath(path);
auto ret = 0;
memset(info, 0, sizeof(struct stat));
info->st_uid = getuid();
info->st_gid = getgid();
info->st_nlink = 1;
info->st_blksize = BLK_SIZE;
if (cleanedPath == "/")
{
info->st_mode = dirDefaultMode();
info->st_ino = 1;
info->st_size = 0;
info->st_blocks = 0;
fillStatTimes(info, rootCtime, rootMtime);
}
else if (data.contains(cleanedPath))
{
auto file = data[cleanedPath];
info->st_ino = file->ino;
info->st_mode = file->mode;
info->st_size = file->bytes.size();
info->st_blocks = file->blks;
fillStatTimes(info, file->ctime, file->mtime);
}
else
{
ret = -ENOENT;
}
unlock();
return ret;
}
int RamDisk::readlink(const char *path, char *buff, size_t len)
{
auto cleanedPath = QDir::cleanPath(path);
auto ret = 0;
lock();
if (data.contains(cleanedPath))
{
memcpy(buff, data[cleanedPath]->bytes.data(), len);
}
else
{
ret = -ENOENT;
}
unlock();
return ret;
}
QString RamDisk::getFileName(const QString &basePath, const QString &fullPath)
{
QString ret;
if (fullPath.startsWith(basePath))
{
auto name = fullPath.mid(basePath.size());
if (name.startsWith("/"))
{
name = name.mid(1);
}
if (name.indexOf("/") == -1)
{
ret = name;
}
}
return ret;
}
bool RamDisk::dirEmpty(const QString &path, int &ret)
{
ret = 0;
if (!data.contains(path) && path != "/")
{
ret = -ENOENT;
}
else
{
auto treeList = data.keys();
for (auto fullPath : treeList)
{
auto name = getFileName(path, fullPath);
if (!name.isEmpty())
{
ret = -ENOTEMPTY; break;
}
}
}
return ret == 0;
}
quint32 RamDisk::getMode(const QString &path)
{
quint32 ret = 0;
if (path == "/")
{
ret = dirDefaultMode();
}
else if (data.contains(path))
{
ret = data[path]->mode;
}
return ret;
}
int RamDisk::readdir(const char *path, void *buff, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi, enum fuse_readdir_flags flags)
{
Q_UNUSED(offset);
Q_UNUSED(fi);
Q_UNUSED(flags);
lock();
auto fileList = data.keys();
auto cleanedPath = QDir::cleanPath(path);
auto ret = 0;
struct stat st;
memset(&st, 0, sizeof(st));
for (auto filePath : fileList)
{
auto name = getFileName(cleanedPath, filePath);
if (!name.isEmpty())
{
auto file = data[QDir::cleanPath(cleanedPath + "/" + name)];
st.st_mode = file->mode;
st.st_ino = file->ino;
st.st_size = file->bytes.size();
st.st_blocks = file->blks;
fillStatTimes(&st, file->ctime, file->mtime);
if (filler(buff, name.toUtf8().data(), &st, 0, FUSE_FILL_DIR_PLUS))
{
ret = -ENOMEM;
}
}
}
unlock();
return ret;
}
int RamDisk::mkdir(const char *path, mode_t mode)
{
Q_UNUSED(mode);
lock();
auto cleanedPath = QDir::cleanPath(path);
auto ret = 0;
if (!data.contains(cleanedPath) && (cleanedPath != "/"))
{
auto info = new file_t();
auto curr = QDateTime::currentDateTimeUtc();
info->mode = dirDefaultMode();
info->ino = inoSeed++;
info->blks = 0;
info->mtime = curr.toMSecsSinceEpoch();
info->ctime = curr.toMSecsSinceEpoch();
data.insert(cleanedPath, info);
}
unlock();
return ret;
}
int RamDisk::symlink(const char *src, const char *dst)
{
lock();
auto cleanedSrcPath = QDir::cleanPath(src);
auto cleanedDstPath = QDir::cleanPath(dst);
auto ret = 0;
if (isFull())
{
ret = -ENOSPC;
}
else
{
auto info = new file_t();
auto curr = QDateTime::currentDateTimeUtc();
info->mode = symmDefaultMode();
info->ino = inoSeed++;
info->blks = 1;
info->bytes = cleanedSrcPath.toUtf8();
info->mtime = curr.toMSecsSinceEpoch();
info->ctime = curr.toMSecsSinceEpoch();
du += info->bytes.size();
data.insert(cleanedDstPath, info);
}
unlock();
return ret;
}
int RamDisk::unlink(const char *path)
{
lock();
auto cleanedPath = QDir::cleanPath(path);
auto ret = 0;
if (!data.contains(cleanedPath))
{
ret = -ENOENT;
}
else
{
du -= data[cleanedPath]->bytes.size();
data.remove(cleanedPath);
}
unlock();
return ret;
}
int RamDisk::rmdir(const char *path)
{
lock();
auto cleanedPath = QDir::cleanPath(path);
auto ret = 0;
if (dirEmpty(cleanedPath, ret))
{
data.remove(cleanedPath);
}
unlock();
return ret;
}
void RamDisk::rePath(const QString &src, const QString &dst)
{
auto pathList = data.keys();
for (auto path : pathList)
{
if (path.startsWith(src))
{
auto file = data.take(path);
auto newPath = path.replace(src, dst);
data.insert(newPath, file);
}
}
}
int RamDisk::rename(const char *src, const char *dst, unsigned int flags)
{
lock();
auto cleanedSrcPath = QDir::cleanPath(src);
auto cleanedDstPath = QDir::cleanPath(dst);
auto srcMode = getMode(cleanedSrcPath);
auto dstMode = getMode(cleanedDstPath);
auto ret = 0;
if (srcMode && dstMode)
{
if (srcMode != dstMode)
{
ret = -EINVAL;
}
else if (flags & RENAME_EXCHANGE)
{
auto tmp = QString(TMP_DIR) + QDir::separator() + QString(cleanedSrcPath);
rePath(cleanedSrcPath, tmp);
rePath(cleanedDstPath, cleanedSrcPath);
rePath(tmp, cleanedDstPath);
}
else if ((flags & RENAME_NOREPLACE) == 0)
{
rePath(cleanedSrcPath, cleanedDstPath);
}
else
{
ret = -EEXIST;
}
}
else if (srcMode && !dstMode)
{
rePath(cleanedSrcPath, cleanedDstPath);
}
else
{
ret = -ENOENT;
}
unlock();
return ret;
}
int RamDisk::open(const char *path, struct fuse_file_info *fi)
{
Q_UNUSED(path);
Q_UNUSED(fi);
return 0;
}
int RamDisk::truncate(const char *path, off_t size, struct fuse_file_info *fi)
{
Q_UNUSED(fi);
lock();
auto cleanedPath = QDir::cleanPath(path);
auto ret = 0;
if (!data.contains(cleanedPath))
{
ret = -ENOENT;
}
else
{
auto file = data[cleanedPath];
auto oldSize = file->bytes.size();
file->bytes = file->bytes.leftJustified(size, 0x00, true);
file->mtime = QDateTime::currentDateTimeUtc().toMSecsSinceEpoch();
if (oldSize > size)
{
du -= oldSize - size;
}
else if (size > oldSize)
{
du += size - oldSize;
}
file->blks = calcNumOfBlocks(size);
}
unlock();
return ret;
}
int RamDisk::create(const char *path, mode_t mode, struct fuse_file_info *fi)
{
Q_UNUSED(mode);
Q_UNUSED(fi);
lock();
auto cleanedPath = QDir::cleanPath(path);
auto ret = 0;
if (cleanedPath == "/")
{
ret = -EINVAL;
}
else if (data.contains(cleanedPath))
{
ret = -EEXIST;
}
else
{
auto file = new file_t();
auto curr = QDateTime::currentDateTimeUtc();
file->ino = inoSeed++;
file->blks = 1;
file->mode = fileDefaultMode();
file->ctime = curr.toMSecsSinceEpoch();
file->mtime = curr.toMSecsSinceEpoch();
data.insert(cleanedPath, file);
}
unlock();
return ret;
}
int RamDisk::read(const char *path, char *buff, size_t len, off_t offs, struct fuse_file_info *fi)
{
Q_UNUSED(fi);
lock();
auto cleanedPath = QDir::cleanPath(path);
auto ret = 0;
if (!data.contains(cleanedPath))
{
ret = -ENOENT;
}
else
{
auto file = data[cleanedPath];
auto iOffs = static_cast<int>(offs);
auto iLen = static_cast<int>(len);
if (iOffs > (file->bytes.size() - 1))
{
ret = -EFAULT;
}
else
{
if ((iOffs + iLen) > file->bytes.size())
{
iLen = file->bytes.size() - iOffs;
}
memcpy(buff, file->bytes.data() + iOffs, iLen);
ret = iLen;
}
}
unlock();
return ret;
}
int RamDisk::write(const char *path, const char *buff, size_t len, off_t offs, struct fuse_file_info *fi)
{
Q_UNUSED(fi);
lock();
auto cleanedPath = QDir::cleanPath(path);
auto ret = 0;
if (isFull())
{
ret = -ENOSPC;
}
else if (!data.contains(cleanedPath))
{
ret = -ENOENT;
}
else
{
auto file = data[cleanedPath];
auto oldSize = file->bytes.size();
file->bytes.replace(offs, len, QByteArray::fromRawData(buff, len));
if (oldSize > file->bytes.size())
{
du -= oldSize - file->bytes.size();
}
else if (file->bytes.size() > oldSize)
{
du += file->bytes.size() - oldSize;
}
file->blks = calcNumOfBlocks(file->bytes.size());
ret = len;
}
unlock();
return ret;
}
int RamDisk::statfs(const char *path, struct statvfs *stbuff)
{
Q_UNUSED(path);
stbuff->f_frsize = BLK_SIZE;
stbuff->f_bsize = BLK_SIZE;
stbuff->f_blocks = calcNumOfBlocks(duMax);
stbuff->f_bfree = stbuff->f_blocks - calcNumOfBlocks(du, false);
stbuff->f_bavail = stbuff->f_bfree;
return 0;
}
void RamDisk::destroy(void *privateData)
{
Q_UNUSED(privateData);
lock();
data.clear();
unlock();
inoSeed = 1;
du = 0;
}
int RamDisk::utimens(const char *path, const struct timespec newTimes[2], struct fuse_file_info *fi)
{
Q_UNUSED(fi);
Q_UNUSED(newTimes);
lock();
auto cleanedPath = QDir::cleanPath(path);
auto ret = 0;
if (!data.contains(cleanedPath))
{
ret = -ENOENT;
}
else
{
data[cleanedPath]->mtime = QDateTime::currentDateTimeUtc().toMSecsSinceEpoch();
}
unlock();
return ret;
}
int RamDisk::fallocate(const char *path, int mode, off_t offs, off_t len, struct fuse_file_info *fi)
{
Q_UNUSED(path);
Q_UNUSED(mode);
Q_UNUSED(fi);
Q_UNUSED(offs);
Q_UNUSED(len);
return 0;
}
int RamDisk::flush(const char *path, struct fuse_file_info *fi)
{
Q_UNUSED(fi);
auto cleanedPath = QDir::cleanPath(path);
auto basePath = QFileInfo(cleanedPath).path();
if (flushHist.size() >= (bondDirs.size() * 4))
{
flushHist = flushHist.mid(bondDirs.size() * 2);
}
if (bondDirs.contains(basePath) && !flushHist.contains(cleanedPath))
{
flushHist.append(cleanedPath);
emit instance->watchedFileFlushed(cleanedPath);
}
return 0;
}
int RamDisk::release(const char *path, struct fuse_file_info *fi)
{
Q_UNUSED(path);
Q_UNUSED(fi);
return 0;
}
int RamDisk::chmod(const char *path, mode_t mode, struct fuse_file_info *fi)
{
Q_UNUSED(path);
Q_UNUSED(mode);
Q_UNUSED(fi);
return 0;
}
int RamDisk::chown(const char *path, uid_t uid, gid_t gid, struct fuse_file_info *fi)
{
Q_UNUSED(path);
Q_UNUSED(uid);
Q_UNUSED(gid);
Q_UNUSED(fi);
return 0;
}
void *RamDisk::init(struct fuse_conn_info *conn, struct fuse_config *cfg)
{
Q_UNUSED(conn);
Q_UNUSED(cfg);
return NULL;
}
void RamDisk::addCam(const QString &name)
{
bondDirs.append("/" + name + "/vid");
}
void RamDisk::rmCam(const QString &name)
{
bondDirs.removeAll("/" + name + "/vid");
}

91
src/ram_disk.h Normal file
View File

@ -0,0 +1,91 @@
#ifndef RAM_DISK_H
#define RAM_DISK_H
// This file is part of JustMotion.
// JustMotion 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.
// JustMotion 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.
#include "common.h"
#define TMP_DIR "#TMP"
mode_t dirDefaultMode();
mode_t fileDefaultMode();
mode_t symmDefaultMode();
class RamDisk : public QObject
{
Q_OBJECT
private:
static RamDisk *instance;
static QString mnt;
static QStringList flushHist;
static QStringList bondDirs;
static QHash<QString, file_t*> data;
static fuse_operations fs_oper;
static quint64 rootMtime;
static quint64 rootCtime;
static quint64 inoSeed;
static quint64 duMax;
static quint64 du;
static QRecursiveMutex mutex;
static QString getFileName(const QString &basePath, const QString &fullPath);
static quint32 getMode(const QString &path);
static bool dirEmpty(const QString &path, int &ret);
static void fillStatTimes(struct stat *info, quint64 cTimeSec, quint64 mTimeSec);
static void rePath(const QString &src, const QString &dst);
static void lock();
static void unlock();
static int calcNumOfBlocks(quint64 len, bool countZero = true);
private slots:
void exec();
public:
static bool isFull();
static int getattr(const char *path, struct stat *info, struct fuse_file_info *fi);
static int readlink(const char *path, char *buff, size_t len);
static int readdir(const char *path, void *buff, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi, enum fuse_readdir_flags flags);
static int mkdir(const char *path, mode_t mode);
static int symlink(const char *src, const char *dst);
static int unlink(const char *path);
static int rmdir(const char *path);
static int chmod(const char *path, mode_t mode, struct fuse_file_info *fi);
static int chown(const char *path, uid_t uid, gid_t gid, struct fuse_file_info *fi);
static int flush(const char *path, struct fuse_file_info *fi);
static int release(const char *path, struct fuse_file_info *fi);
static int rename(const char *src, const char *dst, unsigned int flags);
static int truncate(const char *path, off_t size, struct fuse_file_info *fi);
static int open(const char *path, struct fuse_file_info *fi);
static int create(const char *path, mode_t mode, struct fuse_file_info *fi);
static int read(const char *path, char *buff, size_t len, off_t offs, struct fuse_file_info *fi);
static int write(const char *path, const char *buff, size_t len, off_t offs, struct fuse_file_info *fi);
static int statfs(const char *path, struct statvfs *stbuff);
static int utimens(const char *path, const struct timespec newTimes[2], struct fuse_file_info *fi);
static int fallocate(const char *path, int mode, off_t offs, off_t len, struct fuse_file_info *fi);
static void *init(struct fuse_conn_info *conn, struct fuse_config *cfg);
static void destroy(void *privateData);
static void addCam(const QString &name);
static void rmCam(const QString &name);
explicit RamDisk(QThread *thr, quint64 maxSize, const QString &mntPnt, QObject *parent = nullptr);
signals:
void watchedFileFlushed(const QString &path);
};
#endif // RAM_DISK_H

View File

@ -22,6 +22,7 @@ RecordLoop::RecordLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : QPr
connect(this, &RecordLoop::readyReadStandardOutput, this, &RecordLoop::resetTime);
connect(this, &RecordLoop::readyReadStandardError, this, &RecordLoop::resetTime);
connect(this, &RecordLoop::readyReadStandardError, this, &RecordLoop::readErr);
connect(this, &RecordLoop::started, this, &RecordLoop::resetTime);
moveToThread(thr);
@ -42,7 +43,6 @@ void RecordLoop::init()
checkTimer->setSingleShot(true);
checkTimer->setInterval(3000);
setupBuffDir(shared->buffPath);
restart();
}
@ -63,7 +63,7 @@ QString RecordLoop::camCmdFromConf()
ret += "-vcodec " + shared->vidCodec + " ";
ret += "-acodec " + shared->audCodec + " ";
ret += "-reset_timestamps 1 -sc_threshold 0 -g 2 -force_key_frames 'expr:gte(t, n_forced * 2)' -segment_time 2 -f segment ";
ret += shared->buffPath + "/live/" + QString(STRFTIME_FMT) + shared->streamExt;
ret += shared->buffPath + "/vid/" + QString(STRFTIME_FMT) + shared->streamExt;
return ret;
}
@ -80,6 +80,11 @@ QString RecordLoop::statusLine()
}
}
void RecordLoop::readErr()
{
//qCritical() << "err: " << readAllStandardError();
}
void RecordLoop::resetTime()
{
checkTimer->start();
@ -96,8 +101,6 @@ void RecordLoop::restart()
auto cmdLine = camCmdFromConf();
auto args = parseArgs(cmdLine.toUtf8(), -1);
qInfo() << "start recording command: " << cmdLine;
if (args.isEmpty())
{
qCritical() << "err: couldn't parse a program name";

View File

@ -22,6 +22,7 @@ class RecordLoop : public QProcess
private slots:
void init();
void readErr();
void resetTime();
private: