From e0a6294e8713c7880b0ce9e4fe894fd8545b939d Mon Sep 17 00:00:00 2001 From: zii Date: Sat, 1 Feb 2025 09:59:19 -0500 Subject: [PATCH] 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 --- JustMotion.pro | 4 + build.py | 0 install.py | 7 +- src/camera.cpp | 58 ++-- src/camera.h | 7 +- src/common.cpp | 110 +++--- src/common.h | 52 ++- src/detect_loop.cpp | 163 +++++---- src/detect_loop.h | 17 +- src/event_loop.cpp | 20 +- src/event_loop.h | 3 +- src/main.cpp | 39 ++- src/proc_control.cpp | 147 +++++--- src/proc_control.h | 23 +- src/ram_disk.cpp | 811 +++++++++++++++++++++++++++++++++++++++++++ src/ram_disk.h | 91 +++++ src/record_loop.cpp | 11 +- src/record_loop.h | 1 + 18 files changed, 1278 insertions(+), 286 deletions(-) mode change 100755 => 100644 build.py mode change 100755 => 100644 install.py create mode 100644 src/ram_disk.cpp create mode 100644 src/ram_disk.h diff --git a/JustMotion.pro b/JustMotion.pro index 69af38d..97a3776 100644 --- a/JustMotion.pro +++ b/JustMotion.pro @@ -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 diff --git a/build.py b/build.py old mode 100755 new mode 100644 diff --git a/install.py b/install.py old mode 100755 new mode 100644 index 9478f85..d5dcb76 --- a/install.py +++ b/install.py @@ -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]) diff --git a/src/camera.cpp b/src/camera.cpp index da6b7d0..bc6a6ef 100644 --- a/src/camera.cpp +++ b/src/camera.cpp @@ -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() diff --git a/src/camera.h b/src/camera.h index 0d10f68..175deec 100644 --- a/src/camera.h +++ b/src/camera.h @@ -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: diff --git a/src/common.cpp b/src/common.cpp index 6e49eb6..6d87d11 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -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 ¶m, const QString &line, int *value) } } +void rdLine(const QString ¶m, const QString &line, quint64 *value) +{ + if (line.startsWith(param)) + { + *value = line.mid(param.size()).trimmed().toULongLong(); + } +} + void rdLine(const QString ¶m, 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 = "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; @@ -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) diff --git a/src/common.h b/src/common.h index 39a26c1..bae809c 100644 --- a/src/common.h +++ b/src/common.h @@ -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 #include #include @@ -28,17 +30,25 @@ #include #include #include +#include #include +#include +#include +#include +#include +#include +#include using namespace std; -#define APP_VERSION "3.6.t7" +#define APP_VERSION "3.6.t8" #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 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 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 ¶m, const QString &line, QString *value); void rdLine(const QString ¶m, const QString &line, int *value); +void rdLine(const QString ¶m, const QString &line, quint64 *value); void rdLine(const QString ¶m, const QString &line, bool *value); void enforceMaxEvents(shared_t *share); void enforceMaxImages(shared_t *share); diff --git a/src/detect_loop.cpp b/src/detect_loop.cpp index b92db0d..88415ab 100644 --- a/src/detect_loop.cpp +++ b/src/detect_loop.cpp @@ -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(); } diff --git a/src/detect_loop.h b/src/detect_loop.h index ebd2c5c..279d9c2 100644 --- a/src/detect_loop.h +++ b/src/detect_loop.h @@ -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(); diff --git a/src/event_loop.cpp b/src/event_loop.cpp index 23b5674..7fb6996 100644 --- a/src/event_loop.cpp +++ b/src/event_loop.cpp @@ -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; } diff --git a/src/event_loop.h b/src/event_loop.h index c61f669..cb667c5 100644 --- a/src/event_loop.h +++ b/src/event_loop.h @@ -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(); }; diff --git a/src/main.cpp b/src/main.cpp index 463baef..b8cf506 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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; diff --git a/src/proc_control.cpp b/src/proc_control.cpp index 610c790..7bba755 100644 --- a/src/proc_control.cpp +++ b/src/proc_control.cpp @@ -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(); } } diff --git a/src/proc_control.h b/src/proc_control.h index a5bc970..58f064b 100644 --- a/src/proc_control.h +++ b/src/proc_control.h @@ -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: diff --git a/src/ram_disk.cpp b/src/ram_disk.cpp new file mode 100644 index 0000000..738081d --- /dev/null +++ b/src/ram_disk.cpp @@ -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 RamDisk::data = QHash(); +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(len) / static_cast(BLK_SIZE); + + return static_cast(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.removeFirst(); + } + + 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(offs); + auto iLen = static_cast(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"); +} diff --git a/src/ram_disk.h b/src/ram_disk.h new file mode 100644 index 0000000..b3500d7 --- /dev/null +++ b/src/ram_disk.h @@ -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 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 diff --git a/src/record_loop.cpp b/src/record_loop.cpp index eb949b5..bca7b0a 100644 --- a/src/record_loop.cpp +++ b/src/record_loop.cpp @@ -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"; diff --git a/src/record_loop.h b/src/record_loop.h index 494a779..81dc7dd 100644 --- a/src/record_loop.h +++ b/src/record_loop.h @@ -22,6 +22,7 @@ class RecordLoop : public QProcess private slots: void init(); + void readErr(); void resetTime(); private: