From d1d74014889ae512d17bf88eb38b610465aa32f6 Mon Sep 17 00:00:00 2001 From: Mr-Dave Date: Sun, 10 Apr 2022 23:15:35 -0600 Subject: [PATCH] Add recording functionality to webcontrol --- configure.ac | 32 ++- src/Makefile.am | 2 +- src/dbse.cpp | 318 +++++++++++++++++++++++++++++- src/dbse.hpp | 105 ++++++---- src/event.cpp | 4 + src/motion_loop.cpp | 14 +- src/motionplus.cpp | 3 + src/motionplus.hpp | 3 +- src/webu.cpp | 64 ++++-- src/webu.hpp | 10 +- src/webu_file.cpp | 121 ++++++++++++ src/webu_file.hpp | 27 +++ src/webu_html.cpp | 467 ++++++++++++++++++++++++++++++-------------- src/webu_json.cpp | 86 ++++++++ src/webu_json.hpp | 1 + 15 files changed, 1037 insertions(+), 220 deletions(-) create mode 100644 src/webu_file.cpp create mode 100644 src/webu_file.hpp diff --git a/configure.ac b/configure.ac index 808a6ab1..f88fe7b2 100644 --- a/configure.ac +++ b/configure.ac @@ -415,25 +415,41 @@ AS_IF([test "${PGSQL}" = "no"], [ ) ############################################################################## -### Check for SQLITE3 - Optional +### Check for SQLITE3 ############################################################################## -AC_ARG_WITH(sqlite3, - AS_HELP_STRING([--without-sqlite3],[Disable sqlite3 support.]), - [SQLITE3="$withval"], +AC_ARG_WITH([sqlite3], + AS_HELP_STRING([--with-sqlite3[=DIR]],[Build with sqlite3 support]), + [SQLITE3=$withval], [SQLITE3="yes"] ) + AS_IF([test "${SQLITE3}" = "no"], [ - AC_MSG_CHECKING(for sqlite3) - AC_MSG_RESULT(skipping) + AC_MSG_CHECKING(for SQLite3) + AC_MSG_ERROR([Package sqlite3 is required.]) ],[ - AC_CHECK_HEADERS(sqlite3.h, [ + AC_MSG_CHECKING(SQLite3 pkg-config path) + TEMP_PATH=$PKG_CONFIG_PATH + AS_IF([test "${SQLITE3}" != "yes"], [ + PKG_CONFIG_PATH=${SQLITE3}/lib/pkgconfig:$PKG_CONFIG_PATH + SQLITE3="yes" + ] + ) + export PKG_CONFIG_PATH + AC_MSG_RESULT($PKG_CONFIG_PATH done) + + AC_MSG_CHECKING(for SQLite3) + AS_IF([pkg-config sqlite3], [ TEMP_CPPFLAGS="$TEMP_CPPFLAGS "`pkg-config --cflags sqlite3` TEMP_LIBS="$TEMP_LIBS "`pkg-config --libs sqlite3` AC_DEFINE([HAVE_SQLITE3], [1], [Define to 1 if you have SQLITE3 support]) + AC_MSG_RESULT(yes) ],[ - SQLITE3="no" + AC_MSG_RESULT(no) + AC_MSG_ERROR([Required SQLite3 packages were not found. Please check motionplus_guide.html and install necessary dependencies]) ] ) + PKG_CONFIG_PATH=$TEMP_PATH + export PKG_CONFIG_PATH ] ) diff --git a/src/Makefile.am b/src/Makefile.am index 67a40a15..45811a79 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -31,6 +31,6 @@ bin_PROGRAMS = motionplus motionplus_SOURCES = motionplus.cpp motion_loop.cpp logger.cpp conf.cpp util.cpp alg.cpp alg_sec.cpp\ video_v4l2.cpp video_common.cpp video_loopback.cpp netcam.cpp jpegutils.cpp exif.cpp \ rotate.cpp draw.cpp event.cpp movie.cpp picture.cpp dbse.cpp \ - webu.cpp webu_html.cpp webu_stream.cpp webu_json.cpp webu_post.cpp \ + webu.cpp webu_html.cpp webu_stream.cpp webu_json.cpp webu_post.cpp webu_file.cpp \ mmalcam.cpp $(MMAL_SRC) diff --git a/src/dbse.cpp b/src/dbse.cpp index 575cb7f1..f7e5e48a 100644 --- a/src/dbse.cpp +++ b/src/dbse.cpp @@ -442,7 +442,7 @@ static void dbse_mysql_exec(char *sqlquery,struct ctx_cam *cam, int save_id) } } if (save_id) { - cam->dbse->database_event_id = (unsigned long long) mysql_insert_id(cam->dbse->database_mysql); + cam->dbse->database_event_id = (uint64_t) mysql_insert_id(cam->dbse->database_mysql); } #else (void)sqlquery; @@ -494,7 +494,7 @@ static void dbse_mariadb_exec(char *sqlquery,struct ctx_cam *cam, int save_id) } } if (save_id) { - cam->dbse->database_event_id = (unsigned long long) mysql_insert_id(cam->dbse->database_mariadb); + cam->dbse->database_event_id = (uint64_t) mysql_insert_id(cam->dbse->database_mariadb); } #else (void)sqlquery; @@ -562,7 +562,6 @@ static void dbse_sqlite3_exec(char *sqlquery,struct ctx_cam *cam, int save_id) sqlite3_free(errmsg); } if (save_id) { - //ToDO: Find the equivalent option for sqlite3 cam->dbse->database_event_id = 0; } #else @@ -639,3 +638,316 @@ void dbse_fileclose(struct ctx_cam *cam, char *filename, int sqltype, struct tim } } + +static int dbse_motpls_callback( + void *ptr, int arg_nb, char **arg_val, char **col_nm) +{ + ctx_cam *cam = (ctx_cam *)ptr; + struct stat statbuf; + int indx, rnbr, flen; + + (void)col_nm; + + if (cam->dbsemp->dbse_action == DBSE_ACT_CHKTBL) { + for (indx=0; indx < arg_nb; indx++) { + if (mystrceq(arg_val[indx],"motionplus")) { + cam->dbsemp->table_ok = true; + } + } + } else if (cam->dbsemp->dbse_action == DBSE_ACT_GETCNT) { + for (indx=0; indx < arg_nb; indx++) { + if (mystrceq(col_nm[indx],"movie_cnt")) { + cam->dbsemp->movie_cnt =atoi(arg_val[indx]); + } + } + + } else if (cam->dbsemp->dbse_action == DBSE_ACT_GETTBL) { + rnbr = cam->dbsemp->movie_indx; + if (rnbr < cam->dbsemp->movie_cnt) { + cam->dbsemp->movie_list[rnbr].rowid = -1; + cam->dbsemp->movie_list[rnbr].camid = -1; + cam->dbsemp->movie_list[rnbr].movie_nm = NULL; + cam->dbsemp->movie_list[rnbr].movie_sz = 0; + cam->dbsemp->movie_list[rnbr].movie_dtl = 0; + cam->dbsemp->movie_list[rnbr].found = false; + // select rowid,camid,movie_nm,movie_sz,movie_dtl + cam->dbsemp->movie_list[rnbr].rowid =atoi(arg_val[0]); + cam->dbsemp->movie_list[rnbr].camid =atoi(arg_val[1]); + if (arg_val[2] != NULL) { + flen = strlen(arg_val[2]); + cam->dbsemp->movie_list[rnbr].movie_nm = + (char*)mymalloc(flen + 1); + snprintf(cam->dbsemp->movie_list[rnbr].movie_nm + ,flen+1,"%s",arg_val[2]); + flen += cam->conf->target_dir.length() + 2; + cam->dbsemp->movie_list[rnbr].full_nm = + (char*)mymalloc(flen + 1); + snprintf(cam->dbsemp->movie_list[rnbr].full_nm + ,flen,"%s/%s",cam->conf->target_dir.c_str(), arg_val[2]); + + if (stat(cam->dbsemp->movie_list[rnbr].full_nm, &statbuf) == 0) { + cam->dbsemp->movie_list[rnbr].found = true; + } + } + cam->dbsemp->movie_list[rnbr].movie_sz =atoi(arg_val[3]); + cam->dbsemp->movie_list[rnbr].movie_dtl =atoi(arg_val[4]); + } + cam->dbsemp->movie_indx++; + } + + return 0; +} + +/* Free the list of movies*/ +static void dbse_motpls_freelist(struct ctx_cam *cam) +{ + int indx; + + if (cam->dbsemp == NULL) { + return; + } + + for (indx=0; indxdbsemp->movie_cnt; indx++) { + if (cam->dbsemp->movie_list[indx].movie_nm != NULL) { + free(cam->dbsemp->movie_list[indx].movie_nm); + cam->dbsemp->movie_list[indx].movie_nm = NULL; + } + if (cam->dbsemp->movie_list[indx].full_nm != NULL) { + free(cam->dbsemp->movie_list[indx].full_nm); + cam->dbsemp->movie_list[indx].full_nm = NULL; + } + } + + if (cam->dbsemp->movie_list != NULL) { + free(cam->dbsemp->movie_list); + cam->dbsemp->movie_list = NULL; + } + cam->dbsemp->movie_cnt = 0; + +} + +/* Validate database has our required table, if not create it */ +static int dbse_motpls_validate(struct ctx_cam *cam) +{ + int retcd; + char *errmsg = 0; + std::string sqlquery; + + sqlquery = + "select name from sqlite_master" + " where type='table' " + " and name='motionplus';"; + cam->dbsemp->table_ok = false; + cam->dbsemp->dbse_action = DBSE_ACT_CHKTBL; + retcd = sqlite3_exec(cam->dbsemp->database_sqlite3 + , sqlquery.c_str(), dbse_motpls_callback, cam, &errmsg); + if (retcd != SQLITE_OK ) { + MOTION_LOG(ERR, TYPE_DB, NO_ERRNO + , _("Error checking table: %s"), errmsg); + sqlite3_free(errmsg); + return -1; + } + + if (cam->dbsemp->table_ok == false) { + sqlquery = + "create table motionplus (" + "camid int" + ",movie_nm text" + ",movie_sz int" + ",movie_dtl int" + ");"; + retcd = sqlite3_exec(cam->dbsemp->database_sqlite3 + , sqlquery.c_str(), 0, 0, &errmsg); + if (retcd != SQLITE_OK ) { + MOTION_LOG(ERR, TYPE_DB, NO_ERRNO + , _("Error creating table: %s"), errmsg); + sqlite3_free(errmsg); + return -1; + } + } + + return 0; + +} + +/* Open and validate the motionplus database*/ +void dbse_motpls_init(struct ctx_cam *cam) +{ + std::string dbname; + + cam->dbsemp = new ctx_dbsemp; + cam->dbsemp->movie_cnt = 0; + cam->dbsemp->movie_list = NULL; + + dbname = cam->conf->target_dir + "/dbcam" + + std::to_string(cam->camera_id)+".db"; + + MOTION_LOG(NTC, TYPE_DB, NO_ERRNO, "%s", dbname.c_str()); + + if (sqlite3_open(dbname.c_str(), &cam->dbsemp->database_sqlite3) != SQLITE_OK) { + MOTION_LOG(ERR, TYPE_DB, NO_ERRNO + ,_("Can't open database %s : %s") + ,dbname.c_str(), sqlite3_errmsg(cam->dbsemp->database_sqlite3)); + sqlite3_close(cam->dbsemp->database_sqlite3); + cam->dbsemp->database_sqlite3 = NULL; + return; + } + + if (dbse_motpls_validate(cam) != 0) { + sqlite3_close(cam->dbsemp->database_sqlite3); + cam->dbsemp->database_sqlite3 = NULL; + return; + }; + + return; + +} + +/* Close the motionplus database */ +void dbse_motpls_deinit(struct ctx_cam *cam) +{ + if (cam->dbsemp != NULL) { + if (cam->dbsemp->database_sqlite3 != NULL) { + sqlite3_close(cam->dbsemp->database_sqlite3); + } + cam->dbsemp->database_sqlite3 = NULL; + dbse_motpls_freelist(cam); + + delete cam->dbsemp; + } +} + +/* Execute query against the motionplus database */ +void dbse_motpls_exec(const char *sqlquery, struct ctx_cam *cam) +{ + int retcd; + char *errmsg = 0; + + if (cam->dbsemp == NULL) { + return; + } + if (cam->dbsemp->database_sqlite3 == NULL) { + return; + } + + retcd = sqlite3_exec(cam->dbsemp->database_sqlite3, sqlquery, NULL, 0, &errmsg); + if (retcd != SQLITE_OK ) { + MOTION_LOG(ERR, TYPE_DB, NO_ERRNO, _("SQLite error was %s"), errmsg); + sqlite3_free(errmsg); + } +} + +/* Populate the list of the movies from the database*/ +int dbse_motpls_getlist(struct ctx_cam *cam) +{ + int retcd, indx; + char *errmsg = 0; + std::string sqlquery; + + if (cam->dbsemp == NULL) { + return -1; + } + + dbse_motpls_freelist(cam); + + if (cam->dbsemp->database_sqlite3 == NULL) { + return -1; + } + + sqlquery = + "select count(*) as movie_cnt " + " from motionplus " + " where camid = " + std::to_string(cam->camera_id) + ";"; + cam->dbsemp->dbse_action = DBSE_ACT_GETCNT; + retcd = sqlite3_exec(cam->dbsemp->database_sqlite3 + , sqlquery.c_str(), dbse_motpls_callback, cam, &errmsg); + if (retcd != SQLITE_OK ) { + MOTION_LOG(ERR, TYPE_DB, NO_ERRNO + , _("Error counting table: %s"), errmsg); + sqlite3_free(errmsg); + return -1; + } + + if (cam->dbsemp->movie_cnt > 0) { + cam->dbsemp->movie_list =(ctx_dbsemp_rec *) + mymalloc(sizeof(ctx_dbsemp_rec)*cam->dbsemp->movie_cnt); + cam->dbsemp->movie_indx = 0; + sqlquery = + "select rowid,camid,movie_nm,movie_sz,movie_dtl" + " from motionplus " + " where camid = " + std::to_string(cam->camera_id) + ";"; + cam->dbsemp->dbse_action = DBSE_ACT_GETTBL; + retcd = sqlite3_exec(cam->dbsemp->database_sqlite3 + , sqlquery.c_str(), dbse_motpls_callback, cam, &errmsg); + if (retcd != SQLITE_OK ) { + MOTION_LOG(ERR, TYPE_DB, NO_ERRNO + , _("Error retrieving table: %s"), errmsg); + sqlite3_free(errmsg); + return -1; + } + /* Clean the database of files that were removed*/ + for (indx=0; indxdbsemp->movie_cnt; indx++) { + if (cam->dbsemp->movie_list[indx].found == false) { + sqlquery = + "delete from motionplus " + " where camid = " + std::to_string(cam->camera_id) + + " and rowid = " + std::to_string( + cam->dbsemp->movie_list[indx].rowid)+";"; + dbse_motpls_exec(sqlquery.c_str(),cam); + } + /*since we run vacuum after this, the rowids will no longer be valid*/ + cam->dbsemp->movie_list[indx].rowid = -1; + } + sqlquery ="vacuum"; + dbse_motpls_exec(sqlquery.c_str(),cam); + } + + return 0; +} + +/* Add a record for new movies */ +void dbse_motpls_addrec(struct ctx_cam *cam,char *fname, struct timespec *ts1) +{ + std::string sqlquery; + struct stat statbuf; + char *errmsg = 0; + int retcd, dirlen; + int64_t bsz; + char dtl[12]; + struct tm timestamp_tm; + + if (cam->dbsemp == NULL) { + return; + } + if (cam->dbsemp->database_sqlite3 == NULL) { + return; + } + + if (stat(fname, &statbuf) == 0) { + bsz = statbuf.st_size; + } else { + bsz = 0; + } + localtime_r(&ts1->tv_sec, ×tamp_tm); + strftime(dtl, 11, "%G%m%d", ×tamp_tm); + + /* Add 1 for last slash */ + dirlen = cam->conf->target_dir.length() + 1; + if (dirlen >= (int)strlen(fname)) { + return; + } + + sqlquery = "insert into motionplus "; + sqlquery += " (camid,movie_nm,movie_sz,movie_dtl)"; + sqlquery += " values ("+std::to_string(cam->camera_id); + sqlquery += " ,'" + std::string(fname + dirlen) + "'"; + sqlquery += " ," + std::to_string(bsz); + sqlquery += " ," + std::string(dtl); + sqlquery += ")"; + + retcd = sqlite3_exec(cam->dbsemp->database_sqlite3, sqlquery.c_str(), NULL, 0, &errmsg); + if (retcd != SQLITE_OK ) { + MOTION_LOG(ERR, TYPE_DB, NO_ERRNO, _("SQLite error was %s"), errmsg); + sqlite3_free(errmsg); + } +} diff --git a/src/dbse.hpp b/src/dbse.hpp index c63e9e44..fd785f7c 100644 --- a/src/dbse.hpp +++ b/src/dbse.hpp @@ -24,51 +24,84 @@ #ifndef _INCLUDE_DBSE_HPP_ #define _INCLUDE_DBSE_HPP_ -#ifdef HAVE_MYSQL - #include -#endif - -#ifdef HAVE_MARIADB - #include -#endif - -#ifdef HAVE_SQLITE3 - #include -#endif - -#ifdef HAVE_PGSQL - #include -#endif - -struct ctx_dbse { - int sql_mask; - unsigned long long database_event_id; - - #ifdef HAVE_SQLITE3 - sqlite3 *database_sqlite3; - #endif - #ifdef HAVE_MYSQL - MYSQL *database_mysql; + #include #endif #ifdef HAVE_MARIADB - MYSQL *database_mariadb; + #include + #endif + + #ifdef HAVE_SQLITE3 + #include #endif #ifdef HAVE_PGSQL - PGconn *database_pg; + #include #endif -}; + enum DBSE_ACT { + DBSE_ACT_CHKTBL = 0, + DBSE_ACT_GETCNT = 1, + DBSE_ACT_GETTBL = 2 + }; -void dbse_global_deinit(struct ctx_motapp *motapp); -void dbse_global_init(struct ctx_motapp *motapp); -void dbse_init(struct ctx_cam *cam); -void dbse_deinit(struct ctx_cam *cam); -void dbse_sqlmask_update(struct ctx_cam *cam); -void dbse_firstmotion(struct ctx_cam *cam); -void dbse_newfile(struct ctx_cam *cam, char *filename, int sqltype, struct timespec *ts1); -void dbse_fileclose(struct ctx_cam *cam, char *filename, int sqltype, struct timespec *ts1); + + /* Database structure for user specified database*/ + struct ctx_dbse { + #ifdef HAVE_SQLITE3 + sqlite3 *database_sqlite3; + #endif + #ifdef HAVE_MYSQL + MYSQL *database_mysql; + #endif + #ifdef HAVE_MARIADB + MYSQL *database_mariadb; + #endif + #ifdef HAVE_PGSQL + PGconn *database_pg; + #endif + + int sql_mask; + uint64_t database_event_id; + }; + + /* Record structure of motionplus database */ + struct ctx_dbsemp_rec { + int64_t rowid; /*rowid from database*/ + int camid; /*camera id */ + char *movie_nm; /*Name of the movie file*/ + char *full_nm; /*Name of the movie file*/ + int64_t movie_sz; /*Size of the movie file in bytes*/ + int movie_dtl; /*Date in yyyymmdd format for the movie file*/ + bool found; /*Bool for whether the file exists in dir*/ + }; + + /* Database structure for motionplus dedicated database*/ + struct ctx_dbsemp { + #ifdef HAVE_SQLITE3 + sqlite3 *database_sqlite3; + #endif + enum DBSE_ACT dbse_action; /* action to perform with query*/ + bool table_ok; /* bool of whether table exists*/ + int movie_indx; /* index of movie_list */ + int movie_cnt; /* count of movie_list */ + struct ctx_dbsemp_rec *movie_list; /* list of movies from the database*/ + }; + + + void dbse_global_deinit(struct ctx_motapp *motapp); + void dbse_global_init(struct ctx_motapp *motapp); + void dbse_init(struct ctx_cam *cam); + void dbse_deinit(struct ctx_cam *cam); + void dbse_sqlmask_update(struct ctx_cam *cam); + void dbse_firstmotion(struct ctx_cam *cam); + void dbse_newfile(struct ctx_cam *cam, char *filename, int sqltype, struct timespec *ts1); + void dbse_fileclose(struct ctx_cam *cam, char *filename, int sqltype, struct timespec *ts1); + void dbse_motpls_init(struct ctx_cam *cam); + void dbse_motpls_deinit(struct ctx_cam *cam); + void dbse_motpls_exec(char *sqlquery, struct ctx_cam *cam); + int dbse_motpls_getlist(struct ctx_cam *cam); + void dbse_motpls_addrec(struct ctx_cam *cam,char *fname, struct timespec *ts1); #endif /* _INCLUDE_DBSE_HPP_ */ \ No newline at end of file diff --git a/src/event.cpp b/src/event.cpp index 6a475fc6..cc601f5a 100644 --- a/src/event.cpp +++ b/src/event.cpp @@ -717,9 +717,11 @@ static void event_movie_end(struct ctx_cam *cam, motion_event evnt } } else { event(cam, EVENT_FILECLOSE, NULL, cam->newfilename, (void *)FTYPE_MPEG, ts1); + dbse_motpls_addrec(cam, cam->newfilename, ts1); } } else { event(cam, EVENT_FILECLOSE, NULL, cam->newfilename, (void *)FTYPE_MPEG, ts1); + dbse_motpls_addrec(cam, cam->newfilename, ts1); } } @@ -739,9 +741,11 @@ static void event_movie_end(struct ctx_cam *cam, motion_event evnt } } else { event(cam, EVENT_FILECLOSE, NULL, cam->motionfilename, (void *)FTYPE_MPEG_MOTION, ts1); + dbse_motpls_addrec(cam, cam->motionfilename, ts1); } } else { event(cam, EVENT_FILECLOSE, NULL, cam->motionfilename, (void *)FTYPE_MPEG_MOTION, ts1); + dbse_motpls_addrec(cam, cam->motionfilename, ts1); } } diff --git a/src/motion_loop.cpp b/src/motion_loop.cpp index 8181cd2e..5e4de6a1 100644 --- a/src/motion_loop.cpp +++ b/src/motion_loop.cpp @@ -666,10 +666,10 @@ static void mlp_init_ref(struct ctx_cam *cam) /** mlp_init */ static int mlp_init(struct ctx_cam *cam) { - MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO,_("initialize loop")); - mythreadname_set("ml",cam->threadnr,cam->conf->camera_name.c_str()); + MOTION_LOG(INF, TYPE_ALL, NO_ERRNO,_("Initialize loop")); + pthread_setspecific(tls_key_threadnr, (void *)((unsigned long)cam->threadnr)); if (init_camera_type(cam) != 0 ) { @@ -704,6 +704,8 @@ static int mlp_init(struct ctx_cam *cam) dbse_init(cam); + dbse_motpls_init(cam); + pic_init_mask(cam); pic_init_privacy(cam); @@ -729,15 +731,15 @@ void mlp_cleanup(struct ctx_cam *cam) event(cam, EVENT_TLAPSE_END, NULL, NULL, NULL, NULL); - /*if (cam->event_nr == cam->prev_event) { + if (cam->event_nr == cam->prev_event) { mlp_ring_process(cam); if (cam->imgs.image_preview.diffs) { event(cam, EVENT_IMAGE_PREVIEW, NULL, NULL, NULL, &cam->current_image->imgts); cam->imgs.image_preview.diffs = 0; } - */ + event(cam, EVENT_END, NULL, NULL, NULL, &cam->current_image->imgts); - /* } */ + } webu_stream_deinit(cam); @@ -858,6 +860,8 @@ void mlp_cleanup(struct ctx_cam *cam) dbse_deinit(cam); + dbse_motpls_deinit(cam); + } static void mlp_areadetect(struct ctx_cam *cam) diff --git a/src/motionplus.cpp b/src/motionplus.cpp index 504afde9..11c62cf3 100644 --- a/src/motionplus.cpp +++ b/src/motionplus.cpp @@ -655,6 +655,7 @@ static void motion_cam_add(struct ctx_motapp *motapp) motapp->cam_list[indx_cam]->conf->camera_id = indx; motapp->cam_list[indx_cam]->dbse = (struct ctx_dbse *)mymalloc(sizeof(struct ctx_dbse)); motapp->cam_list[indx_cam]->conf->webcontrol_port = 0; + dbse_motpls_init(motapp->cam_list[indx_cam]); motapp->cam_add = false; @@ -686,6 +687,8 @@ static void motion_cam_delete(struct ctx_motapp *motapp) } motapp->cam_list[motapp->cam_delete]->dbse = NULL; + dbse_motpls_deinit(motapp->cam_list[motapp->cam_delete]); + /* Delete the config context */ delete motapp->cam_list[motapp->cam_delete]->conf; delete motapp->cam_list[motapp->cam_delete]; diff --git a/src/motionplus.hpp b/src/motionplus.hpp index 64db8583..ffa830ec 100644 --- a/src/motionplus.hpp +++ b/src/motionplus.hpp @@ -298,7 +298,8 @@ struct ctx_cam { struct ctx_image_data *current_image; /* Pointer to a structure where the image, diffs etc is stored */ struct ctx_algsec *algsec; struct ctx_rotate *rotate_data; /* rotation data is thread-specific */ - struct ctx_dbse *dbse; + struct ctx_dbse *dbse; /*Multi use/type user specified database */ + struct ctx_dbsemp *dbsemp; /*Dedicated sqlite3 Motionplus database */ struct ctx_movie *movie_norm; struct ctx_movie *movie_motion; struct ctx_movie *movie_timelapse; diff --git a/src/webu.cpp b/src/webu.cpp index 2ac7894b..8280f0b3 100644 --- a/src/webu.cpp +++ b/src/webu.cpp @@ -30,6 +30,7 @@ #include "webu_stream.hpp" #include "webu_json.hpp" #include "webu_post.hpp" +#include "webu_file.hpp" #include "video_v4l2.hpp" /* Context to pass the parms to functions to start mhd */ @@ -55,6 +56,7 @@ static void webu_context_init(struct ctx_motapp *motapp, struct ctx_webui *webui webui->uri_camid = ""; webui->uri_cmd1 = ""; webui->uri_cmd2 = ""; + webui->uri_cmd3 = ""; webui->clientip = ""; webui->lang = ""; /* Two digit lang code */ @@ -70,6 +72,7 @@ static void webu_context_init(struct ctx_motapp *motapp, struct ctx_webui *webui webui->stream_fps = 1; /* Stream rate */ webui->resp_page = ""; /* The response being constructed */ webui->post_info = NULL; + webui->req_file = NULL; webui->post_sz = 0; webui->motapp = motapp; /* The motion application context */ webui->cam = NULL; /* The context pointer for a single camera */ @@ -189,9 +192,10 @@ static void webu_parms_edit(struct ctx_webui *webui) } MOTION_LOG(DBG, TYPE_STREAM, NO_ERRNO - , "camid: >%s< thread: >%d< cmd1: >%s< cmd2: >%s<" + , "camid: >%s< thread: >%d< cmd1: >%s< cmd2: >%s< cmd3: >%s<" , webui->uri_camid.c_str(), webui->threadnbr - , webui->uri_cmd1.c_str(), webui->uri_cmd2.c_str()); + , webui->uri_cmd1.c_str(), webui->uri_cmd2.c_str() + , webui->uri_cmd3.c_str()); } @@ -199,15 +203,14 @@ static void webu_parms_edit(struct ctx_webui *webui) /* Extract the camid and cmds from the url */ static int webu_parseurl(struct ctx_webui *webui) { - int retcd; char *tmpurl; size_t pos_slash1, pos_slash2; - /* Example: /camid/cmd1/cmd2 */ - retcd = 0; + /* Example: /camid/cmd1/cmd2/cmd3 */ webui->uri_camid = ""; webui->uri_cmd1 = ""; webui->uri_cmd2 = ""; + webui->uri_cmd3 = ""; if (webui->url.length() == 0) { return -1; @@ -233,6 +236,11 @@ static int webu_parseurl(struct ctx_webui *webui) return 0; } + /* Remove any trailing slash */ + if (webui->url.substr(webui->url.length()-1,1) == "/") { + webui->url = webui->url.substr(0, webui->url.length()-1); + } + pos_slash1 = webui->url.find("/", 1); if (pos_slash1 != std::string::npos) { webui->uri_camid = webui->url.substr(1, pos_slash1 - 1); @@ -259,15 +267,26 @@ static int webu_parseurl(struct ctx_webui *webui) return 0; } - pos_slash2 = webui->url.find("/", pos_slash1); - if (pos_slash2 != std::string::npos) { - webui->uri_cmd2 = webui->url.substr(pos_slash1, pos_slash2 - pos_slash1); - } else { + if (webui->uri_cmd1 == "movies") { + /* Whole remaining url is the movie name and possibly subdir */ webui->uri_cmd2 = webui->url.substr(pos_slash1); return 0; - } + } else { + pos_slash2 = webui->url.find("/", pos_slash1); + if (pos_slash2 != std::string::npos) { + webui->uri_cmd2 = webui->url.substr(pos_slash1, pos_slash2 - pos_slash1); + } else { + webui->uri_cmd1 = webui->url.substr(pos_slash1); + return 0; + } - return retcd; + pos_slash1 = ++pos_slash2; + if (pos_slash1 >= webui->url.length()) { + return 0; + } + webui->uri_cmd3 = webui->url.substr(pos_slash1); + } + return 0; } @@ -823,8 +842,15 @@ static mhdrslt webu_answer_get(struct ctx_webui *webui) retcd = webu_mhd_send(webui); } - } else if ((webui->uri_camid == "config.json") || - (webui->uri_cmd1 == "config.json")) { + } else if (webui->uri_cmd1 == "movies") { + + retcd = webu_file_main(webui); + if (retcd == MHD_NO) { + webu_html_badreq(webui); + retcd = webu_mhd_send(webui); + } + + } else if (webui->uri_cmd1 == "config.json") { pthread_mutex_lock(&webui->motapp->mutex_post); webu_json_config(webui); @@ -835,6 +861,17 @@ static mhdrslt webu_answer_get(struct ctx_webui *webui) MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("send page failed.")); } + } else if (webui->uri_cmd1 == "movies.json") { + + pthread_mutex_lock(&webui->motapp->mutex_post); + webu_json_movies(webui); + pthread_mutex_unlock(&webui->motapp->mutex_post); + + retcd = webu_mhd_send(webui); + if (retcd == MHD_NO) { + MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO ,_("send page failed.")); + } + } else { pthread_mutex_lock(&webui->motapp->mutex_post); if (webui->motapp->cam_list[0]->conf->webcontrol_interface == "user") { @@ -1336,6 +1373,7 @@ static void webu_init_actions(struct ctx_motapp *motapp) util_parms_add_default(motapp->webcontrol_actions,"camera_delete",parm_vl); util_parms_add_default(motapp->webcontrol_actions,"config",parm_vl); util_parms_add_default(motapp->webcontrol_actions,"ptz",parm_vl); + util_parms_add_default(motapp->webcontrol_actions,"movies","on"); } diff --git a/src/webu.hpp b/src/webu.hpp index afe2b863..68c070df 100644 --- a/src/webu.hpp +++ b/src/webu.hpp @@ -40,6 +40,7 @@ WEBUI_CNCT_MOTION = 3, WEBUI_CNCT_SOURCE = 4, WEBUI_CNCT_SECONDARY = 5, + WEBUI_CNCT_FILE = 6, WEBUI_CNCT_UNKNOWN = 99 }; @@ -57,9 +58,10 @@ struct ctx_webui { std::string url; /* The URL sent from the client */ - std::string uri_camid; /* Parsed camera number from the url eg /camid/cmd1/cmd2 */ - std::string uri_cmd1; /* Parsed command1 from the url eg /camid/cmd1/cmd2 */ - std::string uri_cmd2; /* Parsed command2 from the url eg /camid/cmd1/cmd2 */ + std::string uri_camid; /* Parsed camera number from the url eg /camid/cmd1/cmd2/cmd3 */ + std::string uri_cmd1; /* Parsed command1 from the url eg /camid/cmd1/cmd2/cmd3 */ + std::string uri_cmd2; /* Parsed command2 from the url eg /camid/cmd1/cmd2/cmd3 */ + std::string uri_cmd3; /* Parsed command3 from the url eg /camid/cmd1/cmd2/cmd3 */ std::string clientip; /* IP of the connecting client */ std::string hostfull; /* Full http name for host with port number */ @@ -87,6 +89,8 @@ struct ctx_key *post_info; /* Structure of the entries provided from the post data */ struct MHD_PostProcessor *post_processor; /* Processor for handling Post method connections */ + FILE *req_file; /* requested file*/ + enum WEBUI_METHOD cnct_method; /* Connection method. Get or Post */ uint64_t stream_pos; /* Stream position of sent image */ diff --git a/src/webu_file.cpp b/src/webu_file.cpp new file mode 100644 index 00000000..2b0d35b7 --- /dev/null +++ b/src/webu_file.cpp @@ -0,0 +1,121 @@ +/* + * This file is part of MotionPlus. + * + * MotionPlus 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. + * + * MotionPlus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MotionPlus. If not, see . + * + * Copyright 2020-2022 MotionMrDave@gmail.com +*/ + +#include "motionplus.hpp" +#include "conf.hpp" +#include "logger.hpp" +#include "util.hpp" +#include "picture.hpp" +#include "webu.hpp" +#include "webu_file.hpp" +#include "dbse.hpp" + + +/* Callback for the file reader response*/ +static ssize_t webu_file_reader (void *cls, uint64_t pos, char *buf, size_t max) +{ + struct ctx_webui *webui =(struct ctx_webui *)cls; + + (void)fseek (webui->req_file, pos, SEEK_SET); + return fread (buf, 1, max, webui->req_file); +} + +/* Close the requested file */ +static void webu_file_free (void *cls) +{ + struct ctx_webui *webui =(struct ctx_webui *)cls; + fclose (webui->req_file); +} + +/* Entry point for answering file request*/ +mhdrslt webu_file_main(struct ctx_webui *webui) +{ + mhdrslt retcd; + struct stat statbuf; + struct MHD_Response *response; + std::string full_nm; + int indx; + struct ctx_params *wact; + + /*If we have not fully started yet, simply return*/ + if (webui->cam->dbsemp == NULL) { + return MHD_NO; + } + + if (webui->cam->dbsemp->movie_cnt == 0) { + if (dbse_motpls_getlist(webui->cam) != 0) { + return MHD_NO; + } + } + + wact = webui->motapp->webcontrol_actions; + for (indx = 0; indx < wact->params_count; indx++) { + if (mystreq(wact->params_array[indx].param_name,"movies")) { + if (mystreq(wact->params_array[indx].param_value,"off")) { + MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, "Movies via webcontrol disabled"); + return MHD_NO; + } else { + break; + } + } + } + + full_nm = ""; + for (indx=0; indx < webui->cam->dbsemp->movie_cnt; indx++) { + if (mystreq(webui->cam->dbsemp->movie_list[indx].movie_nm + , webui->uri_cmd2.c_str())) { + full_nm = webui->cam->dbsemp->movie_list[indx].full_nm; + } + } + + if (stat(full_nm.c_str(), &statbuf) == 0) { + webui->req_file = fopen (full_nm.c_str(), "rbe"); + } else { + webui->req_file = NULL; + MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO + ,"Security warning: Client IP %s requested file: %s" + ,webui->clientip.c_str(), webui->uri_cmd2.c_str()); + } + + if (webui->req_file == NULL) { + webui->resp_page = "Bad File" + "Bad File"; + + response = MHD_create_response_from_buffer(webui->resp_page.length() + ,(void *)webui->resp_page.c_str(), MHD_RESPMEM_PERSISTENT); + retcd = MHD_queue_response (webui->connection, MHD_HTTP_NOT_FOUND, response); + MHD_destroy_response (response); + + } else { + response = MHD_create_response_from_callback ( + statbuf.st_size, 32 * 1024 + , &webu_file_reader + , webui + , &webu_file_free); + if (response == NULL) { + fclose (webui->req_file); + return MHD_NO; + } + retcd = MHD_queue_response (webui->connection, MHD_HTTP_OK, response); + MHD_destroy_response (response); + } + + return retcd; +} + diff --git a/src/webu_file.hpp b/src/webu_file.hpp new file mode 100644 index 00000000..bc47ea10 --- /dev/null +++ b/src/webu_file.hpp @@ -0,0 +1,27 @@ +/* + * This file is part of MotionPlus. + * + * MotionPlus 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. + * + * MotionPlus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MotionPlus. If not, see . + * + * Copyright 2020-2022 MotionMrDave@gmail.com +*/ + +#ifndef _INCLUDE_WEBU_FILE_HPP_ +#define _INCLUDE_WEBU_FILE_HPP_ + + struct ctx_webui; + + mhdrslt webu_file_main(struct ctx_webui *webui); + +#endif /* _INCLUDE_WEBU_FILE_HPP_ */ diff --git a/src/webu_html.cpp b/src/webu_html.cpp index 372261c6..4a00fd0e 100644 --- a/src/webu_html.cpp +++ b/src/webu_html.cpp @@ -198,6 +198,25 @@ static void webu_html_style_config(struct ctx_webui *webui) " width: 3rem;\n" " height: 1.5rem;\n" " margin: 0;\n" + " }\n" + " .cls_movies {\n" + " background-color: transparent;\n" + " color: white;\n" + " text-align: center;\n" + " margin-top: 0rem;\n" + " margin-bottom: 0rem;\n" + " font-weight: normal;\n" + " font-size: 0.90rem;\n" + " }\n" + " a:link {\n" + " color: white;\n" + " background-color: transparent;\n" + " text-decoration: none;\n" + " }\n" + " a:visited {\n" + " color: black;\n" + " background-color: transparent;\n" + " text-decoration: none;\n" " }\n"; } @@ -314,6 +333,13 @@ static void webu_html_navbar(struct ctx_webui *webui) " \n" + " \n" + " Recordings\n" + " \n" + " \n" " \n\n"; } @@ -334,6 +360,9 @@ static void webu_html_divmain(struct ctx_webui *webui) "
\n" " \n" "
\n\n" + "
\n" + " \n" + "
\n\n" " \n\n"; } @@ -374,7 +403,7 @@ static void webu_html_script_send_config(struct ctx_webui *webui) " request.onreadystatechange = function() {\n" " if (this.readyState == 4 && this.status == 200) {\n" - " xmlhttp.open('GET', '" + webui->hostfull + "/config.json');\n" + " xmlhttp.open('GET', '" + webui->hostfull + "/0/config.json');\n" " xmlhttp.send();\n\n" " }\n" " };\n" @@ -449,7 +478,7 @@ static void webu_html_script_send_reload(struct ctx_webui *webui) " request.onreadystatechange = function() {\n" " if (this.readyState == 4 && this.status == 200) {\n" - " xmlhttp.open('GET', '" + webui->hostfull + "/config.json');\n" + " xmlhttp.open('GET', '" + webui->hostfull + "/0/config.json');\n" " xmlhttp.send();\n\n" " }\n" " };\n" @@ -467,7 +496,6 @@ static void webu_html_script_send_reload(struct ctx_webui *webui) static void webu_html_script_dropchange_cam(struct ctx_webui *webui) { webui->resp_page += - " /*Cascade camera change in one dropdown to all the others*/\n" " function dropchange_cam(camobj) {\n" " var indx;\n\n" @@ -507,8 +535,10 @@ static void webu_html_script_config_click(struct ctx_webui *webui) " function config_click(actval) {\n" " config_hideall();\n" " document.getElementById('div_cam').style.display='none';\n" + " document.getElementById('div_movies').style.display='none';\n" " document.getElementById('div_config').style.display='inline';\n" - " document.getElementById('div_' + actval).style.display='inline';\n" + " document.getElementById('div_' + actval).style.display='inline';\n" + " cams_reset();\n" " }\n\n"; } @@ -531,18 +561,21 @@ static void webu_html_script_assign_cams(struct ctx_webui *webui) " var camcnt = pData['cameras']['count'];\n" " var html_drop = \"\\n\";\n" " var html_nav = \"\\n\";\n\n" + " var html_mov = \"\\n\";\n\n" " html_drop += \"