MythTV  0.28pre
zmserver.cpp
Go to the documentation of this file.
1 /* Implementation of the ZMServer class.
2  * ============================================================
3  * This program is free software; you can redistribute it
4  * and/or modify it under the terms of the GNU General
5  * Public License as published bythe Free Software Foundation;
6  * either version 2, or (at your option)
7  * any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * ============================================================ */
15 
16 
17 #include <iostream>
18 #include <cstdlib>
19 #include <cstring>
20 #include <cstdio>
21 #include <errno.h>
22 #include <sys/socket.h>
23 #include <fcntl.h>
24 #include <netinet/in.h>
25 #include <sys/stat.h>
26 #include <sys/shm.h>
27 #include <sys/mman.h>
28 
29 #ifdef linux
30 # include <sys/vfs.h>
31 # include <sys/statvfs.h>
32 # include <sys/sysinfo.h>
33 #else
34 # include <sys/param.h>
35 # include <sys/mount.h>
36 # if CONFIG_CYGWIN
37 # include <sys/statfs.h>
38 # else // if !CONFIG_CYGWIN
39 # include <sys/sysctl.h>
40 # endif // !CONFIG_CYGWIN
41 #endif
42 
43 #include "mythtv/mythconfig.h"
44 
45 #if CONFIG_DARWIN
46 #define MSG_NOSIGNAL 0 // Apple also has SO_NOSIGPIPE?
47 #endif
48 
49 #include "zmserver.h"
50 
51 // the version of the protocol we understand
52 #define ZM_PROTOCOL_VERSION "11"
53 
54 // the maximum image size we are ever likely to get from ZM
55 #define MAX_IMAGE_SIZE (2048*1536*3)
56 
57 #define ADD_STR(list,s) list += s; list += "[]:[]";
58 // TODO rewrite after we require C++11, see http://en.cppreference.com/w/cpp/string/basic_string/to_string
59 #define ADD_INT(list,n) sprintf(m_buf, "%d", (int)n); list += m_buf; list += "[]:[]";
60 
61 // error messages
62 #define ERROR_TOKEN_COUNT "Invalid token count"
63 #define ERROR_MYSQL_QUERY "Mysql Query Error"
64 #define ERROR_MYSQL_ROW "Mysql Get Row Error"
65 #define ERROR_FILE_OPEN "Cannot open event file"
66 #define ERROR_INVALID_MONITOR "Invalid Monitor"
67 #define ERROR_INVALID_POINTERS "Cannot get shared memory pointers"
68 #define ERROR_INVALID_MONITOR_FUNCTION "Invalid Monitor Function"
69 #define ERROR_INVALID_MONITOR_ENABLE_VALUE "Invalid Monitor Enable Value"
70 #define ERROR_NO_FRAMES "No frames found for event"
71 
72 // Subpixel ordering (from zm_rgb.h)
73 // Based on byte order naming. For example, for ARGB (on both little endian or big endian)
74 // byte+0 should be alpha, byte+1 should be red, and so on.
75 #define ZM_SUBPIX_ORDER_NONE 2
76 #define ZM_SUBPIX_ORDER_RGB 6
77 #define ZM_SUBPIX_ORDER_BGR 5
78 #define ZM_SUBPIX_ORDER_BGRA 7
79 #define ZM_SUBPIX_ORDER_RGBA 8
80 #define ZM_SUBPIX_ORDER_ABGR 9
81 #define ZM_SUBPIX_ORDER_ARGB 10
82 
83 MYSQL g_dbConn;
84 string g_zmversion = "";
85 string g_password = "";
86 string g_server = "";
87 string g_database = "";
88 string g_webPath = "";
89 string g_user = "";
90 string g_webUser = "";
91 string g_binPath = "";
95 
96 time_t g_lastDBKick = 0;
97 
98 // returns true if the ZM version >= the requested version
99 bool checkVersion(int major, int minor, int revision)
100 {
101  if (g_majorVersion >= major && g_minorVersion >= minor && g_revisionVersion >= revision)
102  return true;
103 
104  return false;
105 }
106 
107 void loadZMConfig(const string &configfile)
108 {
109  cout << "loading zm config from " << configfile << endl;
110  FILE *cfg;
111  char line[512];
112  char val[250];
113 
114  if ( (cfg = fopen(configfile.c_str(), "r")) == NULL )
115  {
116  fprintf(stderr,"Can't open %s\n", configfile.c_str());
117  exit(1);
118  }
119 
120  while ( fgets( line, sizeof(line), cfg ) != NULL )
121  {
122  char *line_ptr = line;
123  // Trim off any cr/lf line endings
124  size_t chomp_len = strcspn( line_ptr, "\r\n" );
125  line_ptr[chomp_len] = '\0';
126 
127  // Remove leading white space
128  size_t white_len = strspn( line_ptr, " \t" );
129  line_ptr += white_len;
130 
131  // Check for comment or empty line
132  if ( *line_ptr == '\0' || *line_ptr == '#' )
133  continue;
134 
135  // Remove trailing white space
136  char *temp_ptr = line_ptr+strlen(line_ptr)-1;
137  while ( *temp_ptr == ' ' || *temp_ptr == '\t' )
138  {
139  *temp_ptr-- = '\0';
140  temp_ptr--;
141  }
142 
143  // Now look for the '=' in the middle of the line
144  temp_ptr = strchr( line_ptr, '=' );
145  if ( !temp_ptr )
146  {
147  fprintf(stderr,"Invalid data in %s: '%s'\n", configfile.c_str(), line );
148  continue;
149  }
150 
151  // Assign the name and value parts
152  char *name_ptr = line_ptr;
153  char *val_ptr = temp_ptr+1;
154 
155  // Trim trailing space from the name part
156  do
157  {
158  *temp_ptr = '\0';
159  temp_ptr--;
160  }
161  while ( *temp_ptr == ' ' || *temp_ptr == '\t' );
162 
163  // Remove leading white space from the value part
164  white_len = strspn( val_ptr, " \t" );
165  val_ptr += white_len;
166 
167  strncpy( val, val_ptr, strlen(val_ptr)+1 );
168  if ( strcasecmp( name_ptr, "ZM_DB_HOST" ) == 0 ) g_server = val;
169  else if ( strcasecmp( name_ptr, "ZM_DB_NAME" ) == 0 ) g_database = val;
170  else if ( strcasecmp( name_ptr, "ZM_DB_USER" ) == 0 ) g_user = val;
171  else if ( strcasecmp( name_ptr, "ZM_DB_PASS" ) == 0 ) g_password = val;
172  else if ( strcasecmp( name_ptr, "ZM_PATH_WEB" ) == 0 ) g_webPath = val;
173  else if ( strcasecmp( name_ptr, "ZM_PATH_BIN" ) == 0 ) g_binPath = val;
174  else if ( strcasecmp( name_ptr, "ZM_WEB_USER" ) == 0 ) g_webUser = val;
175  else if ( strcasecmp( name_ptr, "ZM_VERSION" ) == 0 ) g_zmversion = val;
176  }
177  fclose(cfg);
178 }
179 
181 {
182  if (!mysql_init(&g_dbConn))
183  {
184  cout << "Error: Can't initialise structure: " << mysql_error(&g_dbConn) << endl;
185  exit(mysql_errno(&g_dbConn));
186  }
187 
188  g_dbConn.reconnect = 1;
189 
190  if (!mysql_real_connect(&g_dbConn, g_server.c_str(), g_user.c_str(),
191  g_password.c_str(), 0, 0, 0, 0))
192  {
193  cout << "Error: Can't connect to server: " << mysql_error(&g_dbConn) << endl;
194  exit(mysql_errno( &g_dbConn));
195  }
196 
197  if (mysql_select_db(&g_dbConn, g_database.c_str()))
198  {
199  cout << "Error: Can't select database: " << mysql_error(&g_dbConn) << endl;
200  exit(mysql_errno(&g_dbConn));
201  }
202 }
203 
204 void kickDatabase(bool debug)
205 {
206  if (time(NULL) < g_lastDBKick + DB_CHECK_TIME)
207  return;
208 
209  if (debug)
210  cout << "Kicking database connection" << endl;
211 
212  g_lastDBKick = time(NULL);
213 
214  if (mysql_query(&g_dbConn, "SELECT NULL;") == 0)
215  {
216  MYSQL_RES *res = mysql_store_result(&g_dbConn);
217  if (res)
218  mysql_free_result(res);
219  return;
220  }
221 
222  cout << "Lost connection to DB - trying to reconnect" << endl;
223 
224  // failed so try to reconnect to the DB
225  mysql_close(&g_dbConn);
227 }
228 
230 
232  name(""), type(""), function(""), enabled(0), device(""), host(""),
233  image_buffer_count(0), width(0), height(0), bytes_per_pixel(3), mon_id(0),
234  shared_images(NULL), last_read(0), status(""), palette(0),
235  controllable(0), trackMotion(0), mapFile(-1), shm_ptr(NULL),
236  shared_data(NULL), shared_data26(NULL), id("")
237 {
238 }
239 
240 void MONITOR::initMonitor(bool debug, string mmapPath, int shmKey)
241 {
242  int shared_data_size;
243  int frame_size = width * height * bytes_per_pixel;
244 
245  if (checkVersion(1, 26, 0))
246  {
247  shared_data_size = sizeof(SharedData26) +
248  sizeof(TriggerData26) +
249  ((image_buffer_count) * (sizeof(struct timeval))) +
250  ((image_buffer_count) * frame_size) + 64;
251  }
252  else
253  {
254  shared_data_size = sizeof(SharedData) +
255  sizeof(TriggerData) +
256  ((image_buffer_count) * (sizeof(struct timeval))) +
257  ((image_buffer_count) * frame_size);
258  }
259 
260 #if _POSIX_MAPPED_FILES > 0L
261  /*
262  * Try to open the mmap file first if the architecture supports it.
263  * Otherwise, legacy shared memory will be used below.
264  */
265  stringstream mmap_filename;
266  mmap_filename << mmapPath << "/zm.mmap." << mon_id;
267 
268  mapFile = open(mmap_filename.str().c_str(), O_RDONLY, 0x0);
269  if (mapFile >= 0)
270  {
271  if (debug)
272  cout << "Opened mmap file: " << mmap_filename.str() << endl;
273 
274  shm_ptr = mmap(NULL, shared_data_size, PROT_READ,
275  MAP_SHARED, mapFile, 0x0);
276  if (shm_ptr == MAP_FAILED)
277  {
278  cout << "Failed to map shared memory from file ["
279  << mmap_filename.str() << "] " << "for monitor: "
280  << mon_id << endl;
281  status = "Error";
282 
283  if (close(mapFile) == -1)
284  cout << "Failed to close mmap file" << endl;
285 
286  mapFile = -1;
287  shm_ptr = NULL;
288 
289  return;
290  }
291  }
292  else
293  {
294  // this is not necessarily a problem, maybe the user is still
295  // using the legacy shared memory support
296  if (debug)
297  {
298  cout << "Failed to open mmap file [" << mmap_filename.str() << "] "
299  << "for monitor: " << mon_id
300  << " : " << strerror(errno) << endl;
301  cout << "Falling back to the legacy shared memory method" << endl;
302  }
303  }
304 #endif
305 
306  if (shm_ptr == NULL)
307  {
308  // fail back to shmget() functionality if mapping memory above failed.
309  int shmid;
310 
311  if ((shmid = shmget((shmKey & 0xffffff00) | mon_id,
312  shared_data_size, SHM_R)) == -1)
313  {
314  cout << "Failed to shmget for monitor: " << mon_id << endl;
315  status = "Error";
316  switch(errno)
317  {
318  case EACCES: cout << "EACCES - no rights to access segment\n"; break;
319  case EEXIST: cout << "EEXIST - segment already exists\n"; break;
320  case EINVAL: cout << "EINVAL - size < SHMMIN or size > SHMMAX\n"; break;
321  case ENFILE: cout << "ENFILE - limit on open files has been reached\n"; break;
322  case ENOENT: cout << "ENOENT - no segment exists for the given key\n"; break;
323  case ENOMEM: cout << "ENOMEM - couldn't reserve memory for segment\n"; break;
324  case ENOSPC: cout << "ENOSPC - shmmni or shmall limit reached\n"; break;
325  }
326 
327  return;
328  }
329 
330  shm_ptr = shmat(shmid, 0, SHM_RDONLY);
331 
332 
333  if (shm_ptr == NULL)
334  {
335  cout << "Failed to shmat for monitor: " << mon_id << endl;
336  status = "Error";
337  return;
338  }
339  }
340 
341  if (checkVersion(1, 26, 0))
342  {
343  shared_data = NULL;
345 
346  shared_images = (unsigned char*) shm_ptr +
347  sizeof(SharedData26) + sizeof(TriggerData26) +
348  ((image_buffer_count) * sizeof(struct timeval));
349 
350  if (((unsigned long)shared_images % 16) != 0)
351  {
352  // align images buffer to nearest 16 byte boundary
353  shared_images = (unsigned char*)((unsigned long)shared_images + (16 - ((unsigned long)shared_images % 16)));
354  }
355  }
356  else
357  {
358  shared_data26 = NULL;
360 
361  shared_images = (unsigned char*) shm_ptr +
362  sizeof(SharedData) + sizeof(TriggerData) +
363  ((image_buffer_count) * sizeof(struct timeval));
364  }
365 }
366 
368 {
369  if (checkVersion(1, 26, 0))
370  return shared_data26 != NULL && shared_images != NULL;
371 
372  // must be version >= 1.24.0 and < 1.26.0
373  return shared_data != NULL && shared_images != NULL;
374 }
375 
376 
377 string MONITOR::getIdStr(void)
378 {
379  if (id == "")
380  {
381  std::stringstream out;
382  out << mon_id;
383  id = out.str();
384  }
385  return id;
386 }
387 
389 {
390  if (shared_data)
392 
394 }
395 
397 {
398  if (shared_data)
399  return shared_data->state;
400 
401  return shared_data26->state;
402 }
403 
405 {
406  if (shared_data)
407  {
408  if (bytes_per_pixel == 1)
409  return ZM_SUBPIX_ORDER_NONE;
410  else
411  return ZM_SUBPIX_ORDER_RGB;
412  }
413 
414  return shared_data26->format;
415 }
416 
418 {
419  if (shared_data)
420  return width * height * bytes_per_pixel;
421 
422  return shared_data26->imagesize;
423 }
424 
426 
427 ZMServer::ZMServer(int sock, bool debug)
428 {
429  if (debug)
430  cout << "Using server protocol version '" << ZM_PROTOCOL_VERSION << "'\n";
431 
432  m_sock = sock;
433  m_debug = debug;
434 
435  // get the shared memory key
436  char buf[100];
437  m_shmKey = 0x7a6d2000;
438  string setting = getZMSetting("ZM_SHM_KEY");
439 
440  if (setting != "")
441  {
442  unsigned long long tmp = m_shmKey;
443  sscanf(setting.c_str(), "%20llx", &tmp);
444  m_shmKey = tmp;
445  }
446 
447  if (m_debug)
448  {
449  snprintf(buf, sizeof(buf), "0x%x", (unsigned int)m_shmKey);
450  cout << "Shared memory key is: " << buf << endl;
451  }
452 
453  // get the MMAP path
454  m_mmapPath = getZMSetting("ZM_PATH_MAP");
455  if (m_debug)
456  {
457  cout << "Memory path directory is: " << m_mmapPath << endl;
458  }
459 
460  // get the event filename format
461  setting = getZMSetting("ZM_EVENT_IMAGE_DIGITS");
462  int eventDigits = atoi(setting.c_str());
463  snprintf(buf, sizeof(buf), "%%0%dd-capture.jpg", eventDigits);
465  if (m_debug)
466  cout << "Event file format is: " << m_eventFileFormat << endl;
467 
468  // get the analysis filename format
469  snprintf(buf, sizeof(buf), "%%0%dd-analyse.jpg", eventDigits);
471  if (m_debug)
472  cout << "Analysis file format is: " << m_analysisFileFormat << endl;
473 
474  // is ZM using the deep storage directory format?
475  m_useDeepStorage = (getZMSetting("ZM_USE_DEEP_STORAGE") == "1");
476  if (m_debug)
477  {
478  if (m_useDeepStorage)
479  cout << "using deep storage directory structure" << endl;
480  else
481  cout << "using flat directory structure" << endl;
482  }
483 
484  // is ZM creating analysis images?
485  m_useAnalysisImages = (getZMSetting("ZM_CREATE_ANALYSIS_IMAGES") == "1");
486  if (m_debug)
487  {
489  cout << "using analysis images" << endl;
490  else
491  cout << "not using analysis images" << endl;
492  }
493 
494  getMonitorList();
495 
496  // zero buffer for conversion of integer to string in ADD_INT
497  memset (m_buf, '\0', sizeof(m_buf));
498 }
499 
501 {
502  for (uint x = 0; x < m_monitors.size(); x++)
503  {
504  MONITOR *mon = m_monitors.at(x);
505  if (mon->mapFile != -1)
506  {
507  if (close(mon->mapFile) == -1)
508  cout << "Failed to close mapFile" << endl;
509  else
510  if (m_debug)
511  cout << "Closed mapFile for monitor: " << mon->name << endl;
512  }
513 
514  delete mon;
515  }
516 
517  m_monitors.clear();
518  m_monitorMap.clear();
519 
520  if (m_debug)
521  cout << "ZMServer destroyed\n";
522 }
523 
524 void ZMServer::tokenize(const string &command, vector<string> &tokens)
525 {
526  string token = "";
527  tokens.clear();
528  string::size_type startPos = 0;
529  string::size_type endPos = 0;
530 
531  while((endPos = command.find("[]:[]", startPos)) != string::npos)
532  {
533  token = command.substr(startPos, endPos - startPos);
534  tokens.push_back(token);
535  startPos = endPos + 5;
536  }
537 
538  // make sure we add the last token
539  if (endPos != command.length())
540  {
541  token = command.substr(startPos);
542  tokens.push_back(token);
543  }
544 }
545 
546 // returns true if we get a QUIT command from the client
547 bool ZMServer::processRequest(char* buf, int nbytes)
548 {
549 #if 0
550  // first 8 bytes is the length of the following data
551  char len[9];
552  memcpy(len, buf, 8);
553  len[8] = '\0';
554  int dataLen = atoi(len);
555 #endif
556 
557  buf[nbytes] = '\0';
558  string s(buf+8);
559  vector<string> tokens;
560  tokenize(s, tokens);
561 
562  if (tokens.empty())
563  return false;
564 
565  if (m_debug)
566  cout << "Processing: '" << tokens[0] << "'" << endl;
567 
568  if (tokens[0] == "HELLO")
569  handleHello();
570  else if (tokens[0] == "QUIT")
571  return true;
572  else if (tokens[0] == "GET_SERVER_STATUS")
574  else if (tokens[0] == "GET_MONITOR_STATUS")
576  else if (tokens[0] == "GET_ALARM_STATES")
578  else if (tokens[0] == "GET_EVENT_LIST")
579  handleGetEventList(tokens);
580  else if (tokens[0] == "GET_EVENT_DATES")
581  handleGetEventDates(tokens);
582  else if (tokens[0] == "GET_EVENT_FRAME")
583  handleGetEventFrame(tokens);
584  else if (tokens[0] == "GET_ANALYSE_FRAME")
585  handleGetAnalysisFrame(tokens);
586  else if (tokens[0] == "GET_LIVE_FRAME")
587  handleGetLiveFrame(tokens);
588  else if (tokens[0] == "GET_FRAME_LIST")
589  handleGetFrameList(tokens);
590  else if (tokens[0] == "GET_CAMERA_LIST")
592  else if (tokens[0] == "GET_MONITOR_LIST")
594  else if (tokens[0] == "DELETE_EVENT")
595  handleDeleteEvent(tokens);
596  else if (tokens[0] == "DELETE_EVENT_LIST")
597  handleDeleteEventList(tokens);
598  else if (tokens[0] == "RUN_ZMAUDIT")
600  else if (tokens[0] == "SET_MONITOR_FUNCTION")
601  handleSetMonitorFunction(tokens);
602  else
603  send("UNKNOWN_COMMAND");
604 
605  return false;
606 }
607 
608 bool ZMServer::send(const string &s) const
609 {
610  // send length
611  size_t len = s.size();
612  char buf[9];
613  sprintf(buf, "%8u", (unsigned int) len);
614  int status = ::send(m_sock, buf, 8, MSG_NOSIGNAL);
615  if (status == -1)
616  return false;
617 
618  // send message
619  status = ::send(m_sock, s.c_str(), s.size(), MSG_NOSIGNAL);
620  if ( status == -1 )
621  return false;
622  else
623  return true;
624 }
625 
626 bool ZMServer::send(const string &s, const unsigned char *buffer, int dataLen) const
627 {
628  // send length
629  size_t len = s.size();
630  char buf[9];
631  sprintf(buf, "%8u", (unsigned int) len);
632  int status = ::send(m_sock, buf, 8, MSG_NOSIGNAL);
633  if (status == -1)
634  return false;
635 
636  // send message
637  status = ::send(m_sock, s.c_str(), s.size(), MSG_NOSIGNAL);
638  if ( status == -1 )
639  return false;
640 
641  // send data
642  status = ::send(m_sock, buffer, dataLen, MSG_NOSIGNAL);
643  if ( status == -1 )
644  return false;
645 
646  return true;
647 }
648 
650 {
651  string outStr("");
652  ADD_STR(outStr, string("ERROR - ") + error);
653  send(outStr);
654 }
655 
657 {
658  // just send OK so the client knows all is well
659  // followed by the protocol version we understand
660  string outStr("");
661  ADD_STR(outStr, "OK");
662  ADD_STR(outStr, ZM_PROTOCOL_VERSION);
663  send(outStr);
664 }
665 
666 long long ZMServer::getDiskSpace(const string &filename, long long &total, long long &used)
667 {
668  struct statfs statbuf;
669  memset(&statbuf, 0, sizeof(statbuf));
670  long long freespace = -1;
671 
672  total = used = -1;
673 
674  // there are cases where statfs will return 0 (good), but f_blocks and
675  // others are invalid and set to 0 (such as when an automounted directory
676  // is not mounted but still visible because --ghost was used),
677  // so check to make sure we can have a total size > 0
678  if ((statfs(filename.c_str(), &statbuf) == 0) &&
679  (statbuf.f_blocks > 0) &&
680  (statbuf.f_bsize > 0))
681  {
682  total = statbuf.f_blocks;
683  total *= statbuf.f_bsize;
684  total = total >> 10;
685 
686  freespace = statbuf.f_bavail;
687  freespace *= statbuf.f_bsize;
688  freespace = freespace >> 10;
689 
690  used = total - freespace;
691  }
692 
693  return freespace;
694 }
695 
697 {
698  string outStr("");
699  ADD_STR(outStr, "OK")
700 
701  // server status
702  string status = runCommand(g_binPath + "/zmdc.pl check");
703  ADD_STR(outStr, status)
704 
705  // get load averages
706  double loads[3];
707  if (getloadavg(loads, 3) == -1)
708  {
709  ADD_STR(outStr, "Unknown")
710  }
711  else
712  {
713  char buf[30];
714  sprintf(buf, "%0.2lf", loads[0]);
715  ADD_STR(outStr, buf)
716  }
717 
718  // get free space on the disk where the events are stored
719  char buf[15];
720  long long total, used;
721  string eventsDir = g_webPath + "/events/";
722  getDiskSpace(eventsDir, total, used);
723  sprintf(buf, "%d%%", (int) ((100.0 / ((float) total / used))));
724  ADD_STR(outStr, buf)
725 
726  send(outStr);
727 }
728 
730 {
731  string outStr("");
732  ADD_STR(outStr, "OK")
733 
734  // add the monitor count
735  ADD_INT(outStr, m_monitors.size())
736 
737  for (int x = 0; x < (int)m_monitors.size(); x++)
738  {
739  MONITOR *monitor = m_monitors.at(x);
740 
741  // add monitor ID
742  ADD_INT(outStr, monitor->mon_id)
743 
744  // add monitor status
745  ADD_INT(outStr, monitor->getState())
746  }
747 
748  send(outStr);
749 }
750 
751 void ZMServer::handleGetEventList(vector<string> tokens)
752 {
753  string outStr("");
754 
755  if (tokens.size() != 5)
756  {
757  sendError(ERROR_TOKEN_COUNT);
758  return;
759  }
760 
761  string monitor = tokens[1];
762  bool oldestFirst = (tokens[2] == "1");
763  string date = tokens[3];
764  bool includeContinuous = (tokens[4] == "1");
765 
766  if (m_debug)
767  cout << "Loading events for monitor: " << monitor << ", date: " << date << endl;
768 
769  ADD_STR(outStr, "OK")
770 
771  MYSQL_RES *res;
772  MYSQL_ROW row;
773 
774  string sql("SELECT E.Id, E.Name, M.Id AS MonitorID, M.Name AS MonitorName, E.StartTime, "
775  "E.Length, M.Width, M.Height, M.DefaultRate, M.DefaultScale "
776  "from Events as E inner join Monitors as M on E.MonitorId = M.Id ");
777 
778  if (monitor != "<ANY>")
779  {
780  sql += "WHERE M.Name = '" + monitor + "' ";
781 
782  if (date != "<ANY>")
783  sql += "AND DATE(E.StartTime) = DATE('" + date + "') ";
784  }
785  else
786  {
787  if (date != "<ANY>")
788  {
789  sql += "WHERE DATE(E.StartTime) = DATE('" + date + "') ";
790 
791  if (!includeContinuous)
792  sql += "AND Cause != 'Continuous' ";
793  }
794  else
795  if (!includeContinuous)
796  sql += "WHERE Cause != 'Continuous' ";
797  }
798 
799  if (oldestFirst)
800  sql += "ORDER BY E.StartTime ASC";
801  else
802  sql += "ORDER BY E.StartTime DESC";
803 
804  if (mysql_query(&g_dbConn, sql.c_str()))
805  {
806  fprintf(stderr, "%s\n", mysql_error(&g_dbConn));
807  sendError(ERROR_MYSQL_QUERY);
808  return;
809  }
810 
811  res = mysql_store_result(&g_dbConn);
812  int eventCount = mysql_num_rows(res);
813 
814  if (m_debug)
815  cout << "Got " << eventCount << " events" << endl;
816 
817  ADD_INT(outStr, eventCount)
818 
819  for (int x = 0; x < eventCount; x++)
820  {
821  row = mysql_fetch_row(res);
822  if (row)
823  {
824  ADD_STR(outStr, row[0]) // eventID
825  ADD_STR(outStr, row[1]) // event name
826  ADD_STR(outStr, row[2]) // monitorID
827  ADD_STR(outStr, row[3]) // monitor name
828  row[4][10] = 'T';
829  ADD_STR(outStr, row[4]) // start time
830  ADD_STR(outStr, row[5]) // length
831  }
832  else
833  {
834  cout << "Failed to get mysql row" << endl;
835  sendError(ERROR_MYSQL_ROW);
836  return;
837  }
838  }
839 
840  mysql_free_result(res);
841 
842  send(outStr);
843 }
844 
845 void ZMServer::handleGetEventDates(vector<string> tokens)
846 {
847  string outStr("");
848 
849  if (tokens.size() != 3)
850  {
851  sendError(ERROR_TOKEN_COUNT);
852  return;
853  }
854 
855  string monitor = tokens[1];
856  bool oldestFirst = (tokens[2] == "1");
857 
858  if (m_debug)
859  cout << "Loading event dates for monitor: " << monitor << endl;
860 
861  ADD_STR(outStr, "OK")
862 
863  MYSQL_RES *res;
864  MYSQL_ROW row;
865 
866  string sql("SELECT DISTINCT DATE(E.StartTime) "
867  "from Events as E inner join Monitors as M on E.MonitorId = M.Id ");
868 
869  if (monitor != "<ANY>")
870  sql += "WHERE M.Name = '" + monitor + "' ";
871 
872  if (oldestFirst)
873  sql += "ORDER BY E.StartTime ASC";
874  else
875  sql += "ORDER BY E.StartTime DESC";
876 
877  if (mysql_query(&g_dbConn, sql.c_str()))
878  {
879  fprintf(stderr, "%s\n", mysql_error(&g_dbConn));
880  sendError(ERROR_MYSQL_QUERY);
881  return;
882  }
883 
884  res = mysql_store_result(&g_dbConn);
885  int dateCount = mysql_num_rows(res);
886 
887  if (m_debug)
888  cout << "Got " << dateCount << " dates" << endl;
889 
890  ADD_INT(outStr, dateCount)
891 
892  for (int x = 0; x < dateCount; x++)
893  {
894  row = mysql_fetch_row(res);
895  if (row)
896  {
897  ADD_STR(outStr, row[0]) // event date
898  }
899  else
900  {
901  cout << "Failed to get mysql row" << endl;
902  sendError(ERROR_MYSQL_ROW);
903  return;
904  }
905  }
906 
907  mysql_free_result(res);
908 
909  send(outStr);
910 }
911 
913 {
914  string outStr("");
915  ADD_STR(outStr, "OK")
916 
917  // get monitor list
918  MYSQL_RES *res;
919  MYSQL_ROW row;
920 
921  string sql("SELECT Id, Name, Type, Device, Host, Channel, Function, Enabled "
922  "FROM Monitors;");
923  if (mysql_query(&g_dbConn, sql.c_str()))
924  {
925  fprintf(stderr, "%s\n", mysql_error(&g_dbConn));
926  sendError(ERROR_MYSQL_QUERY);
927  return;
928  }
929 
930  res = mysql_store_result(&g_dbConn);
931 
932  // add monitor count
933  int monitorCount = mysql_num_rows(res);
934 
935  if (m_debug)
936  cout << "Got " << monitorCount << " monitors" << endl;
937 
938  ADD_INT(outStr, monitorCount)
939 
940  for (int x = 0; x < monitorCount; x++)
941  {
942  row = mysql_fetch_row(res);
943  if (row)
944  {
945  string id = row[0];
946  string type = row[2];
947  string device = row[3];
948  string host = row[4];
949  string channel = row[5];
950  string function = row[6];
951  string enabled = row[7];
952  string name = row[1];
953  string events = "";
954  string zmcStatus = "";
955  string zmaStatus = "";
956  getMonitorStatus(id, type, device, host, channel, function,
957  zmcStatus, zmaStatus, enabled);
958  MYSQL_RES *res2;
959  MYSQL_ROW row2;
960 
961  string sql2("SELECT count(if(Archived=0,1,NULL)) AS EventCount "
962  "FROM Events AS E "
963  "WHERE MonitorId = " + id);
964 
965  if (mysql_query(&g_dbConn, sql2.c_str()))
966  {
967  fprintf(stderr, "%s\n", mysql_error(&g_dbConn));
968  sendError(ERROR_MYSQL_QUERY);
969  return;
970  }
971 
972  res2 = mysql_store_result(&g_dbConn);
973  if (mysql_num_rows(res2) > 0)
974  {
975  row2 = mysql_fetch_row(res2);
976  if (row2)
977  events = row2[0];
978  else
979  {
980  cout << "Failed to get mysql row" << endl;
981  sendError(ERROR_MYSQL_ROW);
982  return;
983  }
984  }
985 
986  ADD_STR(outStr, id)
987  ADD_STR(outStr, name)
988  ADD_STR(outStr, zmcStatus)
989  ADD_STR(outStr, zmaStatus)
990  ADD_STR(outStr, events)
991  ADD_STR(outStr, function)
992  ADD_STR(outStr, enabled)
993 
994  mysql_free_result(res2);
995  }
996  else
997  {
998  cout << "Failed to get mysql row" << endl;
999  sendError(ERROR_MYSQL_ROW);
1000  return;
1001  }
1002  }
1003 
1004  mysql_free_result(res);
1005 
1006  send(outStr);
1007 }
1008 
1009 string ZMServer::runCommand(string command)
1010 {
1011  string outStr("");
1012  FILE *fd = popen(command.c_str(), "r");
1013  char buffer[100];
1014 
1015  while (fgets(buffer, sizeof(buffer), fd) != NULL)
1016  {
1017  outStr += buffer;
1018  }
1019  pclose(fd);
1020  return outStr;
1021 }
1022 
1023 void ZMServer::getMonitorStatus(string id, string type, string device, string host, string channel,
1024  string function, string &zmcStatus, string &zmaStatus,
1025  string enabled)
1026 {
1027  zmaStatus = "";
1028  zmcStatus = "";
1029 
1030  string command(g_binPath + "/zmdc.pl status");
1031  string status = runCommand(command);
1032 
1033  if (type == "Local")
1034  {
1035  if (enabled == "0")
1036  zmaStatus = device + "(" + channel + ") [-]";
1037  else if (status.find("'zma -m " + id + "' running") != string::npos)
1038  zmaStatus = device + "(" + channel + ") [R]";
1039  else
1040  zmaStatus = device + "(" + channel + ") [S]";
1041  }
1042  else
1043  {
1044  if (enabled == "0")
1045  zmaStatus = host + " [-]";
1046  else if (status.find("'zma -m " + id + "' running") != string::npos)
1047  zmaStatus = host + " [R]";
1048  else
1049  zmaStatus = host + " [S]";
1050  }
1051 
1052  if (type == "Local")
1053  {
1054  if (enabled == "0")
1055  zmcStatus = function + " [-]";
1056  else if (status.find("'zmc -d "+ device + "' running") != string::npos)
1057  zmcStatus = function + " [R]";
1058  else
1059  zmcStatus = function + " [S]";
1060  }
1061  else
1062  {
1063  if (enabled == "0")
1064  zmcStatus = function + " [-]";
1065  else if (status.find("'zmc -m " + id + "' running") != string::npos)
1066  zmcStatus = function + " [R]";
1067  else
1068  zmcStatus = function + " [S]";
1069  }
1070 }
1071 
1072 void ZMServer::handleGetEventFrame(vector<string> tokens)
1073 {
1074  static unsigned char buffer[MAX_IMAGE_SIZE];
1075 
1076  if (tokens.size() != 5)
1077  {
1078  sendError(ERROR_TOKEN_COUNT);
1079  return;
1080  }
1081 
1082  string monitorID(tokens[1]);
1083  string eventID(tokens[2]);
1084  int frameNo = atoi(tokens[3].c_str());
1085  string eventTime(tokens[4]);
1086 
1087  if (m_debug)
1088  cout << "Getting frame " << frameNo << " for event " << eventID
1089  << " on monitor " << monitorID << " event time is " << eventTime << endl;
1090 
1091  string outStr("");
1092 
1093  ADD_STR(outStr, "OK")
1094 
1095  // try to find the frame file
1096  string filepath("");
1097  char str[100];
1098 
1099  if (m_useDeepStorage)
1100  {
1101  filepath = g_webPath + "/events/" + monitorID + "/" + eventTime + "/";
1102  sprintf(str, m_eventFileFormat.c_str(), frameNo);
1103  filepath += str;
1104  }
1105  else
1106  {
1107  filepath = g_webPath + "/events/" + monitorID + "/" + eventID + "/";
1108  sprintf(str, m_eventFileFormat.c_str(), frameNo);
1109  filepath += str;
1110  }
1111 
1112  FILE *fd;
1113  int fileSize = 0;
1114  if ((fd = fopen(filepath.c_str(), "r" )))
1115  {
1116  fileSize = fread(buffer, 1, sizeof(buffer), fd);
1117  fclose(fd);
1118  }
1119  else
1120  {
1121  cout << "Can't open " << filepath << ": " << strerror(errno) << endl;
1122  sendError(ERROR_FILE_OPEN + string(" - ") + filepath + " : " + strerror(errno));
1123  return;
1124  }
1125 
1126  if (m_debug)
1127  cout << "Frame size: " << fileSize << endl;
1128 
1129  // get the file size
1130  ADD_INT(outStr, fileSize)
1131 
1132  // send the data
1133  send(outStr, buffer, fileSize);
1134 }
1135 
1136 void ZMServer::handleGetAnalysisFrame(vector<string> tokens)
1137 {
1138  static unsigned char buffer[MAX_IMAGE_SIZE];
1139  char str[100];
1140 
1141  if (tokens.size() != 5)
1142  {
1143  sendError(ERROR_TOKEN_COUNT);
1144  return;
1145  }
1146 
1147  string monitorID(tokens[1]);
1148  string eventID(tokens[2]);
1149  int frameNo = atoi(tokens[3].c_str());
1150  string eventTime(tokens[4]);
1151 
1152  if (m_debug)
1153  cout << "Getting analysis frame " << frameNo << " for event " << eventID
1154  << " on monitor " << monitorID << " event time is " << eventTime << endl;
1155 
1156  // get the 'alarm' frames from the Frames table for this event
1157  MYSQL_RES *res;
1158  MYSQL_ROW row = NULL;
1159 
1160  string sql("");
1161  sql += "SELECT FrameId FROM Frames ";
1162  sql += "WHERE EventID = " + eventID + " ";
1163  sql += "AND Type = 'Alarm' ";
1164  sql += "ORDER BY FrameID";
1165 
1166  if (mysql_query(&g_dbConn, sql.c_str()))
1167  {
1168  fprintf(stderr, "%s\n", mysql_error(&g_dbConn));
1169  sendError(ERROR_MYSQL_QUERY);
1170  return;
1171  }
1172 
1173  res = mysql_store_result(&g_dbConn);
1174  int frameCount = mysql_num_rows(res);
1175  int frameID;
1176  string fileFormat = m_analysisFileFormat;
1177 
1178  // if we didn't find any analysis frames look for a normal frame instead
1179  if (frameCount == 0 || m_useAnalysisImages == false)
1180  {
1181  mysql_free_result(res);
1182 
1183  frameNo = 0;
1184  fileFormat = m_eventFileFormat;
1185  sql = "SELECT FrameId FROM Frames ";
1186  sql += "WHERE EventID = " + eventID + " ";
1187  sql += "ORDER BY FrameID";
1188 
1189  if (mysql_query(&g_dbConn, sql.c_str()))
1190  {
1191  fprintf(stderr, "%s\n", mysql_error(&g_dbConn));
1192  sendError(ERROR_MYSQL_QUERY);
1193  return;
1194  }
1195 
1196  res = mysql_store_result(&g_dbConn);
1197  frameCount = mysql_num_rows(res);
1198  }
1199 
1200  // if the required frame mumber is 0 or out of bounds then use the middle frame
1201  if (frameNo == 0 || frameNo < 0 || frameNo > frameCount)
1202  frameNo = (frameCount / 2) + 1;
1203 
1204  // move to the required frame in the table
1205  for (int x = 0; x < frameNo; x++)
1206  {
1207  row = mysql_fetch_row(res);
1208  }
1209 
1210  if (row)
1211  {
1212  frameID = atoi(row[0]);
1213  }
1214  else
1215  {
1216  cout << "handleGetAnalyseFrame: Failed to get mysql row for frameNo " << frameNo << endl;
1217  sendError(ERROR_MYSQL_ROW);
1218  return;
1219  }
1220 
1221  mysql_free_result(res);
1222 
1223  string outStr("");
1224 
1225  ADD_STR(outStr, "OK")
1226 
1227  // try to find the analyse frame file
1228  string filepath("");
1229  if (m_useDeepStorage)
1230  {
1231  filepath = g_webPath + "/events/" + monitorID + "/" + eventTime + "/";
1232  sprintf(str, fileFormat.c_str(), frameID);
1233  filepath += str;
1234  }
1235  else
1236  {
1237  filepath = g_webPath + "/events/" + monitorID + "/" + eventID + "/";
1238  sprintf(str, fileFormat.c_str(), frameID);
1239  filepath += str;
1240  }
1241 
1242  FILE *fd;
1243  int fileSize = 0;
1244  if ((fd = fopen(filepath.c_str(), "r" )))
1245  {
1246  fileSize = fread(buffer, 1, sizeof(buffer), fd);
1247  fclose(fd);
1248  }
1249  else
1250  {
1251  cout << "Can't open " << filepath << ": " << strerror(errno) << endl;
1252  sendError(ERROR_FILE_OPEN + string(" - ") + filepath + " : " + strerror(errno));
1253  return;
1254  }
1255 
1256  if (m_debug)
1257  cout << "Frame size: " << fileSize << endl;
1258 
1259  // get the file size
1260  ADD_INT(outStr, fileSize)
1261 
1262  // send the data
1263  send(outStr, buffer, fileSize);
1264 }
1265 
1266 void ZMServer::handleGetLiveFrame(vector<string> tokens)
1267 {
1268  static unsigned char buffer[MAX_IMAGE_SIZE];
1269 
1270  // we need to periodically kick the DB connection here to make sure it
1271  // stays alive because the user may have left the frontend on the live
1272  // view which doesn't query the DB at all and eventually the connection
1273  // will timeout
1275 
1276  if (tokens.size() != 2)
1277  {
1278  sendError(ERROR_TOKEN_COUNT);
1279  return;
1280  }
1281 
1282  int monitorID = atoi(tokens[1].c_str());
1283 
1284  if (m_debug)
1285  cout << "Getting live frame from monitor: " << monitorID << endl;
1286 
1287  string outStr("");
1288 
1289  ADD_STR(outStr, "OK")
1290 
1291  // echo the monitor id
1292  ADD_INT(outStr, monitorID)
1293 
1294  // try to find the correct MONITOR
1295  MONITOR *monitor;
1296  if (m_monitorMap.find(monitorID) != m_monitorMap.end())
1297  monitor = m_monitorMap[monitorID];
1298  else
1299  {
1300  sendError(ERROR_INVALID_MONITOR);
1301  return;
1302  }
1303 
1304  // are the data pointers valid?
1305  if (!monitor->isValid())
1306  {
1307  sendError(ERROR_INVALID_POINTERS);
1308  return;
1309  }
1310 
1311  // read a frame from the shared memory
1312  int dataSize = getFrame(buffer, sizeof(buffer), monitor);
1313 
1314  if (m_debug)
1315  cout << "Frame size: " << dataSize << endl;
1316 
1317  if (dataSize == 0)
1318  {
1319  // not really an error
1320  outStr = "";
1321  ADD_STR(outStr, "WARNING - No new frame available");
1322  send(outStr);
1323  return;
1324  }
1325 
1326  // add status
1327  ADD_STR(outStr, monitor->status)
1328 
1329  // send the data size
1330  ADD_INT(outStr, dataSize)
1331 
1332  // send the data
1333  send(outStr, buffer, dataSize);
1334 }
1335 
1336 void ZMServer::handleGetFrameList(vector<string> tokens)
1337 {
1338  string eventID;
1339  string outStr("");
1340 
1341  if (tokens.size() != 2)
1342  {
1343  sendError(ERROR_TOKEN_COUNT);
1344  return;
1345  }
1346 
1347  eventID = tokens[1];
1348 
1349  if (m_debug)
1350  cout << "Loading frames for event: " << eventID << endl;
1351 
1352  ADD_STR(outStr, "OK")
1353 
1354  MYSQL_RES *res;
1355  MYSQL_ROW row;
1356  string sql("");
1357 
1358  // check to see what type of event this is
1359  sql += "SELECT Cause, Length, Frames FROM Events ";
1360  sql += "WHERE Id = " + eventID + " ";
1361 
1362  if (mysql_query(&g_dbConn, sql.c_str()))
1363  {
1364  fprintf(stderr, "%s\n", mysql_error(&g_dbConn));
1365  sendError(ERROR_MYSQL_QUERY);
1366  return;
1367  }
1368 
1369  res = mysql_store_result(&g_dbConn);
1370  row = mysql_fetch_row(res);
1371 
1372  // make sure we have some frames to display
1373  if (row[1] == NULL || row[2] == NULL)
1374  {
1375  sendError(ERROR_NO_FRAMES);
1376  return;
1377  }
1378 
1379  string cause = row[0];
1380  double length = atof(row[1]);
1381  int frameCount = atoi(row[2]);
1382 
1383  mysql_free_result(res);
1384 
1385  if (cause == "Continuous")
1386  {
1387  // event is a continuous recording so guess the frame delta's
1388 
1389  if (m_debug)
1390  cout << "Got " << frameCount << " frames (continuous event)" << endl;
1391 
1392  ADD_INT(outStr, frameCount)
1393 
1394  if (frameCount > 0)
1395  {
1396  double delta = length / frameCount;
1397 
1398  for (int x = 0; x < frameCount; x++)
1399  {
1400  char str[10];
1401  sprintf(str, "%f", delta);
1402 
1403  ADD_STR(outStr, "Normal") // Type
1404  ADD_STR(outStr, str) // Delta
1405  }
1406  }
1407  }
1408  else
1409  {
1410  sql = "SELECT Type, Delta FROM Frames ";
1411  sql += "WHERE EventID = " + eventID + " ";
1412  sql += "ORDER BY FrameID";
1413 
1414  if (mysql_query(&g_dbConn, sql.c_str()))
1415  {
1416  fprintf(stderr, "%s\n", mysql_error(&g_dbConn));
1417  sendError(ERROR_MYSQL_QUERY);
1418  return;
1419  }
1420 
1421  res = mysql_store_result(&g_dbConn);
1422  frameCount = mysql_num_rows(res);
1423 
1424  if (m_debug)
1425  cout << "Got " << frameCount << " frames" << endl;
1426 
1427  ADD_INT(outStr, frameCount)
1428 
1429  for (int x = 0; x < frameCount; x++)
1430  {
1431  row = mysql_fetch_row(res);
1432  if (row)
1433  {
1434  ADD_STR(outStr, row[0]) // Type
1435  ADD_STR(outStr, row[1]) // Delta
1436  }
1437  else
1438  {
1439  cout << "handleGetFrameList: Failed to get mysql row " << x << endl;
1440  sendError(ERROR_MYSQL_ROW);
1441  return;
1442  }
1443  }
1444 
1445  mysql_free_result(res);
1446  }
1447 
1448  send(outStr);
1449 }
1450 
1452 {
1453  string outStr("");
1454 
1455  ADD_STR(outStr, "OK")
1456 
1457  ADD_INT(outStr, m_monitors.size())
1458 
1459  for (uint x = 0; x < m_monitors.size(); x++)
1460  {
1461  ADD_STR(outStr, m_monitors.at(x)->name)
1462  }
1463 
1464  send(outStr);
1465 }
1466 
1468 {
1469  string outStr("");
1470 
1471  ADD_STR(outStr, "OK")
1472 
1473  if (m_debug)
1474  cout << "We have " << m_monitors.size() << " monitors" << endl;
1475 
1476  ADD_INT(outStr, m_monitors.size())
1477 
1478  for (uint x = 0; x < m_monitors.size(); x++)
1479  {
1480  MONITOR *mon = m_monitors.at(x);
1481 
1482  ADD_INT(outStr, mon->mon_id)
1483  ADD_STR(outStr, mon->name)
1484  ADD_INT(outStr, mon->width)
1485  ADD_INT(outStr, mon->height)
1486  ADD_INT(outStr, mon->bytes_per_pixel)
1487 
1488  if (m_debug)
1489  {
1490  cout << "id: " << mon->mon_id << endl;
1491  cout << "name: " << mon->name << endl;
1492  cout << "width: " << mon->width << endl;
1493  cout << "height: " << mon->height << endl;
1494  cout << "palette: " << mon->palette << endl;
1495  cout << "byte per pixel: " << mon->bytes_per_pixel << endl;
1496  cout << "sub pixel order:" << mon->getSubpixelOrder() << endl;
1497  cout << "-------------------" << endl;
1498  }
1499  }
1500 
1501  send(outStr);
1502 }
1503 
1504 void ZMServer::handleDeleteEvent(vector<string> tokens)
1505 {
1506  string eventID;
1507  string outStr("");
1508 
1509  if (tokens.size() != 2)
1510  {
1511  sendError(ERROR_TOKEN_COUNT);
1512  return;
1513  }
1514 
1515  eventID = tokens[1];
1516 
1517  if (m_debug)
1518  cout << "Deleting event: " << eventID << endl;
1519 
1520  ADD_STR(outStr, "OK")
1521 
1522  string sql("");
1523  sql += "DELETE FROM Events WHERE Id = " + eventID;
1524 
1525  if (mysql_query(&g_dbConn, sql.c_str()))
1526  {
1527  fprintf(stderr, "%s\n", mysql_error(&g_dbConn));
1528  sendError(ERROR_MYSQL_QUERY);
1529  return;
1530  }
1531 
1532  // run zmaudit.pl to clean everything up
1533  string command(g_binPath + "/zmaudit.pl &");
1534  errno = 0;
1535  if (system(command.c_str()) < 0 && errno)
1536  cerr << "Failed to run '" << command << "'" << endl;
1537 
1538  send(outStr);
1539 }
1540 
1541 void ZMServer::handleDeleteEventList(vector<string> tokens)
1542 {
1543  string eventList("");
1544  string outStr("");
1545 
1546  vector<string>::iterator it = tokens.begin();
1547  if (it != tokens.end())
1548  ++it;
1549  while (it != tokens.end())
1550  {
1551  if (eventList == "")
1552  eventList = (*it);
1553  else
1554  eventList += "," + (*it);
1555 
1556  ++it;
1557  }
1558 
1559  if (m_debug)
1560  cout << "Deleting events: " << eventList << endl;
1561 
1562  string sql("");
1563  sql += "DELETE FROM Events WHERE Id IN (" + eventList + ")";
1564 
1565  if (mysql_query(&g_dbConn, sql.c_str()))
1566  {
1567  fprintf(stderr, "%s\n", mysql_error(&g_dbConn));
1568  sendError(ERROR_MYSQL_QUERY);
1569  return;
1570  }
1571 
1572  ADD_STR(outStr, "OK")
1573  send(outStr);
1574 }
1575 
1577 {
1578  string outStr("");
1579 
1580  // run zmaudit.pl to clean up orphaned db entries etc
1581  string command(g_binPath + "/zmaudit.pl &");
1582 
1583  if (m_debug)
1584  cout << "Running command: " << command << endl;
1585 
1586  errno = 0;
1587  if (system(command.c_str()) < 0 && errno)
1588  cerr << "Failed to run '" << command << "'" << endl;
1589 
1590  ADD_STR(outStr, "OK")
1591  send(outStr);
1592 }
1593 
1595 {
1596  m_monitors.clear();
1597  m_monitorMap.clear();
1598 
1599  string sql("SELECT Id, Name, Width, Height, ImageBufferCount, MaxFPS, Palette, ");
1600  sql += " Type, Function, Enabled, Device, Host, Controllable, TrackMotion";
1601 
1602  if (checkVersion(1, 26, 0))
1603  sql += ", Colours";
1604 
1605  sql += " FROM Monitors";
1606  sql += " ORDER BY Sequence";
1607 
1608  MYSQL_RES *res;
1609  MYSQL_ROW row;
1610 
1611  if (mysql_query(&g_dbConn, sql.c_str()))
1612  {
1613  fprintf(stderr, "%s\n", mysql_error(&g_dbConn));
1614  return;
1615  }
1616 
1617  res = mysql_store_result(&g_dbConn);
1618  int monitorCount = mysql_num_rows(res);
1619 
1620  if (m_debug)
1621  cout << "Got " << monitorCount << " monitors" << endl;
1622 
1623  for (int x = 0; x < monitorCount; x++)
1624  {
1625  row = mysql_fetch_row(res);
1626  if (row)
1627  {
1628  MONITOR *m = new MONITOR;
1629  m->mon_id = atoi(row[0]);
1630  m->name = row[1];
1631  m->width = atoi(row[2]);
1632  m->height = atoi(row[3]);
1633  m->image_buffer_count = atoi(row[4]);
1634  m->palette = atoi(row[6]);
1635  m->type = row[7];
1636  m->function = row[8];
1637  m->enabled = atoi(row[9]);
1638  m->device = row[10];
1639  m->host = row[11];
1640  m->controllable = atoi(row[12]);
1641  m->trackMotion = atoi(row[13]);
1642 
1643  // from version 1.26.0 ZM can have 1, 3 or 4 bytes per pixel
1644  // older versions can be 1 or 3
1645  if (checkVersion(1, 26, 0))
1646  m->bytes_per_pixel = atoi(row[14]);
1647  else
1648  if (m->palette == 1)
1649  m->bytes_per_pixel = 1;
1650  else
1651  m->bytes_per_pixel = 3;
1652 
1653  m_monitors.push_back(m);
1654  m_monitorMap[m->mon_id] = m;
1655 
1657  }
1658  else
1659  {
1660  cout << "Failed to get mysql row" << endl;
1661  return;
1662  }
1663  }
1664 
1665  mysql_free_result(res);
1666 }
1667 
1668 int ZMServer::getFrame(unsigned char *buffer, int bufferSize, MONITOR *monitor)
1669 {
1670  (void) bufferSize;
1671 
1672  // is there a new frame available?
1673  if (monitor->getLastWriteIndex() == monitor->last_read)
1674  return 0;
1675 
1676  // sanity check last_read
1677  if (monitor->getLastWriteIndex() < 0 ||
1678  monitor->getLastWriteIndex() >= monitor->image_buffer_count)
1679  return 0;
1680 
1681  monitor->last_read = monitor->getLastWriteIndex();
1682 
1683  switch (monitor->getState())
1684  {
1685  case IDLE:
1686  monitor->status = "Idle";
1687  break;
1688  case PREALARM:
1689  monitor->status = "Pre Alarm";
1690  break;
1691  case ALARM:
1692  monitor->status = "Alarm";
1693  break;
1694  case ALERT:
1695  monitor->status = "Alert";
1696  break;
1697  case TAPE:
1698  monitor->status = "Tape";
1699  break;
1700  default:
1701  monitor->status = "Unknown";
1702  break;
1703  }
1704 
1705  // FIXME: should do some sort of compression JPEG??
1706  // just copy the data to our buffer for now
1707 
1708  // fixup the colours if necessary we aim to always send RGB24 images
1709  unsigned char *data = monitor->shared_images + monitor->getFrameSize() * monitor->last_read;
1710  unsigned int rpos = 0;
1711  unsigned int wpos = 0;
1712 
1713  switch (monitor->getSubpixelOrder())
1714  {
1715  case ZM_SUBPIX_ORDER_NONE:
1716  {
1717  for (wpos = 0, rpos = 0; wpos < (unsigned int) (monitor->width * monitor->height * 3); wpos += 3, rpos += 1)
1718  {
1719  buffer[wpos + 0] = data[rpos + 0]; // r
1720  buffer[wpos + 1] = data[rpos + 0]; // g
1721  buffer[wpos + 2] = data[rpos + 0]; // b
1722  }
1723 
1724  break;
1725  }
1726 
1727  case ZM_SUBPIX_ORDER_RGB:
1728  {
1729  for (wpos = 0, rpos = 0; wpos < (unsigned int) (monitor->width * monitor->height * 3); wpos += 3, rpos += 3)
1730  {
1731  buffer[wpos + 0] = data[rpos + 0]; // r
1732  buffer[wpos + 1] = data[rpos + 1]; // g
1733  buffer[wpos + 2] = data[rpos + 2]; // b
1734  }
1735 
1736  break;
1737  }
1738 
1739  case ZM_SUBPIX_ORDER_BGR:
1740  {
1741  for (wpos = 0, rpos = 0; wpos < (unsigned int) (monitor->width * monitor->height * 3); wpos += 3, rpos += 3)
1742  {
1743  buffer[wpos + 0] = data[rpos + 2]; // r
1744  buffer[wpos + 1] = data[rpos + 1]; // g
1745  buffer[wpos + 2] = data[rpos + 0]; // b
1746  }
1747 
1748  break;
1749  }
1750  case ZM_SUBPIX_ORDER_BGRA:
1751  {
1752  for (wpos = 0, rpos = 0; wpos < (unsigned int) (monitor->width * monitor->height * 3); wpos += 3, rpos += 4)
1753  {
1754  buffer[wpos + 0] = data[rpos + 2]; // r
1755  buffer[wpos + 1] = data[rpos + 1]; // g
1756  buffer[wpos + 2] = data[rpos + 0]; // b
1757  }
1758 
1759  break;
1760  }
1761 
1762  case ZM_SUBPIX_ORDER_RGBA:
1763  {
1764  for (wpos = 0, rpos = 0; wpos < (unsigned int) (monitor->width * monitor->height * 3); wpos += 3, rpos += 4)
1765  {
1766  buffer[wpos + 0] = data[rpos + 0]; // r
1767  buffer[wpos + 1] = data[rpos + 1]; // g
1768  buffer[wpos + 2] = data[rpos + 2]; // b
1769  }
1770 
1771  break;
1772  }
1773 
1774  case ZM_SUBPIX_ORDER_ABGR:
1775  {
1776  for (wpos = 0, rpos = 0; wpos < (unsigned int) (monitor->width * monitor->height * 3); wpos += 3, rpos += 4)
1777  {
1778  buffer[wpos + 0] = data[rpos + 3]; // r
1779  buffer[wpos + 1] = data[rpos + 2]; // g
1780  buffer[wpos + 2] = data[rpos + 1]; // b
1781  }
1782 
1783  break;
1784  }
1785 
1786  case ZM_SUBPIX_ORDER_ARGB:
1787  {
1788  for (wpos = 0, rpos = 0; wpos < (unsigned int) (monitor->width * monitor->height * 3); wpos += 3, rpos += 4)
1789  {
1790  buffer[wpos + 0] = data[rpos + 1]; // r
1791  buffer[wpos + 1] = data[rpos + 2]; // g
1792  buffer[wpos + 2] = data[rpos + 3]; // b
1793  }
1794 
1795  break;
1796  }
1797  }
1798 
1799  return monitor->width * monitor->height * 3;
1800 }
1801 
1802 string ZMServer::getZMSetting(const string &setting)
1803 {
1804  string result;
1805  string sql("SELECT Name, Value FROM Config ");
1806  sql += "WHERE Name = '" + setting + "'";
1807 
1808  MYSQL_RES *res;
1809  MYSQL_ROW row;
1810 
1811  if (mysql_query(&g_dbConn, sql.c_str()))
1812  {
1813  fprintf(stderr, "%s\n", mysql_error(&g_dbConn));
1814  return "";
1815  }
1816 
1817  res = mysql_store_result(&g_dbConn);
1818  row = mysql_fetch_row(res);
1819  if (row)
1820  {
1821  result = row[1];
1822  }
1823  else
1824  {
1825  cout << "Failed to get mysql row" << endl;
1826  result = "";
1827  }
1828 
1829  if (m_debug)
1830  cout << "getZMSetting: " << setting << " Result: " << result << endl;
1831 
1832  mysql_free_result(res);
1833 
1834  return result;
1835 }
1836 
1837 void ZMServer::handleSetMonitorFunction(vector<string> tokens)
1838 {
1839  string outStr("");
1840 
1841  if (tokens.size() != 4)
1842  {
1843  sendError(ERROR_TOKEN_COUNT);
1844  return;
1845  }
1846 
1847  string monitorID(tokens[1]);
1848  string function(tokens[2]);
1849  string enabled(tokens[3]);
1850 
1851  // Check validity of input passed to server. Does monitor exist && is function ok
1852  if (m_monitorMap.find(atoi(monitorID.c_str())) == m_monitorMap.end())
1853  {
1854  sendError(ERROR_INVALID_MONITOR);
1855  return;
1856  }
1857 
1858  if (function != FUNCTION_NONE && function != FUNCTION_MONITOR &&
1859  function != FUNCTION_MODECT && function != FUNCTION_NODECT &&
1860  function != FUNCTION_RECORD && function != FUNCTION_MOCORD)
1861  {
1862  sendError(ERROR_INVALID_MONITOR_FUNCTION);
1863  return;
1864  }
1865 
1866  if (enabled != "0" && enabled != "1")
1867  {
1868  sendError(ERROR_INVALID_MONITOR_ENABLE_VALUE);
1869  return;
1870  }
1871 
1872  if (m_debug)
1873  cout << "User input validated OK" << endl;
1874 
1875 
1876  // Now perform db update && (re)start/stop daemons as required.
1877  MONITOR *monitor = m_monitorMap[atoi(monitorID.c_str())];
1878  string oldFunction = monitor->function;
1879  string newFunction = function;
1880  int oldEnabled = monitor->enabled;
1881  int newEnabled = atoi(enabled.c_str());
1882  monitor->function = newFunction;
1883  monitor->enabled = newEnabled;
1884 
1885  if (m_debug)
1886  cout << "SetMonitorFunction MonitorId: " << monitorID << endl <<
1887  " oldEnabled: " << oldEnabled << endl <<
1888  " newEnabled: " << newEnabled << endl <<
1889  " oldFunction: " << oldFunction << endl <<
1890  " newFunction: " << newFunction << endl;
1891 
1892  if ( newFunction != oldFunction || newEnabled != oldEnabled)
1893  {
1894  string sql("UPDATE Monitors ");
1895  sql += "SET Function = '" + function + "', ";
1896  sql += "Enabled = '" + enabled + "' ";
1897  sql += "WHERE Id = '" + monitorID + "'";
1898 
1899  if (mysql_query(&g_dbConn, sql.c_str()))
1900  {
1901  fprintf(stderr, "%s\n", mysql_error(&g_dbConn));
1902  sendError(ERROR_MYSQL_QUERY);
1903  return;
1904  }
1905 
1906  if (m_debug)
1907  cout << "Monitor function SQL update OK" << endl;
1908 
1909  string status = runCommand(g_binPath + "/zmdc.pl check");
1910 
1911  // Now refresh servers
1912  if (RUNNING.compare(0, RUNNING.size(), status, 0, RUNNING.size()) == 0)
1913  {
1914  if (m_debug)
1915  cout << "Monitor function Refreshing daemons" << endl;
1916 
1917  bool restart = (oldFunction == FUNCTION_NONE) ||
1918  (newFunction == FUNCTION_NONE) ||
1919  (newEnabled != oldEnabled);
1920 
1921  if (restart)
1922  zmcControl(monitor, RESTART);
1923  else
1924  zmcControl(monitor, "");
1925  zmaControl(monitor, RELOAD);
1926  }
1927  else
1928  if (m_debug)
1929  cout << "zm daemons are not running" << endl;
1930  }
1931  else
1932  cout << "Not updating monitor function as identical to existing configuration" << endl;
1933 
1934  ADD_STR(outStr, "OK")
1935  send(outStr);
1936 }
1937 
1938 void ZMServer::zmcControl(MONITOR *monitor, const string &mode)
1939 {
1940  string zmcArgs = "";
1941  string sql = "";
1942  sql += "SELECT count(if(Function!='None',1,NULL)) as ActiveCount ";
1943  sql += "FROM Monitors ";
1944 
1945  if (monitor->type == "Local" )
1946  {
1947  sql += "WHERE Device = '" + monitor->device + "'";
1948  zmcArgs = "-d " + monitor->device;
1949  }
1950  else
1951  {
1952  sql += "WHERE Id = '" + monitor->getIdStr() + "'";
1953  zmcArgs = "-m " + monitor->mon_id;
1954  }
1955 
1956  if (mysql_query(&g_dbConn, sql.c_str()))
1957  {
1958  fprintf(stderr, "%s\n", mysql_error(&g_dbConn));
1959  sendError(ERROR_MYSQL_QUERY);
1960  return;
1961  }
1962 
1963  MYSQL_RES *res;
1964  MYSQL_ROW row;
1965  int activeCount;
1966 
1967  res = mysql_store_result(&g_dbConn);
1968  row = mysql_fetch_row(res);
1969 
1970  if (row)
1971  activeCount = atoi(row[0]);
1972  else
1973  {
1974  sendError(ERROR_MYSQL_QUERY);
1975  return;
1976  }
1977 
1978  if (!activeCount)
1979  runCommand(g_binPath + "/zmdc.pl stop zmc " + zmcArgs);
1980  else
1981  {
1982  if (mode == RESTART)
1983  runCommand(g_binPath + "/zmdc.pl stop zmc " + zmcArgs);
1984 
1985  runCommand(g_binPath + "/zmdc.pl start zmc " + zmcArgs);
1986  }
1987 }
1988 
1989 void ZMServer::zmaControl(MONITOR *monitor, const string &mode)
1990 {
1991  int zmOptControl = atoi(getZMSetting("ZM_OPT_CONTROL").c_str());
1992  int zmOptFrameServer = atoi(getZMSetting("ZM_OPT_FRAME_SERVER").c_str());
1993 
1994  if (monitor->function == FUNCTION_MODECT ||
1995  monitor->function == FUNCTION_RECORD ||
1996  monitor->function == FUNCTION_MOCORD ||
1997  monitor->function == FUNCTION_NODECT)
1998  {
1999  if (mode == RESTART)
2000  {
2001  if (zmOptControl)
2002  runCommand(g_binPath + "/zmdc.pl stop zmtrack.pl -m " + monitor->getIdStr());
2003 
2004  runCommand(g_binPath + "/zmdc.pl stop zma -m " + monitor->getIdStr());
2005 
2006  if (zmOptFrameServer)
2007  runCommand(g_binPath + "/zmdc.pl stop zmf -m " + monitor->getIdStr());
2008  }
2009 
2010  if (zmOptFrameServer)
2011  runCommand(g_binPath + "/zmdc.pl start zmf -m " + monitor->getIdStr());
2012 
2013  runCommand(g_binPath + "/zmdc.pl start zma -m " + monitor->getIdStr());
2014 
2015  if (zmOptControl && monitor->controllable && monitor->trackMotion &&
2016  ( monitor->function == FUNCTION_MODECT || monitor->function == FUNCTION_MOCORD) )
2017  runCommand(g_binPath + "/zmdc.pl start zmtrack.pl -m " + monitor->getIdStr());
2018 
2019  if (mode == RELOAD)
2020  runCommand(g_binPath + "/zmdc.pl reload zma -m " + monitor->getIdStr());
2021  }
2022  else
2023  {
2024  if (zmOptControl)
2025  runCommand(g_binPath + "/zmdc.pl stop zmtrack.pl -m " + monitor->getIdStr());
2026 
2027  runCommand(g_binPath + "/zmdc.pl stop zma -m " + monitor->getIdStr());
2028 
2029  if (zmOptFrameServer)
2030  runCommand(g_binPath + "/zmdc.pl stop zmf -m " + monitor->getIdStr());
2031  }
2032 }
GLuint buffer
void tokenize(const string &command, vector< string > &tokens)
Definition: zmserver.cpp:524
MYTH_GLsizeiptr const GLvoid * data
string id
Definition: zmserver.h:189
const string FUNCTION_MODECT
Definition: zmserver.h:52
const string RUNNING
Definition: zmserver.h:60
int mapFile
Definition: zmserver.h:184
void getMonitorStatus(string id, string type, string device, string host, string channel, string function, string &zmcStatus, string &zmaStatus, string enabled)
Definition: zmserver.cpp:1023
int getState(void)
Definition: zmserver.cpp:396
void handleGetEventFrame(vector< string > tokens)
Definition: zmserver.cpp:1072
int last_read
Definition: zmserver.h:179
int mon_id
Definition: zmserver.h:177
string g_server
Definition: zmserver.cpp:86
bool m_debug
Definition: zmserver.h:232
int g_minorVersion
Definition: zmserver.cpp:93
void connectToDatabase(void)
Definition: zmserver.cpp:180
bool m_useDeepStorage
Definition: zmserver.h:236
static void error(const char *str,...)
Definition: vbi.c:41
void initMonitor(bool debug, string mmapPath, int shmKey)
Definition: zmserver.cpp:240
void handleGetCameraList(void)
Definition: zmserver.cpp:1451
string g_webPath
Definition: zmserver.cpp:88
void kickDatabase(bool debug)
Definition: zmserver.cpp:204
string device
Definition: zmserver.h:171
void zmaControl(MONITOR *monitor, const string &mode)
Definition: zmserver.cpp:1989
void handleRunZMAudit(void)
Definition: zmserver.cpp:1576
string host
Definition: zmserver.h:172
key_t m_shmKey
Definition: zmserver.h:240
int height
Definition: zmserver.h:175
long f_bavail
Definition: compat.h:159
const char * filename
Definition: ioapi.h:135
void handleGetEventList(vector< string > tokens)
Definition: zmserver.cpp:751
void loadZMConfig(const string &configfile)
Definition: zmserver.cpp:107
unsigned int uint
Definition: compat.h:136
const string RESTART
Definition: zmserver.h:58
string getZMSetting(const string &setting)
Definition: zmserver.cpp:1802
typedef int(ZCALLBACK *close_file_func) OF((voidpf opaque
int palette
Definition: zmserver.h:181
void handleGetEventDates(vector< string > tokens)
Definition: zmserver.cpp:845
Definition: zmserver.h:68
string g_webUser
Definition: zmserver.cpp:90
int m_sock
Definition: zmserver.h:233
int g_revisionVersion
Definition: zmserver.cpp:94
string g_zmversion
Definition: zmserver.cpp:84
string g_binPath
Definition: zmserver.cpp:91
voidpf void * buf
Definition: ioapi.h:136
const string FUNCTION_NONE
Definition: zmserver.h:56
State state
Definition: zmserver.h:78
bool isValid(void)
Definition: zmserver.cpp:367
string m_eventFileFormat
Definition: zmserver.h:238
int g_majorVersion
Definition: zmserver.cpp:92
uint32_t state
Definition: zmserver.h:100
time_t g_lastDBKick
Definition: zmserver.cpp:96
string g_password
Definition: zmserver.cpp:85
void handleGetFrameList(vector< string > tokens)
Definition: zmserver.cpp:1336
MYSQL g_dbConn
Definition: zmserver.cpp:83
char m_buf[10]
Definition: zmserver.h:242
uint32_t imagesize
Definition: zmserver.h:113
string getIdStr(void)
Definition: zmserver.cpp:377
int statfs(const char *path, struct statfs *buffer)
Definition: compat.h:166
string type
Definition: zmserver.h:168
vector< MONITOR * > m_monitors
Definition: zmserver.h:234
const char * name
Definition: ParseText.cpp:338
const string FUNCTION_MOCORD
Definition: zmserver.h:55
string g_database
Definition: zmserver.cpp:87
int errno
bool send(const string &s) const
Definition: zmserver.cpp:608
map< int, MONITOR * > m_monitorMap
Definition: zmserver.h:235
void * shm_ptr
Definition: zmserver.h:185
const string FUNCTION_RECORD
Definition: zmserver.h:54
int image_buffer_count
Definition: zmserver.h:173
void handleGetMonitorStatus(void)
Definition: zmserver.cpp:912
void handleGetServerStatus(void)
Definition: zmserver.cpp:696
int enabled
Definition: zmserver.h:170
__u64 val
Definition: videodev2.h:1041
struct v4l2_mpeg_vbi_itv0_line line[35]
Definition: videodev2.h:1039
VERBOSE_PREAMBLE Most debug(nodatabase, notimestamp, noextra)") VERBOSE_MAP(VB_IMPORTANT
unsigned char * shared_images
Definition: zmserver.h:178
int bytes_per_pixel
Definition: zmserver.h:176
typedef long(ZCALLBACK *tell_file_func) OF((voidpf opaque
Definition: compat.h:154
int getFrameSize(void)
Definition: zmserver.cpp:417
bool processRequest(char *buf, int nbytes)
Definition: zmserver.cpp:547
void getMonitorList(void)
Definition: zmserver.cpp:1594
string g_user
Definition: zmserver.cpp:89
string function
Definition: zmserver.h:169
SharedData26 * shared_data26
Definition: zmserver.h:188
int FILE
Definition: mythburn.py:110
int controllable
Definition: zmserver.h:182
void sendError(string error)
Definition: zmserver.cpp:649
void handleGetLiveFrame(vector< string > tokens)
Definition: zmserver.cpp:1266
string status
Definition: zmserver.h:180
void handleGetMonitorList(void)
Definition: zmserver.cpp:1467
string name
Definition: zmserver.h:167
void handleSetMonitorFunction(vector< string > tokens)
Definition: zmserver.cpp:1837
const string FUNCTION_NODECT
Definition: zmserver.h:53
ZMServer(int sock, bool debug)
Definition: zmserver.cpp:427
__u32 id
Definition: videodev2.h:1038
long f_blocks
Definition: compat.h:157
GLint GLenum GLsizei width
int getFrame(unsigned char *buffer, int bufferSize, MONITOR *monitor)
Definition: zmserver.cpp:1668
uint8_t format
Definition: zmserver.h:112
static int x0
Definition: mythsocket.cpp:59
int getLastWriteIndex(void)
Definition: zmserver.cpp:388
Definition: zmserver.h:66
typedef void(APIENTRY *MYTH_GLTEXIMAGE1DPROC)(GLenum target
int getSubpixelOrder(void)
Definition: zmserver.cpp:404
void zmcControl(MONITOR *monitor, const string &mode)
Definition: zmserver.cpp:1938
int width
Definition: zmserver.h:174
long f_bsize
Definition: compat.h:156
Definition: zmserver.h:64
int trackMotion
Definition: zmserver.h:183
GLint GLenum GLsizei GLint GLenum GLenum type
GLuint GLfloat x
uint32_t last_write_index
Definition: zmserver.h:98
void handleDeleteEventList(vector< string > tokens)
Definition: zmserver.cpp:1541
const char int mode
Definition: ioapi.h:135
Definition: zmserver.h:67
const string FUNCTION_MONITOR
Definition: zmserver.h:51
bool m_useAnalysisImages
Definition: zmserver.h:237
void handleDeleteEvent(vector< string > tokens)
Definition: zmserver.cpp:1504
int last_write_index
Definition: zmserver.h:79
void handleGetAnalysisFrame(vector< string > tokens)
Definition: zmserver.cpp:1136
__u32 height
Definition: videodev2.h:1039
string m_analysisFileFormat
Definition: zmserver.h:239
bool checkVersion(int major, int minor, int revision)
Definition: zmserver.cpp:99
const string RELOAD
Definition: zmserver.h:59
string runCommand(string command)
Definition: zmserver.cpp:1009
long long getDiskSpace(const string &filename, long long &total, long long &used)
Definition: zmserver.cpp:666
void handleHello(void)
Definition: zmserver.cpp:656
SharedData * shared_data
Definition: zmserver.h:187
GLenum GLsizei len
void handleGetAlarmStates(void)
Definition: zmserver.cpp:729
string m_mmapPath
Definition: zmserver.h:241