api.export.php 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375
  1. <?php
  2. /**
  3. * Archive records export implementation
  4. */
  5. class Export {
  6. /**
  7. * Contains alter.ini config as key=>value
  8. *
  9. * @var array
  10. */
  11. protected $altCfg = array();
  12. /**
  13. * Contains binpaths.ini config as key=>value
  14. *
  15. * @var array
  16. */
  17. protected $binPaths = array();
  18. /**
  19. * Cameras instance placeholder
  20. *
  21. * @var object
  22. */
  23. protected $cameras = '';
  24. /**
  25. * Contains full cameras data as
  26. *
  27. * @var array
  28. */
  29. protected $allCamerasData = array();
  30. /**
  31. * Storages instance placeholder.
  32. *
  33. * @var object
  34. */
  35. protected $storages = '';
  36. /**
  37. * Archive instance placeholder.
  38. *
  39. * @var object
  40. */
  41. protected $archive = '';
  42. /**
  43. * Contains ffmpeg binary path
  44. *
  45. * @var string
  46. */
  47. protected $ffmpgPath = '';
  48. /**
  49. * System messages helper instance placeholder
  50. *
  51. * @var object
  52. */
  53. protected $messages = '';
  54. /**
  55. * Contains current instance user login
  56. *
  57. * @var string
  58. */
  59. protected $myLogin = '';
  60. /**
  61. * Contains scheduler database abstraction layer
  62. *
  63. * @var object
  64. */
  65. protected $scheduleDb = '';
  66. /**
  67. * ACL instance placeholder
  68. *
  69. * @var object
  70. */
  71. protected $acl = '';
  72. /**
  73. * Export schedule process StarDust instance
  74. *
  75. * @var object
  76. */
  77. protected $scheduleProcess = '';
  78. /**
  79. * Force background schedule run on video export task creation flag
  80. *
  81. * @var bool
  82. */
  83. protected $scheduleForceFlag = false;
  84. /**
  85. * other predefined stuff like routes
  86. */
  87. const LABEL_RUNNING = ' 🏁 ';
  88. const EXPORTLIST_MASK = '_el.txt';
  89. const URL_ME = '?module=export';
  90. const URL_RECORDS = '?module=records';
  91. const ROUTE_CHANNEL = 'exportchannel';
  92. const ROUTE_SHOWDATE = 'exportdatearchive';
  93. const ROUTE_DELETE = 'delrec';
  94. const ROUTE_PREVIEW = 'previewrecord';
  95. const ROUTE_REFRESH = 'updateslookup';
  96. const PROUTE_MODET_RUN = 'filtermotion';
  97. const PROUTE_MODET_SENS = 'motionsensitivity';
  98. const PROUTE_MODET_TIMESCALE = 'motiontimescale';
  99. const ROUTE_SCHED_OK = 'scheduledsuccess';
  100. const ROUTE_BACK_EXPORT = 'chanback';
  101. const PROUTE_DATE_EXPORT = 'dateexport';
  102. const PROUTE_TIME_FROM = 'timefrom';
  103. const PROUTE_TIME_TO = 'timeto';
  104. const PATH_RECORDS = 'howl/recdl/';
  105. const PID_EXPORT = 'EXPORT_';
  106. const PID_SCHEDULE = 'EXPORTSCHEDULE';
  107. const RECORDS_EXT = '.mp4';
  108. const TABLE_SCHED = 'schedule';
  109. const OPTION_MODET = 'MODET_ENABLED';
  110. const OPTION_FORCESCHED = 'EXPORT_FORCED_SCHED';
  111. const WRAPPER = '/bin/wrapi';
  112. public function __construct() {
  113. $this->setLogin();
  114. $this->initMessages();
  115. $this->loadConfigs();
  116. $this->setOptions();
  117. $this->initStarDust();
  118. $this->initStorages();
  119. $this->initCameras();
  120. $this->initArchive();
  121. $this->initScheduleDb();
  122. }
  123. /**
  124. * Sets current instance login
  125. *
  126. * @return void
  127. */
  128. protected function setLogin() {
  129. $this->myLogin = whoami();
  130. }
  131. /**
  132. * Loads some required configs
  133. *
  134. * @global $ubillingConfig
  135. *
  136. * @return void
  137. */
  138. protected function loadConfigs() {
  139. global $ubillingConfig;
  140. $this->binPaths = $ubillingConfig->getBinpaths();
  141. $this->altCfg = $ubillingConfig->getAlter();
  142. }
  143. /**
  144. * Sets required properties depends on config options
  145. *
  146. * @return void
  147. */
  148. protected function setOptions() {
  149. $this->ffmpgPath = $this->binPaths['FFMPG_PATH'];
  150. if (isset($this->altCfg[self::OPTION_FORCESCHED])) {
  151. if ($this->altCfg[self::OPTION_FORCESCHED]) {
  152. $this->scheduleForceFlag = true;
  153. }
  154. }
  155. }
  156. /**
  157. * Inits system messages helper
  158. *
  159. * @return void
  160. */
  161. protected function initMessages() {
  162. $this->messages = new UbillingMessageHelper();
  163. }
  164. /**
  165. * Inits StarDust process manager
  166. *
  167. * @return void
  168. */
  169. protected function initStarDust() {
  170. $this->scheduleProcess = new StarDust(self::PID_SCHEDULE);
  171. }
  172. /**
  173. * Inits cameras into protected prop and loads its full data
  174. *
  175. * @return void
  176. */
  177. protected function initCameras() {
  178. $this->cameras = new Cameras();
  179. $this->allCamerasData = $this->cameras->getAllCamerasFullData();
  180. }
  181. /**
  182. * Inits storages into protected prop for further usage
  183. *
  184. * @return void
  185. */
  186. protected function initStorages() {
  187. $this->storages = new Storages();
  188. }
  189. /**
  190. * Inits archive into protected prop
  191. *
  192. * @return void
  193. */
  194. protected function initArchive() {
  195. $this->archive = new Archive();
  196. }
  197. /**
  198. * Inits schedule database abstraction laye
  199. *
  200. * @return void
  201. */
  202. protected function initScheduleDb() {
  203. $this->scheduleDb = new NyanORM(self::TABLE_SCHED);
  204. }
  205. /**
  206. * Inits ACL instance
  207. *
  208. * @return void
  209. */
  210. protected function initAcl() {
  211. $this->acl = new ACL();
  212. }
  213. /**
  214. * Renders available cameras list
  215. *
  216. * @return string
  217. */
  218. public function renderCamerasList() {
  219. $result = '';
  220. $this->initAcl();
  221. if ($this->acl->haveCamsAssigned()) {
  222. $allStotagesData = $this->storages->getAllStoragesData();
  223. if (!empty($allStotagesData)) {
  224. if (!empty($this->allCamerasData)) {
  225. $screenshots = new ChanShots();
  226. $result .= wf_tag('div', false, 'cameraslist');
  227. foreach ($this->allCamerasData as $io => $each) {
  228. $eachCamId = $each['CAMERA']['id'];
  229. if ($this->acl->isMyCamera($eachCamId)) {
  230. $eachCamIp = $each['CAMERA']['ip'];
  231. $eachCamDesc = $each['CAMERA']['comment'];
  232. $eachCamChannel = $each['CAMERA']['channel'];
  233. $cells = '';
  234. if (cfr('CAMERAS')) {
  235. $cells .= wf_TableCell($eachCamId);
  236. $cells .= wf_TableCell($eachCamIp);
  237. }
  238. $eachCamUrl = self::URL_ME . '&' . self::ROUTE_CHANNEL . '=' . $eachCamChannel;
  239. $camPreview = '';
  240. $chanShot = $screenshots->getChannelScreenShot($eachCamChannel);
  241. if (empty($chanShot)) {
  242. $chanShot = $screenshots::ERR_NOSIG;
  243. } else {
  244. $chanshotValid = $screenshots->isChannelScreenshotValid($chanShot);
  245. if (!$chanshotValid) {
  246. $chanShot = $screenshots::ERR_CORRUPT;
  247. } else {
  248. //replacing chanshot url with base64 encoded image
  249. $embedData = $screenshots->getLastCheckedShot();
  250. if (!empty($embedData)) {
  251. $chanShot = $embedData;
  252. }
  253. }
  254. }
  255. if (!$each['CAMERA']['active']) {
  256. $chanShot = $screenshots::ERR_DISABLD;
  257. }
  258. $camPreview = wf_img($chanShot, $eachCamDesc);
  259. $containerId = 'wrcamcont_' . $eachCamDesc;
  260. $result .= wf_tag('div', false, '', 'id="' . $containerId . '"');
  261. $camInfo = wf_tag('div', false, 'camera-info') . $eachCamDesc . wf_tag('div', true);
  262. $result .= wf_Link($eachCamUrl, $camPreview . $camInfo, false, 'camera-item');
  263. $result .= wf_tag('div', true);
  264. }
  265. }
  266. $result .= wf_tag('div', true);
  267. $result .= wf_AjaxContainer('wrqsstatus', '', '');
  268. } else {
  269. $result .= $this->messages->getStyledMessage(__('Cameras') . ': ' . __('Nothing to show'), 'warning');
  270. }
  271. } else {
  272. $result .= $this->messages->getStyledMessage(__('Storages') . ': ' . __('Nothing found'), 'warning');
  273. }
  274. } else {
  275. $result .= $this->messages->getStyledMessage(__('No assigned cameras to show'), 'warning');
  276. }
  277. return ($result);
  278. }
  279. /**
  280. * Renders recordings availability due some day of month
  281. *
  282. * @return string
  283. */
  284. protected function renderDayRecordsAvailability($chunksList, $date) {
  285. $result = '';
  286. if (!empty($chunksList)) {
  287. $dayMinAlloc = $this->archive->allocDayTimeline();
  288. $chunksByDay = 0;
  289. $curDate = curdate();
  290. $fewMinAgo = strtotime("-5 minute", time());
  291. $fewMinLater = strtotime("+1 minute", time());
  292. foreach ($chunksList as $timeStamp => $eachChunk) {
  293. $dayOfMonth = date("Y-m-d", $timeStamp);
  294. if ($dayOfMonth == $date) {
  295. $timeOfDay = date("H:i", $timeStamp);
  296. if (isset($dayMinAlloc[$timeOfDay])) {
  297. $dayMinAlloc[$timeOfDay] = 1;
  298. $chunksByDay++;
  299. }
  300. }
  301. }
  302. //any records here?
  303. if ($chunksByDay) {
  304. if ($chunksByDay > 3) {
  305. $barWidth = 0.069444444;
  306. $barStyle = 'width:' . $barWidth . '%;';
  307. $result = wf_tag('div', false, '', 'style = "width:100%;"');
  308. foreach ($dayMinAlloc as $eachMin => $recAvail) {
  309. $recAvailBar = ($recAvail) ? 'skins/rec_avail.png' : 'skins/rec_unavail.png';
  310. if ($curDate == $date) {
  311. $eachMinTs = strtotime($date . ' ' . $eachMin . ':00');
  312. if (zb_isTimeStampBetween($fewMinAgo, $fewMinLater, $eachMinTs)) {
  313. $recAvailBar = 'skins/rec_now.png';
  314. }
  315. }
  316. $recAvailTitle = ($recAvail) ? $eachMin : $eachMin . ' - ' . __('No record');
  317. $timeBarLabel = wf_img($recAvailBar, $recAvailTitle, $barStyle);
  318. $result .= $timeBarLabel;
  319. }
  320. $result .= wf_tag('div', true);
  321. }
  322. $result .= wf_delimiter(0);
  323. }
  324. }
  325. return ($result);
  326. }
  327. /**
  328. * Returns custom inline datepicker
  329. *
  330. * @param string $name
  331. * @param array $datesAvail
  332. * @param string $selected
  333. *
  334. * @return string
  335. */
  336. protected function wf_InlineDatePicker($name, $datesAvail, $selected = '') {
  337. $result = '';
  338. $inputId = wf_InputId();
  339. $divId = 'inline' . $inputId;
  340. $result .= wf_HiddenInput($name, $selected, $inputId);
  341. $result .= wf_tag('div', false, '', 'id="' . $divId . '"') . wf_tag('div', true);
  342. $jsCode = wf_tag('script');
  343. $curlang = curlang();
  344. $locale = "monthNamesShort: ['" . rcms_date_localise('Jan') . "','" . rcms_date_localise('Feb') . "','" . rcms_date_localise('Mar') . "','" . rcms_date_localise('Apr') . "','" . rcms_date_localise('Ma') . "','" . rcms_date_localise('Jun') . "','" . rcms_date_localise('Jul') . "','" . rcms_date_localise('Aug') . "','" . rcms_date_localise('Sep') . "','" . rcms_date_localise('Oct') . "','" . rcms_date_localise('Nov') . "','" . rcms_date_localise('Dec') . "'],";
  345. $locale .= "dayNamesMin: ['" . rcms_date_localise('Sun') . "','" . rcms_date_localise('Mon') . "','" . rcms_date_localise('Tue') . "','" . rcms_date_localise('Wed') . "','" . rcms_date_localise('Thu') . "','" . rcms_date_localise('Fri') . "','" . rcms_date_localise('Sat') . "'],";
  346. $locale .= "prevText: '" . __('Previous') . "',";
  347. $locale .= "nextText: '" . __('Next') . "',";
  348. $daysEnable = '';
  349. if (!empty($datesAvail)) {
  350. $allowedDatesJs = ' var enableDays = [';
  351. foreach ($datesAvail as $io => $each) {
  352. $allowedDatesJs .= "'" . $each . "',";
  353. }
  354. $allowedDatesJs = substr($allowedDatesJs, 1, -1);
  355. $allowedDatesJs .= ']';
  356. $daysEnable .= $allowedDatesJs;
  357. $daysEnable .= "
  358. function enableAllTheseDays(date) {
  359. var fDate = $.datepicker.formatDate('yy-mm-dd', date);
  360. var result = [false, \"\"];
  361. $.each(enableDays, function(k, d) {
  362. if (fDate === d) {
  363. result = [true, \"row1\"];
  364. }
  365. });
  366. return result;
  367. }
  368. ";
  369. }
  370. $jsCode .= $daysEnable;
  371. $jsCode .= " $('#" . $divId . "').datepicker({
  372. inline: true,
  373. altField: '#" . $inputId . "',
  374. changeMonth: true,
  375. yearRange: \"-2:+0\",
  376. changeYear: true,
  377. dateFormat: 'yy-mm-dd',
  378. firstDay: 1,
  379. beforeShowDay: enableAllTheseDays,
  380. " . $locale . "
  381. }
  382. );
  383. $('#" . $inputId . "').change(function(){
  384. $('#" . $divId . "').datepicker('setDate', $(this).val());
  385. });
  386. ";
  387. $jsCode .= wf_tag('script', true);
  388. $result .= $jsCode;
  389. return ($result);
  390. }
  391. /**
  392. * Renders export form with timeline for some chunks list
  393. *
  394. * @param string $channelId
  395. * @param array $chunksList
  396. *
  397. * @return string
  398. */
  399. protected function renderExportForm($channelId, $chunksList) {
  400. $result = '';
  401. $channelId = ubRouting::filters($channelId, 'mres');
  402. $dayPointer = ubRouting::checkPost(self::PROUTE_DATE_EXPORT) ? ubRouting::post(self::PROUTE_DATE_EXPORT) : curdate();
  403. if (!empty($chunksList)) {
  404. $datesTmp = array();
  405. foreach ($chunksList as $timeStamp => $chunkName) {
  406. $chunkDate = date("Y-m-d", $timeStamp);
  407. $datesTmp[$chunkDate] = $chunkDate;
  408. }
  409. if (!empty($datesTmp)) {
  410. //TODO: render latest channel screenshot here
  411. $chanShots = new ChanShots();
  412. $latestScreenShot = $chanShots->getChannelScreenShot($channelId);
  413. $grid = '';
  414. if ($latestScreenShot) {
  415. $grid .= wf_tag('div', false, '', 'style="float:left; margin: 5px;"');
  416. $grid .= wf_img_sized($latestScreenShot, '', '', '', 'float:left; height:240px;');
  417. $grid .= wf_tag('div', true);
  418. }
  419. $grid .= wf_tag('div', false, '', 'style="float:left; margin: 5px;"');
  420. $grid .= $this->wf_InlineDatePicker(self::PROUTE_DATE_EXPORT, $datesTmp, $dayPointer);
  421. $grid .= wf_tag('div', true);
  422. $inputs = $grid;
  423. $inputs .= wf_CleanDiv();
  424. $inputs .= wf_TextInput(self::PROUTE_TIME_FROM, '', ubRouting::post(self::PROUTE_TIME_FROM), false, 5, '', self::PROUTE_TIME_FROM, self::PROUTE_TIME_FROM, 'style="display:none;"') . ' ';
  425. $inputs .= wf_TextInput(self::PROUTE_TIME_TO, '', ubRouting::post(self::PROUTE_TIME_TO), false, 5, '', self::PROUTE_TIME_TO, self::PROUTE_TIME_TO, 'style="display:none;"') . ' ';
  426. $sliderCode = file_get_contents('modules/jsc/exportSlider.js');
  427. $inputs .= wf_delimiter();
  428. //time range selection slider
  429. $inputs .= $sliderCode;
  430. $inputs .= wf_delimiter();
  431. //here some timeline for selected day to indicate records availability
  432. $inputs .= $this->renderDayRecordsAvailability($chunksList, $dayPointer);
  433. $inputs .= wf_Submit(__('Save record'));
  434. $result .= wf_Form('', 'POST', $inputs, '');
  435. $result .= wf_CleanDiv();
  436. } else {
  437. $result .= $this->messages->getStyledMessage(__('Nothing to show'), 'warning');
  438. }
  439. }
  440. return ($result);
  441. }
  442. /**
  443. * Renders basic archive lookup interface
  444. *
  445. * @param string $channelId
  446. *
  447. * @return string
  448. */
  449. public function renderExportLookup($channelId) {
  450. $result = '';
  451. $channelId = ubRouting::filters($channelId, 'mres');
  452. //camera ID lookup by channel
  453. $allCamerasChannels = $this->cameras->getAllCamerasChannels();
  454. $cameraId = (isset($allCamerasChannels[$channelId])) ? $allCamerasChannels[$channelId] : 0;
  455. if ($cameraId) {
  456. if (isset($this->allCamerasData[$cameraId])) {
  457. $cameraData = $this->allCamerasData[$cameraId]['CAMERA'];
  458. $showDate = (ubRouting::checkGet(self::ROUTE_SHOWDATE)) ? ubRouting::get(self::ROUTE_SHOWDATE, 'mres') : curdate();
  459. //any chunks here?
  460. $chunksList = $this->storages->getChannelChunks($cameraData['storageid'], $cameraData['channel']);
  461. if (!empty($chunksList)) {
  462. $result .= $this->renderExportForm($channelId, $chunksList);
  463. } else {
  464. $result .= $this->messages->getStyledMessage(__('Nothing to show'), 'warning');
  465. }
  466. } else {
  467. $result .= $this->messages->getStyledMessage(__('Camera') . ' [' . $cameraId . '] ' . __('not exists'), 'error');
  468. }
  469. } else {
  470. $result .= $this->messages->getStyledMessage(__('Camera') . ' ' . __('with channel') . ' `' . $channelId . '` ' . __('not exists'), 'error');
  471. }
  472. $result .= wf_delimiter(1);
  473. $result .= wf_BackLink(self::URL_ME);
  474. if (cfr('CAMERAS')) {
  475. if ($cameraId) {
  476. $result .= wf_Link(Cameras::URL_ME . '&' . Cameras::ROUTE_EDIT . '=' . $cameraId, wf_img('skins/icon_camera_small.png') . ' ' . __('Camera'), false, 'ubButton');
  477. }
  478. }
  479. if (cfr('LIVECAMS')) {
  480. $result .= wf_Link(LiveCams::URL_ME . '&' . LiveCams::ROUTE_VIEW . '=' . $channelId, wf_img('skins/icon_live_small.png') . ' ' . __('Live'), false, 'ubButton');
  481. }
  482. if (cfr('ARCHIVE')) {
  483. $result .= wf_Link(Archive::URL_ME . '&' . Archive::ROUTE_VIEW . '=' . $channelId, wf_img('skins/icon_archive_small.png') . ' ' . __('Video from camera'), false, 'ubButton');
  484. }
  485. return ($result);
  486. }
  487. /**
  488. * Prepares per-user recordings space
  489. *
  490. * @param string $userLogin
  491. *
  492. * @return string
  493. */
  494. protected function prepareRecordingsDir($userLogin = '') {
  495. $result = '';
  496. if (empty($userLogin)) {
  497. //using current user`s instance
  498. $userLogin = $this->myLogin;
  499. }
  500. if (!empty($userLogin)) {
  501. $fullUserPath = self::PATH_RECORDS . $userLogin;
  502. //base recordings path
  503. if (!file_exists(self::PATH_RECORDS)) {
  504. //creating base path
  505. mkdir(self::PATH_RECORDS, 0777);
  506. chmod(self::PATH_RECORDS, 0777);
  507. }
  508. if (!file_exists($fullUserPath)) {
  509. //and per-user path
  510. mkdir($fullUserPath, 0777);
  511. chmod($fullUserPath, 0777);
  512. }
  513. if (file_exists($fullUserPath)) {
  514. $result = $fullUserPath . '/'; //with ending slash
  515. }
  516. }
  517. return ($result);
  518. }
  519. /**
  520. * Returns space used by user recordings
  521. *
  522. * @param string $recordsDir
  523. *
  524. * @return int
  525. */
  526. protected function getUserUsedSpace($recordsDir) {
  527. $result = 0;
  528. if (!empty($recordsDir)) {
  529. $allRecords = rcms_scandir($recordsDir);
  530. if (!empty($allRecords)) {
  531. foreach ($allRecords as $io => $eachRecord) {
  532. $result += filesize($recordsDir . $eachRecord);
  533. }
  534. }
  535. }
  536. return ($result);
  537. }
  538. /**
  539. * Returns count of users registered in system
  540. *
  541. * @return int
  542. */
  543. protected function getUserCount() {
  544. $result = 0;
  545. $allUsers = rcms_scandir(USERS_PATH);
  546. if (!empty($allUsers)) {
  547. $result = sizeof($allUsers);
  548. }
  549. return ($result);
  550. }
  551. /**
  552. * Returns count bytes count allowed to each user to store his records
  553. *
  554. * @return int
  555. */
  556. protected function getUserMaxSpace() {
  557. $result = 0;
  558. $storageTotalSpace = disk_total_space('/');
  559. $storageFreeSpace = disk_free_space('/');
  560. $usedStorageSpace = $storageTotalSpace - $storageFreeSpace;
  561. if (isset($this->altCfg['EXPORTS_RESERVED_SPACE'])) {
  562. $maxUsagePercent = 100 - ($this->altCfg['EXPORTS_RESERVED_SPACE']); // explict value
  563. } else {
  564. $maxUsagePercent = 100 - ($this->altCfg['STORAGE_RESERVED_SPACE'] / 2); // half of reserved space
  565. }
  566. $maxUsageSpace = zb_Percent($storageTotalSpace, $maxUsagePercent);
  567. $mustBeFree = $storageTotalSpace - $maxUsageSpace;
  568. $usersCount = $this->getUserCount();
  569. if ($usersCount > 0) {
  570. $result = $mustBeFree / $usersCount;
  571. }
  572. return ($result);
  573. }
  574. /**
  575. * Performs export of some chunks list of some channel into selected directory
  576. *
  577. * @param array $chunksList
  578. * @param string $channelId
  579. * @param string $directory
  580. * @param int $taskId
  581. *
  582. * @return void/string
  583. */
  584. protected function exportChunksList($chunksList, $channelId, $directory, $taskId) {
  585. $result = '';
  586. $exportProcess = new StarDust(self::PID_EXPORT . $channelId);
  587. if ($exportProcess->notRunning()) {
  588. $exportProcess->start();
  589. $allChannels = $this->cameras->getAllCamerasChannels();
  590. $cameraId = $allChannels[$channelId];
  591. log_register('EXPORT STARTED [' . $taskId . '] CAMERA [' . $cameraId . '] CHANNEL `' . $channelId . '`');
  592. if (!empty($chunksList)) {
  593. $firstTs = 0;
  594. $lastTs = 0;
  595. $exportListData = '';
  596. $exportListPath = Storages::PATH_HOWL . $channelId . '_' . zb_rand_string(8) . self::EXPORTLIST_MASK;
  597. //building concat list here
  598. foreach ($chunksList as $eachTimeStamp => $eachChunk) {
  599. if (file_exists($eachChunk)) {
  600. if (!$firstTs) {
  601. $firstTs = $eachTimeStamp;
  602. }
  603. $lastTs = $eachTimeStamp;
  604. $exportListData .= "file '" . $eachChunk . "'" . PHP_EOL;
  605. }
  606. }
  607. //saving export list
  608. file_put_contents($exportListPath, $exportListData);
  609. //record file name
  610. $dateFmt = "Y-m-d-H-i-s";
  611. $recordFileName = date($dateFmt, $firstTs) . '_' . date($dateFmt, $lastTs) . '_' . $cameraId . self::RECORDS_EXT;
  612. $fullRecordFilePath = $directory . $recordFileName;
  613. if (!file_exists($fullRecordFilePath)) {
  614. $command = $this->ffmpgPath . ' -loglevel error -f concat -safe 0 -i ' . $exportListPath . ' -c copy ' . $fullRecordFilePath;
  615. shell_exec($command);
  616. //mark export schedule task as done
  617. $this->scheduleDb->where('id', '=', $taskId);
  618. $this->scheduleDb->data('done', 1);
  619. $this->scheduleDb->data('finishdate', curdatetime());
  620. $this->scheduleDb->save();
  621. } else {
  622. log_register('EXPORT SKIPPED [' . $taskId . '] CAMERA [' . $cameraId . '] CHANNEL `' . $channelId . '` ALREADY EXISTS');
  623. }
  624. //cleanup export list
  625. unlink($exportListPath);
  626. } else {
  627. $result .= __('Something went wrong');
  628. }
  629. $exportProcess->stop();
  630. log_register('EXPORT FINISHED [' . $taskId . '] CAMERA [' . $cameraId . '] CHANNEL `' . $channelId . '`');
  631. } else {
  632. $result .= __('Export process already running');
  633. }
  634. return ($result);
  635. }
  636. /**
  637. * Schedules future export operation
  638. *
  639. * @param string $userLogin
  640. * @param string $channelId
  641. * @param string $dateFrom
  642. * @param string $dateTo
  643. * @param int $sizeForecast
  644. *
  645. * @return void/string
  646. */
  647. public function scheduleExportTask($userLogin, $channelId, $dateFrom, $dateTo, $sizeForecast) {
  648. $result = '';
  649. $userLoginF = ubRouting::filters($userLogin, 'mres');
  650. $dateF = curdatetime();
  651. $channelIdF = ubRouting::filters($channelId, 'mres');
  652. $dateFromF = ubRouting::filters($dateFrom, 'mres');
  653. $dateToF = ubRouting::filters($dateTo, 'mres');
  654. $sizeForecastF = ubRouting::filters($sizeForecast, 'int');
  655. $allChannels = $this->cameras->getAllCamerasChannels();
  656. $cameraId = $allChannels[$channelId];
  657. if ($userLoginF and $channelId and $dateFrom and $dateTo and $cameraId) {
  658. $this->scheduleDb->data('date', $dateF);
  659. $this->scheduleDb->data('user', $userLogin);
  660. $this->scheduleDb->data('channel', $channelIdF);
  661. $this->scheduleDb->data('datetimefrom', $dateFromF);
  662. $this->scheduleDb->data('datetimeto', $dateToF);
  663. $this->scheduleDb->data('sizeforecast', $sizeForecastF);
  664. $this->scheduleDb->data('done', 0);
  665. $this->scheduleDb->create();
  666. $newSchedId = $this->scheduleDb->getLastId();
  667. log_register('EXPORT SCHEDULED [' . $newSchedId . '] CAMERA [' . $cameraId . '] CHANNEL `' . $channelId . '` FROM `' . $dateFrom . '` TO `' . $dateTo . '`');
  668. } else {
  669. $result .= __('Something went wrong');
  670. }
  671. return ($result);
  672. }
  673. /**
  674. * Performs processin of all scheduled exports tasks
  675. *
  676. * @return void
  677. */
  678. public function scheduleRun() {
  679. $this->scheduleDb->where('done', '=', 0);
  680. $allScheduledTasks = $this->scheduleDb->getAll();
  681. if (!empty($allScheduledTasks)) {
  682. $allCameraChannels = $this->cameras->getAllCamerasChannels();
  683. foreach ($allScheduledTasks as $io => $each) {
  684. $userLogin = $each['user'];
  685. $userRecordingsDir = $this->prepareRecordingsDir($userLogin);
  686. $channelId = $each['channel'];
  687. $cameraId = $allCameraChannels[$channelId];
  688. $cameraData = $this->allCamerasData[$cameraId];
  689. $storageId = $cameraData['STORAGE']['id'];
  690. $allChannelChunks = $this->storages->getChannelChunks($storageId, $channelId);
  691. $dateTimeFromTs = strtotime($each['datetimefrom']);
  692. $dateTimeToTs = strtotime($each['datetimeto']);
  693. $chunksInRange = $this->storages->filterChunksTimeRange($allChannelChunks, $dateTimeFromTs, $dateTimeToTs);
  694. if ($chunksInRange and $userRecordingsDir) {
  695. $this->exportChunksList($chunksInRange, $channelId, $userRecordingsDir, $each['id']);
  696. }
  697. }
  698. }
  699. }
  700. /**
  701. * Returns expected scheduled records export size
  702. *
  703. * @param string $userLogin
  704. *
  705. * @return int
  706. */
  707. protected function scheduleGetForecastSize($userLogin) {
  708. $result = 0;
  709. $userLogin = ubRouting::filters($userLogin, 'mres');
  710. $this->scheduleDb->where('user', '=', $userLogin);
  711. $this->scheduleDb->where('done', '=', 0);
  712. $rawResult = $this->scheduleDb->getAll();
  713. if (!empty($rawResult)) {
  714. foreach ($rawResult as $io => $each) {
  715. $result += $each['sizeforecast'];
  716. }
  717. }
  718. return ($result);
  719. }
  720. /**
  721. * Performs export of some channels records into single file
  722. *
  723. * @param string $channelId
  724. * @param string $date
  725. * @param string $timeFrom
  726. * @param string $timeTo
  727. *
  728. * @return void/string on error
  729. */
  730. public function requestExport($channelId, $date, $timeFrom, $timeTo) {
  731. $result = '';
  732. $userRecordingsDir = $this->prepareRecordingsDir(); //anyway we need this
  733. $channelId = ubRouting::filters($channelId, 'mres');
  734. $date = ubRouting::filters($date, 'mres');
  735. $timeFrom = ubRouting::filters($timeFrom, 'mres');
  736. $timeTo = ubRouting::filters($timeTo, 'mres');
  737. $fullDateFrom = strtotime($date . $timeFrom . ':00');
  738. $fullDateTo = strtotime($date . $timeTo . ':59');
  739. $allCameraChannels = $this->cameras->getAllCamerasChannels();
  740. //TODO: here must be some per user ACL checks
  741. if (isset($allCameraChannels[$channelId])) {
  742. $cameraId = $allCameraChannels[$channelId];
  743. if (isset($this->allCamerasData[$cameraId])) {
  744. $cameraData = $this->allCamerasData[$cameraId];
  745. $storageId = $cameraData['STORAGE']['id'];
  746. $allChannelChunks = $this->storages->getChannelChunks($storageId, $channelId);
  747. if (!empty($allCameraChannels)) {
  748. if ($fullDateFrom < $fullDateTo) {
  749. $chunksInRange = $this->storages->filterChunksTimeRange($allChannelChunks, $fullDateFrom, $fullDateTo);
  750. if (!empty($chunksInRange)) {
  751. $chunksSize = $this->storages->getChunksSize($chunksInRange); //total chunks size
  752. $usedSpace = $this->getUserUsedSpace($userRecordingsDir); //space used by user
  753. $maxSpace = $this->getUserMaxSpace(); //max of reserved space for each user
  754. $scheduleForecast = $this->scheduleGetForecastSize($this->myLogin); //already scheduled tasks forecast
  755. $usageForecast = $usedSpace + $chunksSize + $scheduleForecast; //how much space will be with current export?
  756. //checking is some of user space left?
  757. if ($usageForecast <= $maxSpace) {
  758. $schedDateFrom = date("Y-m-d H:i:s", $fullDateFrom);
  759. $schedDateTo = date("Y-m-d H:i:s", $fullDateTo);
  760. //creating export schedule
  761. $result .= $this->scheduleExportTask($this->myLogin, $channelId, $schedDateFrom, $schedDateTo, $chunksSize);
  762. //force schedule background run?
  763. if ($this->scheduleForceFlag) {
  764. if (empty($result)) {
  765. if ($this->scheduleProcess->notRunning()) {
  766. $this->scheduleProcess->runBackgroundProcess(self::WRAPPER . ' "exportsched"', 0);
  767. log_register('EXPORT SCHEDULE FORCED');
  768. } else {
  769. log_register('EXPORT SCHEDULE ALREADY RUNNING');
  770. }
  771. }
  772. }
  773. } else {
  774. $result .= __('There is not enough space reserved for exporting your records');
  775. }
  776. } else {
  777. $result .= __('No records in archive for this time range');
  778. }
  779. } else {
  780. $result .= __('Wrong time range');
  781. }
  782. } else {
  783. $result .= __('Nothing to export');
  784. }
  785. } else {
  786. $result .= __('Camera') . ' [' . $cameraId . '] ' . __('not exists');
  787. }
  788. } else {
  789. $result .= __('Camera') . ' ' . __('with channel') . ' `' . $channelId . '` ' . __('not exists');
  790. }
  791. return ($result);
  792. }
  793. /**
  794. * Parses exported file name
  795. *
  796. * @param string $fileName
  797. *
  798. * @return array
  799. */
  800. public function parseRecordFileName($fileName) {
  801. $result = array();
  802. $cleanName = str_replace(self::RECORDS_EXT, '', $fileName);
  803. $explodedName = explode('_', $cleanName);
  804. $rawFrom = explode('-', $explodedName[0]);
  805. $from = $rawFrom[0] . '-' . $rawFrom[1] . '-' . $rawFrom[2] . ' ' . $rawFrom[3] . ':' . $rawFrom[4] . ':' . $rawFrom[5];
  806. $rawTo = explode('-', $explodedName[1]);
  807. $to = $rawTo[0] . '-' . $rawTo[1] . '-' . $rawTo[2] . ' ' . $rawTo[3] . ':' . $rawTo[4] . ':' . $rawTo[5];
  808. $result['from'] = $from;
  809. $result['to'] = $to;
  810. $result['cameraid'] = $explodedName[2];
  811. $result['marker'] = @$explodedName[3];
  812. return ($result);
  813. }
  814. /**
  815. * Renders recording file deletion dialog
  816. *
  817. * @param string $fileName
  818. *
  819. * @return string
  820. */
  821. protected function renderRecDelDialog($fileName) {
  822. $result = '';
  823. $currentModule = ubRouting::get('module');
  824. if (!empty($currentModule)) {
  825. if ($currentModule == 'export') {
  826. $channelId = ubRouting::get(self::ROUTE_CHANNEL, 'gigasafe');
  827. $deleteUrl = self::URL_ME . '&' . self::ROUTE_CHANNEL . '=' . $channelId . '&' . self::ROUTE_DELETE . '=' . $fileName;
  828. $cancelUrl = self::URL_ME . '&' . self::ROUTE_CHANNEL . '=' . $channelId;
  829. $label = wf_tag('center') . wf_img('skins/trash-bin.png') . wf_tag('center', true);
  830. $label .= wf_delimiter(0);
  831. $label .= $this->messages->getDeleteAlert();
  832. $label .= wf_delimiter(0);
  833. $result .= wf_ConfirmDialog($deleteUrl, web_delete_icon(), $label, '', $cancelUrl, __('Delete') . ' ' . __('Recording') . '?');
  834. }
  835. if ($currentModule == 'records') {
  836. $deleteUrl = self::URL_RECORDS . '&' . self::ROUTE_DELETE . '=' . $fileName;
  837. $cancelUrl = self::URL_RECORDS;
  838. $label = wf_tag('center') . wf_img('skins/trash-bin.png') . wf_tag('center', true);
  839. $label .= wf_delimiter(0);
  840. $label .= $this->messages->getDeleteAlert();
  841. $label .= wf_delimiter(0);
  842. $result .= wf_ConfirmDialog($deleteUrl, web_delete_icon(), $label, '', $cancelUrl, __('Delete') . ' ' . __('Recording') . '?');
  843. }
  844. }
  845. return ($result);
  846. }
  847. /**
  848. * Returns all current user scheduler tasks done, undone or all
  849. *
  850. * @param string $state - all/done/undone
  851. *
  852. * @return array
  853. */
  854. protected function scheduleGetMyTasks($state = 'all') {
  855. $result = array();
  856. $this->scheduleDb->where('user', '=', $this->myLogin);
  857. switch ($state) {
  858. case 'done':
  859. $this->scheduleDb->where('done', '=', 1);
  860. break;
  861. case 'undone':
  862. $this->scheduleDb->where('done', '=', 0);
  863. break;
  864. }
  865. $result = $this->scheduleDb->getAll();
  866. return ($result);
  867. }
  868. /**
  869. * Renders scheduled recording exports tasks for current user
  870. *
  871. * @return string
  872. */
  873. public function renderScheduledExports() {
  874. $result = '';
  875. $allUndoneTasks = $this->scheduleGetMyTasks('undone');
  876. if (!empty($allUndoneTasks)) {
  877. $starDust = new StarDust();
  878. $allExportProcesses = $starDust->getAllStates();
  879. $cells = wf_TableCell(__('Camera'));
  880. $cells .= wf_TableCell(__('Time') . ' ' . __('from'));
  881. $cells .= wf_TableCell(__('Time') . ' ' . __('to'));
  882. $cells .= wf_TableCell(__('Size forecast'));
  883. $rows = wf_TableRowStyled($cells, 'row1');
  884. foreach ($allUndoneTasks as $io => $each) {
  885. $channelPid = self::PID_EXPORT . $each['channel'];
  886. $runningLabel = '';
  887. if (isset($allExportProcesses[$channelPid])) {
  888. if (!$allExportProcesses[$channelPid]['finished']) {
  889. $runningLabel = self::LABEL_RUNNING;
  890. }
  891. }
  892. $cells = wf_TableCell($this->cameras->getCameraComment($each['channel']) . $runningLabel);
  893. $cells .= wf_TableCell($each['datetimefrom']);
  894. $cells .= wf_TableCell($each['datetimeto']);
  895. $cells .= wf_TableCell(wr_convertSize($each['sizeforecast']), '', '', 'sorttable_customkey="' . $each['sizeforecast'] . '"');
  896. $rows .= wf_TableRowStyled($cells, 'row5');
  897. }
  898. $result .= wf_TableBody($rows, '100%', 0, 'resp-table sortable');
  899. }
  900. return ($result);
  901. }
  902. /**
  903. * Renders howl player for previously generated playlist
  904. *
  905. * @param string $filePath - full file path
  906. * @param string $width - width in px or %
  907. * @param bool $autoPlay - start playback right now?
  908. * @param string $playerId - must be equal to channel name to access playlist in DOM
  909. *
  910. * @return string
  911. */
  912. protected function renderRecordPlayer($filePath, $width = '600px', $autoPlay = false, $playerId = '') {
  913. $player = new Player($width, $autoPlay);
  914. $result = $player->renderSinglePlayer($filePath, $playerId);
  915. return ($result);
  916. }
  917. /**
  918. * Renders recording preview with web-player
  919. *
  920. * @param string $fileName
  921. *
  922. * @return string
  923. */
  924. public function renderRecordPreview($fileName) {
  925. $result = '';
  926. $webPlayer = '';
  927. $controls = '';
  928. $filePath = '';
  929. $userRecordingsDir = $this->prepareRecordingsDir();
  930. if (ubRouting::checkGet(self::ROUTE_BACK_EXPORT)) {
  931. //back to channel export interface
  932. $controls .= wf_BackLink(self::URL_ME . '&' . self::ROUTE_CHANNEL . '=' . ubRouting::get(self::ROUTE_BACK_EXPORT)) . ' ';
  933. } else {
  934. //just back to records list
  935. $controls .= wf_BackLink(self::URL_RECORDS) . ' ';
  936. }
  937. if (!empty($fileName)) {
  938. @$fileName = base64_decode($fileName);
  939. if (!empty($fileName)) {
  940. $filePath .= $userRecordingsDir . $fileName;
  941. }
  942. if ($filePath and file_exists($filePath)) {
  943. $webPlayer .= wf_tag('div', false, 'recplayercontainer');
  944. $webPlayer .= $this->renderRecordPlayer($filePath, '100%', true, $filePath);
  945. $webPlayer .= wf_tag('div', true);
  946. $controls .= wf_Link($filePath, web_icon_download() . ' ' . __('Download'), false, 'ubButton');
  947. } else {
  948. $result .= $this->messages->getStyledMessage(__('File not exists'), 'error');
  949. }
  950. }
  951. $result .= $webPlayer;
  952. $result .= wf_delimiter(0);
  953. $result .= $controls;
  954. return ($result);
  955. }
  956. /**
  957. * Renders motion detection filter schedule form
  958. *
  959. * @param string $fileName
  960. *
  961. * @return string
  962. */
  963. public function renderMoDetScheduleForm($fileName) {
  964. $result = '';
  965. $fileNameEnc = base64_encode($fileName);
  966. $sensParams = array(
  967. 'low' => __('Low'),
  968. 'normal' => __('Normal'),
  969. 'high' => __('High'),
  970. );
  971. $timeScaleParams = array(
  972. 'orig' => __('Original'),
  973. 'slow' => __('Slow'),
  974. 'fast' => __('Fast'),
  975. 'sonic' => __('Sonic'),
  976. );
  977. $result .= wf_tag('center');
  978. $hint = __('With this tool, you can filter out only scenes with motion and save them as a new recording');
  979. $result .= wf_img('skins/motion_big.png', $hint);
  980. $result .= wf_tag('center', true);
  981. $result .= wf_delimiter(0);
  982. $inputs = wf_HiddenInput(self::PROUTE_MODET_RUN, $fileNameEnc);
  983. $inputs .= wf_Selector(self::PROUTE_MODET_SENS, $sensParams, __('Sensitivity'), 'normal', true) . ' ';
  984. $inputs .= wf_Selector(self::PROUTE_MODET_TIMESCALE, $timeScaleParams, __('Time scale'), 'fast', true) . ' ';
  985. $inputs .= wf_delimiter(0);
  986. $inputs .= wf_tag('center');
  987. $inputs .= wf_SubmitClassed(__('Run motion filtering'), 'confirmagree', '', __('Run motion filtering')) . ' ';
  988. $inputs .= wf_Link(self::URL_RECORDS, __('Cancel'), false, 'confirmcancel');
  989. $inputs .= wf_tag('center', true);
  990. $result .= wf_Form('', 'POST', $inputs, 'glamour');
  991. return ($result);
  992. }
  993. /**
  994. * Returns motion detection threshold value by its text alias
  995. *
  996. * @param string $sens
  997. *
  998. * @return int
  999. */
  1000. public function getMoDetParamSensitivity($sens = '') {
  1001. $result = 2;
  1002. if (!empty($sens)) {
  1003. switch ($sens) {
  1004. case 'low':
  1005. $result = 4;
  1006. break;
  1007. case 'normal':
  1008. $result = 2;
  1009. break;
  1010. case 'high':
  1011. $result = 1;
  1012. break;
  1013. }
  1014. }
  1015. return ($result);
  1016. }
  1017. /**
  1018. * Returns motion detection timescale value by its text alias
  1019. *
  1020. * @param string $tScale
  1021. *
  1022. * @return int
  1023. */
  1024. public function getMoDetParamTimeScale($tScale = '') {
  1025. $result = 15;
  1026. if (!empty($tScale)) {
  1027. switch ($tScale) {
  1028. case 'orig':
  1029. $result = 1;
  1030. break;
  1031. case 'slow':
  1032. $result = 10;
  1033. break;
  1034. case 'fast':
  1035. $result = 15;
  1036. break;
  1037. case 'sonic':
  1038. $result = 25;
  1039. break;
  1040. }
  1041. }
  1042. return ($result);
  1043. }
  1044. /**
  1045. * Checks for reserved space availability to perform motion filtering depend on source file size
  1046. *
  1047. * @param string $fileNameEnc
  1048. *
  1049. * @return bool
  1050. */
  1051. public function isMoDetSpaceAvailable($fileNameEnc) {
  1052. $result = false;
  1053. $fileName = @base64_decode($fileNameEnc);
  1054. $userRecordingsDir = $this->prepareRecordingsDir();
  1055. if ($fileName) {
  1056. $filePath = $userRecordingsDir . $fileName;
  1057. if (file_exists($filePath)) {
  1058. $maxUserSpace = $this->getUserMaxSpace();
  1059. $usedSpaceByMe = $this->getUserUsedSpace($userRecordingsDir);
  1060. $moDetForecast = filesize($filePath); //we hope that the filtered file will not be larger
  1061. $spaceLeft = $maxUserSpace - $usedSpaceByMe;
  1062. $spaceFreePredict = $spaceLeft - $moDetForecast;
  1063. if ($spaceFreePredict > 0) {
  1064. $result = true;
  1065. }
  1066. }
  1067. }
  1068. return ($result);
  1069. }
  1070. /**
  1071. * Returns list of available records
  1072. *
  1073. * @param string $channelId
  1074. *
  1075. * @return string
  1076. */
  1077. public function renderAvailableRecords($channelId = '') {
  1078. $result = '';
  1079. $sysProc = new SysProc();
  1080. $moDet = new MoDet();
  1081. $userRecordingsDir = $this->prepareRecordingsDir();
  1082. $recordsExtFilter = '*' . self::RECORDS_EXT;
  1083. $allRecords = rcms_scandir($userRecordingsDir, $recordsExtFilter);
  1084. $moDetFlag = (@$this->altCfg[$moDet::OPTION_ENABLE]) ? true : false;
  1085. $processingFilesCount = 0;
  1086. //channel filter applied?
  1087. if ($channelId) {
  1088. if (!empty($allRecords)) {
  1089. $cameraId = $this->cameras->getCameraIdByChannel($channelId);
  1090. $filteredRecordMask = '_' . $cameraId . self::RECORDS_EXT;
  1091. foreach ($allRecords as $io => $each) {
  1092. if (!ispos($each, $filteredRecordMask)) {
  1093. unset($allRecords[$io]);
  1094. }
  1095. }
  1096. }
  1097. }
  1098. if (!empty($allRecords)) {
  1099. $cells = wf_TableCell(__('Camera'));
  1100. $cells .= wf_TableCell(__('Date'));
  1101. $cells .= wf_TableCell(__('Time') . ' ' . __('from'));
  1102. $cells .= wf_TableCell(__('Time') . ' ' . __('to'));
  1103. $cells .= wf_TableCell(__('Size'), '10%');
  1104. $cells .= wf_TableCell(__('Actions'));
  1105. $rows = wf_TableRow($cells, 'row1');
  1106. foreach ($allRecords as $io => $eachFile) {
  1107. $actLinks = '';
  1108. $moControls = '';
  1109. $fileNameParts = $this->parseRecordFileName($eachFile);
  1110. $recordSize = filesize($userRecordingsDir . $eachFile);
  1111. $recordSizeLabel = wr_convertSize($recordSize);
  1112. $fileName = $eachFile;
  1113. $fileUrl = $userRecordingsDir . $eachFile;
  1114. $previewUrl = self::URL_RECORDS . '&' . self::ROUTE_PREVIEW . '=' . base64_encode($fileName);
  1115. $cameraComment = $this->cameras->getCameraCommentById($fileNameParts['cameraid']);
  1116. $recDate = date("Y-m-d", strtotime($fileNameParts['from']));
  1117. $recTimeFrom = date("H:i:s", strtotime($fileNameParts['from']));
  1118. $recTimeTo = date("H:i:s", strtotime($fileNameParts['to']));
  1119. $fileInUseFlag = $sysProc->isFileInUse($fileName);
  1120. $fileMarker = $fileNameParts['marker'];
  1121. if ($channelId) {
  1122. $previewUrl .= '&' . self::ROUTE_BACK_EXPORT . '=' . $channelId;
  1123. }
  1124. if (empty($channelId)) {
  1125. if ($fileMarker == $moDet::FILTERED_MARK) {
  1126. $cameraComment .= ' ' . wf_img('skins/motion_filtered.png', __('Motion filtered'));
  1127. } else {
  1128. if ($moDetFlag) {
  1129. if (cfr('MOTION')) {
  1130. $modetForm = $this->renderMoDetScheduleForm($fileName);
  1131. $moControls .= wf_modalAuto(wf_img('skins/motion.png', __('Motion')), __('Motion filtering'), $modetForm);
  1132. }
  1133. }
  1134. }
  1135. }
  1136. $actLinks .= wf_Link($previewUrl, wf_img('skins/icon_play_small.png', __('Show'))) . ' ';
  1137. $actLinks .= $moControls;
  1138. $actLinks .= wf_Link($fileUrl, web_icon_download()) . ' ';
  1139. $actLinks .= $this->renderRecDelDialog($eachFile) . ' ';
  1140. //locking all operations if some of files in processing now
  1141. if ($fileInUseFlag) {
  1142. $actLinks = wf_img_sized('skins/ajaxloader.gif', __('Record') . ' ' . __('is currently being processed'), 110);
  1143. $processingFilesCount++;
  1144. }
  1145. $cells = wf_TableCell($cameraComment);
  1146. $cells .= wf_TableCell($recDate);
  1147. $cells .= wf_TableCell($recTimeFrom);
  1148. $cells .= wf_TableCell($recTimeTo);
  1149. $cells .= wf_TableCell($recordSizeLabel, '', '', 'sorttable_customkey="' . $recordSize . '"');
  1150. $cells .= wf_TableCell($actLinks);
  1151. $rows .= wf_TableRow($cells, 'row5');
  1152. }
  1153. $result .= wf_TableBody($rows, '100%', 0, 'resp-table sortable');
  1154. //catching auto-update requests
  1155. if (!$channelId) {
  1156. if (ubRouting::checkGet(self::ROUTE_REFRESH)) {
  1157. if ($processingFilesCount == 0) {
  1158. ubRouting::nav(self::URL_RECORDS);
  1159. }
  1160. } else {
  1161. if ($processingFilesCount > 0) {
  1162. ubRouting::nav(self::URL_RECORDS . '&' . self::ROUTE_REFRESH . '=true');
  1163. }
  1164. }
  1165. }
  1166. } else {
  1167. $noRecordsNotice = __('You have no saved records yet');
  1168. if ($channelId) {
  1169. $noRecordsNotice .= ' ' . __('for this camera');
  1170. }
  1171. $result .= $this->messages->getStyledMessage($noRecordsNotice, 'info');
  1172. }
  1173. $maxUserSpace = $this->getUserMaxSpace();
  1174. $usedSpaceByMe = $this->getUserUsedSpace($userRecordingsDir);
  1175. $scheduledExportsForecast = $this->scheduleGetForecastSize($this->myLogin);
  1176. $spaceFree = $maxUserSpace - $usedSpaceByMe - $scheduledExportsForecast;
  1177. if ($spaceFree > 0) {
  1178. $spaceLabel = wr_convertSize($spaceFree);
  1179. $notificationType = 'info';
  1180. if ($usedSpaceByMe == 0) {
  1181. $notificationType = 'success';
  1182. }
  1183. } else {
  1184. $spaceLabel = __('Exhausted') . ' :(';
  1185. $notificationType = 'warning';
  1186. }
  1187. $result .= $this->messages->getStyledMessage(__('Free space for saving your records') . ': ' . $spaceLabel, $notificationType);
  1188. return ($result);
  1189. }
  1190. /**
  1191. * Deletes existing recording file
  1192. *
  1193. * @param string $fileName
  1194. * @param string $userLogin
  1195. *
  1196. * @return void/string
  1197. */
  1198. public function deleteRecording($fileName, $userLogin = '') {
  1199. $result = '';
  1200. $fileName = ubRouting::filters($fileName, 'mres');
  1201. $sysProc = new SysProc();
  1202. if (empty($userLogin)) {
  1203. $userLogin = $this->myLogin;
  1204. }
  1205. if (!empty($fileName)) {
  1206. if (!$sysProc->isFileInUse($fileName)) {
  1207. $userRecordingsDir = $this->prepareRecordingsDir($userLogin);
  1208. if (!empty($userRecordingsDir)) {
  1209. if (file_exists($userRecordingsDir . $fileName)) {
  1210. if (is_writable($userRecordingsDir . $fileName)) {
  1211. unlink($userRecordingsDir . $fileName);
  1212. log_register('EXPORT DELETE `' . $fileName . '`');
  1213. } else {
  1214. $result .= __('Recording') . ' ' . __('is not writable');
  1215. log_register('EXPORT DELETE FAIL `' . $fileName . '` NOT WRITABLE');
  1216. }
  1217. } else {
  1218. $result .= __('Recording') . ' ' . __('not exists');
  1219. log_register('EXPORT DELETE FAIL `' . $fileName . '` NOT EXISTS');
  1220. }
  1221. }
  1222. } else {
  1223. $result .= __('Recording') . ' ' . __('is currently being processed');
  1224. log_register('EXPORT DELETE FAIL `' . $fileName . '` IN PROCESSING');
  1225. }
  1226. } else {
  1227. $result .= __('Recording') . ' ' . __('is empty');
  1228. log_register('EXPORT DELETE FAIL FILENAME EMPTY');
  1229. }
  1230. return ($result);
  1231. }
  1232. /**
  1233. * Returns some channel human-readable comment
  1234. *
  1235. * @param string $channelId
  1236. *
  1237. * @return string
  1238. */
  1239. public function getCameraComment($channelId) {
  1240. $result = '';
  1241. if ($channelId) {
  1242. $result .= $this->cameras->getCameraComment($channelId);
  1243. }
  1244. return ($result);
  1245. }
  1246. /**
  1247. * Just renders successfull export notification and confirmation
  1248. *
  1249. * @param string $channelId
  1250. *
  1251. * @return string
  1252. */
  1253. public function renderExportScheduledNotify($channelId) {
  1254. $result = '';
  1255. $notification = '';
  1256. $notification .= wf_tag('center') . wf_img('skins/checked.png') . wf_tag('center', true);
  1257. $notification .= wf_delimiter(0);
  1258. $notification .= __('Saving recording from the camera is scheduled for you') . '.';
  1259. $notification .= wf_delimiter(0);
  1260. $notification .= __('This will start in a few minutes and may take a while depending on the length of the recording') . '.';
  1261. $notification .= wf_delimiter();
  1262. $notification .= wf_tag('center') . wf_Link(self::URL_ME . '&' . self::ROUTE_CHANNEL . '=' . $channelId, __('Got it') . '!', true, 'confirmagree') . wf_tag('center', true);
  1263. $result .= wf_modalOpenedAuto(__('Success'), $notification);
  1264. return ($result);
  1265. }
  1266. }