196 changes: 89 additions & 107 deletions mythplugins/mythzoneminder/mythzmserver/zmserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
* ============================================================ */


#include <algorithm>
#include <fstream>
#include <iostream>
#include <cstdlib>
#include <cstring>
Expand Down Expand Up @@ -49,12 +51,8 @@
// the version of the protocol we understand
#define ZM_PROTOCOL_VERSION "11"

// the maximum image size we are ever likely to get from ZM
#define MAX_IMAGE_SIZE (2048*1536*3)

#define ADD_STR(list,s) list += (s); (list) += "[]:[]";
// TODO rewrite after we require C++11, see http://en.cppreference.com/w/cpp/string/basic_string/to_string
#define ADD_INT(list,n) sprintf(m_buf, "%d", (n)); (list) += m_buf; (list) += "[]:[]";
#define ADD_INT(list,n) (list) += std::to_string(n); (list) += "[]:[]";

// error messages
#define ERROR_TOKEN_COUNT "Invalid token count"
Expand Down Expand Up @@ -106,75 +104,67 @@ bool checkVersion(int major, int minor, int revision)
void loadZMConfig(const string &configfile)
{
cout << "loading zm config from " << configfile << endl;
char line[512];
char val[250];

FILE *cfg = fopen(configfile.c_str(), "r");
if ( cfg == nullptr )
std::ifstream ifs(configfile);
if ( ifs.fail() )
{
fprintf(stderr, "Can't open %s\n", configfile.c_str());
}

while ( fgets( line, sizeof(line), cfg ) != nullptr )
string line {};
while ( std::getline(ifs, line) )
{
char *line_ptr = line;
// Trim off any cr/lf line endings
size_t chomp_len = strcspn( line_ptr, "\r\n" );
line_ptr[chomp_len] = '\0';

// Remove leading white space
size_t white_len = strspn( line_ptr, " \t" );
line_ptr += white_len;
// Trim off begining and ending whitespace including cr/lf line endings
constexpr const char *whitespace = " \t\r\n";
auto begin = line.find_first_not_of(whitespace);
if (begin == string::npos)
continue; // Only whitespace
auto end = line.find_last_not_of(whitespace);
if (end != string::npos)
end = end + 1;
line = line.substr(begin, end);

// Check for comment or empty line
if ( *line_ptr == '\0' || *line_ptr == '#' )
if ( line.empty() || line[0] == '#' )
continue;

// Remove trailing white space
char *temp_ptr = line_ptr+strlen(line_ptr)-1;
while ( *temp_ptr == ' ' || *temp_ptr == '\t' )
{
*temp_ptr-- = '\0';
temp_ptr--;
}

// Now look for the '=' in the middle of the line
temp_ptr = strchr( line_ptr, '=' );
if ( !temp_ptr )
auto index = line.find('=');
if (index == string::npos)
{
fprintf(stderr,"Invalid data in %s: '%s'\n", configfile.c_str(), line );
fprintf(stderr,"Invalid data in %s: '%s'\n", configfile.c_str(), line.c_str() );
continue;
}

// Assign the name and value parts
char *name_ptr = line_ptr;
char *val_ptr = temp_ptr+1;
string name = line.substr(0,index);
string val = line.substr(index+1);

// Trim trailing space from the name part
do
{
*temp_ptr = '\0';
temp_ptr--;
}
while ( *temp_ptr == ' ' || *temp_ptr == '\t' );
end = name.find_last_not_of(whitespace);
if (end != string::npos)
end = end + 1;
name = name.substr(0, end);

// Remove leading white space from the value part
white_len = strspn( val_ptr, " \t" );
val_ptr += white_len;

strncpy( val, val_ptr, strlen(val_ptr)+1 );
if ( strcasecmp( name_ptr, "ZM_DB_HOST" ) == 0 ) g_server = val;
else if ( strcasecmp( name_ptr, "ZM_DB_NAME" ) == 0 ) g_database = val;
else if ( strcasecmp( name_ptr, "ZM_DB_USER" ) == 0 ) g_user = val;
else if ( strcasecmp( name_ptr, "ZM_DB_PASS" ) == 0 ) g_password = val;
else if ( strcasecmp( name_ptr, "ZM_PATH_WEB" ) == 0 ) g_webPath = val;
else if ( strcasecmp( name_ptr, "ZM_PATH_BIN" ) == 0 ) g_binPath = val;
else if ( strcasecmp( name_ptr, "ZM_WEB_USER" ) == 0 ) g_webUser = val;
else if ( strcasecmp( name_ptr, "ZM_VERSION" ) == 0 ) g_zmversion = val;
else if ( strcasecmp( name_ptr, "ZM_PATH_MAP" ) == 0 ) g_mmapPath = val;
else if ( strcasecmp( name_ptr, "ZM_DIR_EVENTS" ) == 0 ) g_eventsPath = val;
}
fclose(cfg);
begin = val.find_first_not_of(whitespace);
if (begin != string::npos)
val = val.substr(begin);

// convert name to uppercase
std::transform(name.cbegin(), name.cend(), name.begin(), ::toupper);

if ( name == "ZM_DB_HOST" ) g_server = val;
else if ( name == "ZM_DB_NAME" ) g_database = val;
else if ( name == "ZM_DB_USER" ) g_user = val;
else if ( name == "ZM_DB_PASS" ) g_password = val;
else if ( name == "ZM_PATH_WEB" ) g_webPath = val;
else if ( name == "ZM_PATH_BIN" ) g_binPath = val;
else if ( name == "ZM_WEB_USER" ) g_webUser = val;
else if ( name == "ZM_VERSION" ) g_zmversion = val;
else if ( name == "ZM_PATH_MAP" ) g_mmapPath = val;
else if ( name == "ZM_DIR_EVENTS" ) g_eventsPath = val;
}
}

#if !defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 80000
Expand Down Expand Up @@ -526,7 +516,6 @@ ZMServer::ZMServer(int sock, bool debug)
m_debug = debug;

// get the shared memory key
char buf[100];
m_shmKey = 0x7a6d2000;
string setting = getZMSetting("ZM_SHM_KEY");

Expand All @@ -539,8 +528,9 @@ ZMServer::ZMServer(int sock, bool debug)

if (m_debug)
{
snprintf(buf, sizeof(buf), "0x%x", (unsigned int)m_shmKey);
cout << "Shared memory key is: " << buf << endl;
cout << "Shared memory key is: 0x"
<< std::hex << (unsigned int)m_shmKey
<< std::dec << endl;
}

// get the MMAP path
Expand All @@ -557,14 +547,13 @@ ZMServer::ZMServer(int sock, bool debug)
// get the event filename format
setting = getZMSetting("ZM_EVENT_IMAGE_DIGITS");
int eventDigits = atoi(setting.c_str());
snprintf(buf, sizeof(buf), "%%0%dd-capture.jpg", eventDigits);
m_eventFileFormat = buf;
string eventDigitsFmt = "%0" + std::to_string(eventDigits) + "d";
m_eventFileFormat = eventDigitsFmt + "-capture.jpg";
if (m_debug)
cout << "Event file format is: " << m_eventFileFormat << endl;

// get the analysis filename format
snprintf(buf, sizeof(buf), "%%0%dd-analyse.jpg", eventDigits);
m_analysisFileFormat = buf;
m_analysisFileFormat = eventDigitsFmt + "-analyse.jpg";
if (m_debug)
cout << "Analysis file format is: " << m_analysisFileFormat << endl;

Expand Down Expand Up @@ -701,10 +690,9 @@ bool ZMServer::processRequest(char* buf, int nbytes)
bool ZMServer::send(const string &s) const
{
// send length
size_t len = s.size();
char buf[9];
sprintf(buf, "%8u", (unsigned int) len);
int status = ::send(m_sock, buf, 8, MSG_NOSIGNAL);
string str = "0000000" + std::to_string(s.size());
str.erase(0, str.size()-8);
int status = ::send(m_sock, str.data(), 8, MSG_NOSIGNAL);
if (status == -1)
return false;

Expand All @@ -716,10 +704,9 @@ bool ZMServer::send(const string &s) const
bool ZMServer::send(const string &s, const unsigned char *buffer, int dataLen) const
{
// send length
size_t len = s.size();
char buf[9];
sprintf(buf, "%8u", (unsigned int) len);
int status = ::send(m_sock, buf, 8, MSG_NOSIGNAL);
string str = "0000000" + std::to_string(s.size());
str.erase(0, str.size()-8);
int status = ::send(m_sock, str.data(), 8, MSG_NOSIGNAL);
if (status == -1)
return false;

Expand Down Expand Up @@ -796,18 +783,18 @@ void ZMServer::handleGetServerStatus(void)
}
else
{
char buf[30];
sprintf(buf, "%0.2lf", loads[0]);
// to_string gives six decimal places. Drop last four.
string buf = std::to_string(loads[0]);
buf.resize(buf.size() - 4);
ADD_STR(outStr, buf)
}

// get free space on the disk where the events are stored
char buf[15];
long long total = 0;
long long used = 0;
string eventsDir = g_webPath + "/events/";
getDiskSpace(eventsDir, total, used);
sprintf(buf, "%d%%", static_cast<int>((used * 100) / total));
string buf = std::to_string(static_cast<int>((used * 100) / total)) + "%";
ADD_STR(outStr, buf)

send(outStr);
Expand Down Expand Up @@ -1085,11 +1072,11 @@ string ZMServer::runCommand(const string& command)
{
string outStr;
FILE *fd = popen(command.c_str(), "r");
char buffer[100];
std::array<char,100> buffer {};

while (fgets(buffer, sizeof(buffer), fd) != nullptr)
while (fgets(buffer.data(), buffer.size(), fd) != nullptr)
{
outStr += buffer;
outStr += buffer.data();
}
pclose(fd);
return outStr;
Expand Down Expand Up @@ -1148,7 +1135,7 @@ void ZMServer::getMonitorStatus(const string &id, const string &type,

void ZMServer::handleGetEventFrame(vector<string> tokens)
{
static unsigned char s_buffer[MAX_IMAGE_SIZE];
static FrameData s_buffer {};

if (tokens.size() != 5)
{
Expand All @@ -1171,33 +1158,33 @@ void ZMServer::handleGetEventFrame(vector<string> tokens)

// try to find the frame file
string filepath;
char str[100];
string str (100,'\0');

if (checkVersion(1, 32, 0))
{
int year = 0;
int month = 0;
int day = 0;

sscanf(eventTime.c_str(), "%2d/%2d/%2d", &year, &month, &day);
sprintf(str, "20%02d-%02d-%02d", year, month, day);
sscanf(eventTime.data(), "%2d/%2d/%2d", &year, &month, &day);
sprintf(str.data(), "20%02d-%02d-%02d", year, month, day);

filepath = g_eventsPath + "/" + monitorID + "/" + str + "/" + eventID + "/";
sprintf(str, m_eventFileFormat.c_str(), frameNo);
sprintf(str.data(), m_eventFileFormat.c_str(), frameNo);
filepath += str;
}
else
{
if (m_useDeepStorage)
{
filepath = g_webPath + "/events/" + monitorID + "/" + eventTime + "/";
sprintf(str, m_eventFileFormat.c_str(), frameNo);
sprintf(str.data(), m_eventFileFormat.c_str(), frameNo);
filepath += str;
}
else
{
filepath = g_webPath + "/events/" + monitorID + "/" + eventID + "/";
sprintf(str, m_eventFileFormat.c_str(), frameNo);
sprintf(str.data(), m_eventFileFormat.c_str(), frameNo);
filepath += str;
}
}
Expand All @@ -1206,7 +1193,7 @@ void ZMServer::handleGetEventFrame(vector<string> tokens)
FILE *fd = fopen(filepath.c_str(), "r" );
if (fd != nullptr)
{
fileSize = fread(s_buffer, 1, sizeof(s_buffer), fd);
fileSize = fread(s_buffer.data(), 1, s_buffer.size(), fd);
fclose(fd);
}
else
Expand All @@ -1223,13 +1210,13 @@ void ZMServer::handleGetEventFrame(vector<string> tokens)
ADD_INT(outStr, fileSize)

// send the data
send(outStr, s_buffer, fileSize);
send(outStr, s_buffer.data(), fileSize);
}

void ZMServer::handleGetAnalysisFrame(vector<string> tokens)
{
static unsigned char s_buffer[MAX_IMAGE_SIZE];
char str[100];
static FrameData s_buffer {};
std::array<char,100> str {};

if (tokens.size() != 5)
{
Expand Down Expand Up @@ -1328,8 +1315,8 @@ void ZMServer::handleGetAnalysisFrame(vector<string> tokens)
int day = 0;

sscanf(eventTime.c_str(), "%2d/%2d/%2d", &year, &month, &day);
sprintf(str, "20%02d-%02d-%02d", year, month, day);
filepath = g_eventsPath + "/" + monitorID + "/" + str + "/" + eventID + "/";
sprintf(str.data(), "20%02d-%02d-%02d", year, month, day);
filepath = g_eventsPath + "/" + monitorID + "/" + str.data() + "/" + eventID + "/";
}
else
{
Expand All @@ -1347,12 +1334,12 @@ void ZMServer::handleGetAnalysisFrame(vector<string> tokens)
// try to find an analysis frame for the frameID
if (m_useAnalysisImages)
{
sprintf(str, m_analysisFileFormat.c_str(), frameID);
frameFile = filepath + str;
sprintf(str.data(), m_analysisFileFormat.c_str(), frameID);
frameFile = filepath + str.data();

if ((fd = fopen(frameFile.c_str(), "r" )))
{
fileSize = fread(s_buffer, 1, sizeof(s_buffer), fd);
fileSize = fread(s_buffer.data(), 1, s_buffer.size(), fd);
fclose(fd);

if (m_debug)
Expand All @@ -1362,18 +1349,18 @@ void ZMServer::handleGetAnalysisFrame(vector<string> tokens)
ADD_INT(outStr, fileSize)

// send the data
send(outStr, s_buffer, fileSize);
send(outStr, s_buffer.data(), fileSize);
return;
}
}

// try to find a normal frame for the frameID these should always be available
sprintf(str, m_eventFileFormat.c_str(), frameID);
frameFile = filepath + str;
sprintf(str.data(), m_eventFileFormat.c_str(), frameID);
frameFile = filepath + str.data();

if ((fd = fopen(frameFile.c_str(), "r" )))
{
fileSize = fread(s_buffer, 1, sizeof(s_buffer), fd);
fileSize = fread(s_buffer.data(), 1, s_buffer.size(), fd);
fclose(fd);
}
else
Expand All @@ -1390,12 +1377,12 @@ void ZMServer::handleGetAnalysisFrame(vector<string> tokens)
ADD_INT(outStr, fileSize)

// send the data
send(outStr, s_buffer, fileSize);
send(outStr, s_buffer.data(), fileSize);
}

void ZMServer::handleGetLiveFrame(vector<string> tokens)
{
static unsigned char s_buffer[MAX_IMAGE_SIZE];
static FrameData s_buffer {};

// we need to periodically kick the DB connection here to make sure it
// stays alive because the user may have left the frontend on the live
Expand Down Expand Up @@ -1437,7 +1424,7 @@ void ZMServer::handleGetLiveFrame(vector<string> tokens)
}

// read a frame from the shared memory
int dataSize = getFrame(s_buffer, sizeof(s_buffer), monitor);
int dataSize = getFrame(s_buffer, monitor);

if (m_debug)
cout << "Frame size: " << dataSize << endl;
Expand All @@ -1458,7 +1445,7 @@ void ZMServer::handleGetLiveFrame(vector<string> tokens)
ADD_INT(outStr, dataSize)

// send the data
send(outStr, s_buffer, dataSize);
send(outStr, s_buffer.data(), dataSize);
}

void ZMServer::handleGetFrameList(vector<string> tokens)
Expand Down Expand Up @@ -1521,11 +1508,8 @@ void ZMServer::handleGetFrameList(vector<string> tokens)

for (int x = 0; x < frameCount; x++)
{
char str[10];
sprintf(str, "%f", delta);

ADD_STR(outStr, "Normal") // Type
ADD_STR(outStr, str) // Delta
ADD_STR(outStr, std::to_string(delta)) // Delta
}
}
}
Expand Down Expand Up @@ -1785,10 +1769,8 @@ void ZMServer::getMonitorList(void)
mysql_free_result(res);
}

int ZMServer::getFrame(unsigned char *buffer, int bufferSize, MONITOR *monitor)
int ZMServer::getFrame(FrameData &buffer, MONITOR *monitor)
{
(void) bufferSize;

// is there a new frame available?
if (monitor->getLastWriteIndex() == monitor->m_lastRead )
return 0;
Expand Down
14 changes: 12 additions & 2 deletions mythplugins/mythzoneminder/mythzmserver/zmserver.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@

using namespace std;

// the maximum image size we are ever likely to get from ZM
#define MAX_IMAGE_SIZE (2048*1536*3)
using FrameData = std::array<uint8_t,MAX_IMAGE_SIZE>;

extern bool checkVersion(int major, int minor, int revision);
extern void loadZMConfig(const string &configfile);
extern void connectToDatabase(void);
Expand Down Expand Up @@ -68,6 +72,10 @@ enum State
TAPE
};

// Prevent clang-tidy modernize-avoid-c-arrays warnings in these
// library structures
extern "C" {

// shared data for ZM version 1.24.x and 1.25.x
struct SharedData
{
Expand Down Expand Up @@ -244,6 +252,9 @@ struct VideoStoreData
timeval recording;
};

// end of library structures.
};

class MONITOR
{
public:
Expand Down Expand Up @@ -300,7 +311,7 @@ class ZMServer
bool send(const string &s, const unsigned char *buffer, int dataLen) const;
void sendError(const string &error);
void getMonitorList(void);
static int getFrame(unsigned char *buffer, int bufferSize, MONITOR *monitor);
static int getFrame(FrameData &buffer, MONITOR *monitor);
static long long getDiskSpace(const string &filename, long long &total, long long &used);
static void tokenize(const string &command, vector<string> &tokens);
void handleHello(void);
Expand Down Expand Up @@ -338,7 +349,6 @@ class ZMServer
string m_analysisFileFormat;
key_t m_shmKey;
string m_mmapPath;
char m_buf[10] {0};
};


Expand Down
8 changes: 4 additions & 4 deletions mythplugins/mythzoneminder/mythzoneminder/zmclient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -736,7 +736,7 @@ void ZMClient::getAnalyseFrame(Event *event, int frameNo, QImage &image)
delete [] data;
}

int ZMClient::getLiveFrame(int monitorID, QString &status, unsigned char* buffer, int bufferSize)
int ZMClient::getLiveFrame(int monitorID, QString &status, FrameData& buffer)
{
QMutexLocker locker(&m_commandLock);

Expand Down Expand Up @@ -771,9 +771,9 @@ int ZMClient::getLiveFrame(int monitorID, QString &status, unsigned char* buffer
status = strList[2];

// get frame length from data
int imageSize = strList[3].toInt();
size_t imageSize = strList[3].toInt();

if (bufferSize < imageSize)
if (buffer.size() < imageSize)
{
LOG(VB_GENERAL, LOG_ERR,
"ZMClient::getLiveFrame(): Live frame buffer is too small!");
Expand All @@ -784,7 +784,7 @@ int ZMClient::getLiveFrame(int monitorID, QString &status, unsigned char* buffer
if (imageSize == 0)
return 0;

if (!readData(buffer, imageSize))
if (!readData(buffer.data(), imageSize))
{
LOG(VB_GENERAL, LOG_ERR,
"ZMClient::getLiveFrame(): Failed to get image data");
Expand Down
4 changes: 3 additions & 1 deletion mythplugins/mythzoneminder/mythzoneminder/zmclient.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ using namespace std;

// zm
#include "zmdefines.h"
#define MAX_IMAGE_SIZE (2048*1536*3)
using FrameData = std::array<uint8_t,MAX_IMAGE_SIZE>;

class MPUBLIC ZMClient : public QObject
{
Expand Down Expand Up @@ -46,7 +48,7 @@ class MPUBLIC ZMClient : public QObject
const QString &date, bool includeContinuous, vector<Event*> *eventList);
void getEventFrame(Event *event, int frameNo, MythImage **image);
void getAnalyseFrame(Event *event, int frameNo, QImage &image);
int getLiveFrame(int monitorID, QString &status, unsigned char* buffer, int bufferSize);
int getLiveFrame(int monitorID, QString &status, FrameData& buffer);
void getFrameList(int eventID, vector<Frame*> *frameList);
void deleteEvent(int eventID);
void deleteEventList(vector<Event*> *eventList);
Expand Down
6 changes: 3 additions & 3 deletions mythplugins/mythzoneminder/mythzoneminder/zmliveplayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ void ZMLivePlayer::changePlayerMonitor(int playerNo)

void ZMLivePlayer::updateFrame()
{
static unsigned char s_buffer[MAX_IMAGE_SIZE];
static std::array<uint8_t,MAX_IMAGE_SIZE> s_buffer {};
m_frameTimer->stop();

// get a list of monitor id's that need updating
Expand All @@ -358,7 +358,7 @@ void ZMLivePlayer::updateFrame()
for (int x = 0; x < monList.count(); x++)
{
QString status;
int frameSize = ZMClient::get()->getLiveFrame(monList[x], status, s_buffer, sizeof(s_buffer));
int frameSize = ZMClient::get()->getLiveFrame(monList[x], status, s_buffer);

if (frameSize > 0 && !status.startsWith("ERROR"))
{
Expand All @@ -372,7 +372,7 @@ void ZMLivePlayer::updateFrame()
p->getMonitor()->status = status;
p->updateStatus();
}
p->updateFrame(s_buffer);
p->updateFrame(s_buffer.data());
}
}
}
Expand Down