Compare commits

..

No commits in common. "045aa25f6dbf6b3efae009e328cdc60e73d3c5bc" and "c103ee3c47d39180ec30d7b45e287827cf9b1295" have entirely different histories.

18 changed files with 306 additions and 1320 deletions

View File

@ -3,8 +3,6 @@ QT -= gui
CONFIG += c++11 console
CONFIG -= app_bundle
LIBS += -lfuse3
TARGET = build/jmotion
OBJECTS_DIR = build
MOC_DIR = build
@ -16,7 +14,6 @@ HEADERS += \
src/detect_loop.h \
src/event_loop.h \
src/proc_control.h \
src/ram_disk.h \
src/record_loop.h
SOURCES += \
@ -25,6 +22,5 @@ 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,33 +19,13 @@ def get_app_name(text):
def get_qt_path():
try:
if os.path.exists("/usr/lib/qt6/bin"):
return "/usr/lib/qt6/bin"
return str(subprocess.check_output(["qtpaths6", "--binaries-dir"]), 'utf-8').strip()
return str(subprocess.check_output(["qtpaths", "--binaries-dir"]), 'utf-8').strip()
except:
print("A direct call to 'qtpaths6' has failed so automatic retrieval of the QT bin folder is not possible.")
print("A direct call to 'qtpaths' 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":
@ -110,7 +90,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, qt_lib):
def linux_build_app_dir(app_ver, app_name, app_target, qt_bin):
if not os.path.exists("app_dir/lib"):
os.makedirs("app_dir/lib")
@ -138,8 +118,12 @@ def linux_build_app_dir(app_ver, app_name, app_target, qt_bin, qt_lib):
file_name = os.path.basename(src_file)
verbose_copy(src_file, "app_dir/lib/" + file_name)
verbose_copy(qt_lib + "/libQt6DBus.so.6", "app_dir/lib/libQt6DBus.so.6")
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("templates/linux_run_script.sh", "app_dir/" + app_target + ".sh")
verbose_copy("templates/linux_uninstall.sh", "app_dir/uninstall.sh")
@ -199,7 +183,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", "libfuse3-dev", "libfuse-dev", "fuse3", "imagemagick", "qt6-base-dev"]
dep_pkgs_b = ["ffmpeg", "libfuse-dev", "fuse3", "imagemagick"]
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):
@ -234,16 +218,10 @@ 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()
@ -263,7 +241,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, qt_lib)
linux_build_app_dir(app_ver, app_name, app_target, qt_bin)
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/local/" + app_target, False)
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)
def replace_bin(binary, old_bin, new_bin, offs):
while(True):
@ -123,12 +123,13 @@ 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,16 +12,13 @@
#include "camera.h"
Camera::Camera(const shared_t &glbShare, const QString &confFile, const QString &statDir, RamDisk *ram, ProcControl *proc, QCoreApplication *parent) : QObject(nullptr)
Camera::Camera(const QString &confFile, const QString &statDir, 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;
@ -30,9 +27,11 @@ Camera::Camera(const shared_t &glbShare, const QString &confFile, const QString
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);
@ -50,8 +49,6 @@ void Camera::objMinusOne()
if (delOnZero)
{
procCon->objMinusOne(shared.conf);
QDir(shared.buffPath).removeRecursively();
deleteLater();
@ -61,7 +58,6 @@ void Camera::objMinusOne()
void Camera::prepForDel()
{
ramDisk->rmCam(shared.camName);
statTimer->blockSignals(true);
delOnZero = true;
@ -80,22 +76,16 @@ void Camera::updateStat()
file.close();
}
void Camera::start(const QString &conf)
int Camera::start(const QString &conf)
{
if (!rdConf(conf, &shared))
if (rdConf(conf, &shared))
{
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);
setupBuffDir(shared.buffPath, true);
ramDisk->addCam(shared.camName);
procCon->objPlusOne(shared.conf);
if (!fsW->files().contains(conf)) fsW->addPath(conf);
if (!fsW->files().contains(conf))
{
fsW->addPath(conf);
}
thr1 = new QThread(nullptr);
thr2 = new QThread(nullptr);
@ -113,29 +103,35 @@ void 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(ramDisk, &RamDisk::watchedFileFlushed, detLoop, &DetectLoop::fileFlushed);
connect(detLoop, &DetectLoop::starving, recLoop, &RecordLoop::restart);
thr1->start();
thr2->start();
thr3->start();
objCount = 3;
auto global = QDir::cleanPath(shared.confPath) + "/global";
if (QFileInfo::exists(global))
{
rdConf(global, &shared, false);
}
}
return shared.retCode;
}
void Camera::confChanged(const QString &path)
{
Q_UNUSED(path);
emit stop();
prepForDel();
if (!QFileInfo::exists(path))
{
deleteLater();
}
else
{
auto ret = start(path);
if (ret != 0)
{
deleteLater();
}
}
}
QString Camera::statusLine()

View File

@ -18,7 +18,6 @@
#include "detect_loop.h"
#include "record_loop.h"
#include "proc_control.h"
#include "ram_disk.h"
class Camera : public QObject
{
@ -30,12 +29,10 @@ 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;
@ -50,9 +47,9 @@ private slots:
public:
explicit Camera(const shared_t &glbShare, const QString &confFile, const QString &statDir, RamDisk *ram, ProcControl *proc, QCoreApplication *parent);
explicit Camera(const QString &confFile, const QString &statDir, ProcControl *proc, QCoreApplication *parent);
void start(const QString &conf);
int 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 + "/vid", share->streamExt);
auto names = lsFilesInDir(share->buffPath + "/live", share->streamExt);
while (names.size() > (share->liveSecs / 2))
{
QFile::remove(share->buffPath + "/vid/" + names[0]);
QFile::remove(share->buffPath + "/live/" + names[0]);
names.removeFirst();
}
@ -128,14 +128,6 @@ 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))
@ -166,44 +158,42 @@ bool mkPath(const QString &path)
return ret;
}
bool rdConf(const QString &filePath, shared_t *share, bool reset)
bool rdConf(const QString &filePath, shared_t *share)
{
auto ret = true;
QFile varFile(filePath);
if (!varFile.open(QFile::ReadOnly))
{
QTextStream(stderr) << "err: config file - " << filePath << " does not exists or lack read permissions." << Qt::endl;
share->retCode = ENOENT;
ret = false;
QTextStream(stderr) << "err: config file - " << filePath << " does not exists or lack read permissions." << Qt::endl;
}
else
{
if (reset)
{
share->recordUri.clear();
share->postCmd.clear();
share->camName.clear();
share->recPath.clear();
share->recordUri.clear();
share->postCmd.clear();
share->camName.clear();
share->buffPath.clear();
share->recPath.clear();
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";
}
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";
QString line;
@ -215,6 +205,7 @@ bool rdConf(const QString &filePath, shared_t *share, bool reset)
{
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);
@ -245,27 +236,29 @@ bool rdConf(const QString &filePath, shared_t *share, bool reset)
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 = QDir::cleanPath(share->varPath) + "/footage/" + share->camName;
share->recPath = "/var/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;
@ -277,7 +270,30 @@ bool rdConf(const QString &filePath, shared_t *share, bool reset)
}
}
return ret;
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");
}
}
QString buildThreadCount(int count)

View File

@ -13,8 +13,6 @@
// 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>
@ -30,25 +28,17 @@
#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.6"
#define APP_VERSION "3.5"
#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
{
@ -56,41 +46,22 @@ enum CmdExeType
VID_LOOP
};
class ProcControl;
class Camera;
struct evt_t
{
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;
QList<QString> vidList;
QString timeStamp;
QString imgPath;
float score;
bool inQue;
int queAge;
};
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;
@ -103,7 +74,8 @@ struct shared_t
QString thumbExt;
QString recScale;
QString imgScale;
quint64 maxMemSize;
bool singleTenant;
bool skipCmd;
int liveSecs;
int recFps;
int evMaxSecs;
@ -113,8 +85,6 @@ struct shared_t
int retCode;
};
QString buildThreadCount(int count);
QStringList lsFilesInDir(const QString &path, const QString &ext = QString());
QStringList lsDirsInDir(const QString &path);
@ -122,11 +92,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 reset = true);
bool rdConf(const QString &filePath, shared_t *share);
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,11 +12,10 @@
#include "detect_loop.h"
DetectLoop::DetectLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : QObject(parent)
DetectLoop::DetectLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : QFileSystemWatcher(parent)
{
pcTimer = 0;
shared = sharedRes;
starved = true;
connect(thr, &QThread::started, this, &DetectLoop::init);
connect(thr, &QThread::finished, this, &DetectLoop::deleteLater);
@ -26,15 +25,15 @@ DetectLoop::DetectLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : QOb
void DetectLoop::init()
{
pcTimer = new QTimer(this);
checkTimer = new QTimer(this);
pcTimer = new QTimer(this);
connect(pcTimer, &QTimer::timeout, this, &DetectLoop::pcBreak);
connect(checkTimer, &QTimer::timeout, this, &DetectLoop::eTimeOut);
connect(pcTimer, &QTimer::timeout, this, &DetectLoop::pcBreak);
connect(this, &QFileSystemWatcher::directoryChanged, this, &DetectLoop::updated);
pcTimer->start(shared->postSecs * 1000);
checkTimer->start(5000);
checkTimer->setSingleShot(true);
setupBuffDir(shared->buffPath);
addPath(shared->buffPath + "/live");
}
void DetectLoop::reset()
@ -48,19 +47,35 @@ void DetectLoop::reset()
eventQue.timeStamp.clear();
}
void DetectLoop::eTimeOut()
void DetectLoop::updated(const QString &path)
{
starved = true;
eTimer.start();
emit starving();
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);
}
}
void DetectLoop::pcBreak()
{
prevClips.clear();
if (!shared->postCmd.isEmpty())
{
checkTimer->stop();
qInfo() << "---POST_BREAK---";
if (eventQue.inQue)
@ -82,8 +97,6 @@ void DetectLoop::pcBreak()
QProcess::execute(args[0], args.mid(1));
}
}
checkTimer->start();
}
}
@ -148,8 +161,10 @@ QStringList DetectLoop::buildSnapArgs(const QString &vidSrc, const QString &imgP
QString DetectLoop::statusLine()
{
if (starved)
if (eTimer.elapsed() >= 5000)
{
emit starving();
return "STARVED";
}
else
@ -158,95 +173,81 @@ QString DetectLoop::statusLine()
}
}
bool DetectLoop::pathFilter(const QString &path, const QString &subName)
void DetectLoop::exec()
{
return path.startsWith("/" + shared->camName + "/" + subName) && path.endsWith(shared->streamExt);
}
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);
void DetectLoop::fileFlushed(const QString &path)
{
if (pathFilter(path, "vid"))
if (compArgs.isEmpty())
{
if (vidAPath.isEmpty())
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))
{
vidAPath = shared->varPath + "/stream" + path;
imgAPath = QDir::cleanPath(shared->buffPath) + QDir::separator() + QString("img") + QDir::separator() + QFileInfo(vidAPath).baseName() + ".bmp";
QProcess extComp;
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";
extComp.start(compArgs[0], compArgs.mid(1));
extComp.waitForFinished();
QProcess::execute("ffmpeg", buildSnapArgs(vidBPath, imgBPath));
float score = 0;
starved = false;
checkTimer->start();
auto compArgs = buildArgs(imgAPath, imgBPath);
if (compArgs.isEmpty())
if (shared->outputType == "stdout")
{
qCritical() << "err: could not parse a executable name from img_comp_cmd: " << shared->compCmd;
score = getFloatFromExe(extComp.readAllStandardOutput());
}
else
{
QProcess extComp;
extComp.start(compArgs[0], compArgs.mid(1));
extComp.waitForFinished();
float score = 0;
if (shared->outputType == "stdout")
{
score = getFloatFromExe(extComp.readAllStandardOutput());
}
else
{
score = getFloatFromExe(extComp.readAllStandardError());
}
qInfo() << compArgs.join(" ") << " --result: " << QString::number(score);
if (eventQue.inQue)
{
eventQue.queAge += 2;
if (eventQue.score < score)
{
eventQue.score = score;
eventQue.imgPath = imgBPath;
}
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);
}
score = getFloatFromExe(extComp.readAllStandardError());
}
vidAPath = vidBPath;
imgAPath = imgBPath;
qInfo() << compArgs.join(" ") << " --result: " << QString::number(score);
if (eventQue.inQue)
{
eventQue.queAge += 4;
if (eventQue.score < score)
{
eventQue.score = score;
eventQue.imgPath = imgBPath;
}
eventQue.vidList.append(vidAPath);
eventQue.vidList.append(vidBPath);
if (eventQue.queAge >= shared->evMaxSecs)
{
eventQue.inQue = false;
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);
}
}
}
vidAPath.clear();
vidBPath.clear();
}

View File

@ -15,7 +15,7 @@
#include "common.h"
class DetectLoop : public QObject
class DetectLoop : public QFileSystemWatcher
{
Q_OBJECT
@ -23,37 +23,32 @@ private:
QString vidAPath;
QString vidBPath;
QString imgAPath;
QString imgBPath;
QString vidAName;
QString vidBName;
QStringList prevClips;
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 eTimeOut();
void updated(const QString &path);
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,17 +28,27 @@ void EventLoop::init()
{
loopTimer = new QTimer(this);
connect(loopTimer, &QTimer::timeout, this, &EventLoop::exec);
connect(loopTimer, &QTimer::timeout, this, &EventLoop::loopSlot);
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 + "/vid/" + event.timeStamp + ".ctmp";
auto concat = shared->buffPath + "/live/" + event.timeStamp + ".ctmp";
QFile file(concat, this);
@ -100,7 +110,7 @@ QString EventLoop::statusLine()
}
}
void EventLoop::exec()
bool EventLoop::exec()
{
enforceMaxEvents(shared);
enforceMaxImages(shared);
@ -112,8 +122,6 @@ void EventLoop::exec()
qInfo() << "attempting write out of event: " << event.timeStamp;
event.vidList.removeDuplicates();
if (wrOutVod(event))
{
QStringList args;
@ -125,4 +133,6 @@ void 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 exec();
void loopSlot();
private:
@ -36,6 +36,7 @@ public:
explicit EventLoop(shared_t *shared, QThread *thr, QObject *parent = nullptr);
bool exec();
QString statusLine();
};

View File

@ -38,14 +38,8 @@ 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;
@ -55,19 +49,9 @@ 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(globalConf.confPath);
showHelp(etcDir);
}
else if (args.contains("-v"))
{
@ -75,11 +59,22 @@ int main(int argc, char** argv)
}
else if (args.contains("-d"))
{
auto proc = new ProcControl(procFile, staDir, &globalConf, &app);
auto confs = lsFilesInDir(etcDir);
auto proc = new ProcControl(procFile, staDir, &app);
proc->startApp();
if (!proc->init())
{
ret = EACCES;
}
else
{
for (auto &&conf : confs)
{
new Camera(etcDir + "/" + conf, staDir, proc, &app);
}
ret = app.exec();
ret = app.exec();
}
}
else if (args.contains("-u"))
{
@ -126,7 +121,7 @@ int main(int argc, char** argv)
}
else
{
showHelp(globalConf.confPath);
showHelp(etcDir);
}
return ret;

View File

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

View File

@ -14,8 +14,6 @@
// GNU General Public License for more details.
#include "common.h"
#include "ram_disk.h"
#include "camera.h"
class ProcControl : public QObject
{
@ -24,37 +22,26 @@ 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, shared_t *glbShare, QCoreApplication *parent);
explicit ProcControl(const QString &procFile, const QString &statDir, QCoreApplication *parent);
void objPlusOne(const QString &conf);
void objPlusOne();
bool init();
public slots:
void objMinusOne(const QString &conf);
void closeApp();
void startApp();
void objMinusOne();
signals:

View File

@ -1,811 +0,0 @@
// 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");
}

View File

@ -1,91 +0,0 @@
#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,7 +22,6 @@ 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);
@ -43,6 +42,7 @@ 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 + "/vid/" + QString(STRFTIME_FMT) + shared->streamExt;
ret += shared->buffPath + "/live/" + QString(STRFTIME_FMT) + shared->streamExt;
return ret;
}
@ -80,11 +80,6 @@ QString RecordLoop::statusLine()
}
}
void RecordLoop::readErr()
{
//qCritical() << "err: " << readAllStandardError();
}
void RecordLoop::resetTime()
{
checkTimer->start();
@ -101,6 +96,8 @@ 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,7 +22,6 @@ class RecordLoop : public QProcess
private slots:
void init();
void readErr();
void resetTime();
private: