api.storages.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. <?php
  2. /**
  3. * Storages management class implementation
  4. */
  5. class Storages {
  6. /**
  7. * Storages database abstraction layer placeholder
  8. *
  9. * @var object
  10. */
  11. protected $storagesDb = '';
  12. /**
  13. * Contains all available storages as id=>storageData
  14. *
  15. * @var array
  16. */
  17. protected $allStorages = array();
  18. /**
  19. * Contains system messages helper instance
  20. *
  21. * @var object
  22. */
  23. protected $messages = '';
  24. /**
  25. * Some predefined stuff here
  26. */
  27. const PATH_HOWL = 'howl/';
  28. const PROUTE_PATH = 'newstoragepath';
  29. const PROUTE_NAME = 'newstoragename';
  30. const ROUTE_DEL = 'deletestorageid';
  31. const PROUTE_ED_STORAGE = 'editstorageid';
  32. const PROUTE_ED_NAME = 'editstoragename';
  33. const URL_ME = '?module=storages';
  34. const DATA_TABLE = 'storages';
  35. public function __construct() {
  36. $this->initMessages();
  37. $this->initStoragesDb();
  38. $this->loadStorages();
  39. }
  40. /**
  41. * Inits system messages helper
  42. *
  43. * @return void
  44. */
  45. protected function initMessages() {
  46. $this->messages = new UbillingMessageHelper();
  47. }
  48. /**
  49. * Inits database abstraction layer
  50. *
  51. * @return void
  52. */
  53. protected function initStoragesDb() {
  54. $this->storagesDb = new NyanORM(self::DATA_TABLE);
  55. }
  56. /**
  57. * Loads all available storages from database
  58. *
  59. * @return void
  60. */
  61. protected function loadStorages() {
  62. $this->storagesDb->orderBy('id', 'DESC');
  63. $this->allStorages = $this->storagesDb->getAll('id');
  64. }
  65. /**
  66. * Returns storage data by its ID
  67. *
  68. * @param int $storageId
  69. *
  70. * @return array
  71. */
  72. public function getStorageData($storageId) {
  73. $result = array();
  74. if (isset($this->allStorages[$storageId])) {
  75. $result = $this->allStorages[$storageId];
  76. }
  77. return ($result);
  78. }
  79. /**
  80. * Returns all existing storages names as id=>name
  81. *
  82. * @return array
  83. */
  84. public function getAllStorageNames() {
  85. $result = array();
  86. if (!empty($this->allStorages)) {
  87. foreach ($this->allStorages as $io => $each) {
  88. $result[$each['id']] = $each['name'];
  89. }
  90. }
  91. return ($result);
  92. }
  93. /**
  94. * Returns all existing storages names as id=>name
  95. *
  96. * @return array
  97. */
  98. public function getAllStorageNamesLocalized() {
  99. $result = array();
  100. if (!empty($this->allStorages)) {
  101. foreach ($this->allStorages as $io => $each) {
  102. $result[$each['id']] = __($each['name']);
  103. }
  104. }
  105. return ($result);
  106. }
  107. /**
  108. * Returns all storages data as id=>storageData
  109. *
  110. * @return array
  111. */
  112. public function getAllStoragesData() {
  113. return ($this->allStorages);
  114. }
  115. /**
  116. * Checks is some path not used by another storage?
  117. *
  118. * @param string $path
  119. *
  120. * @return bool
  121. */
  122. protected function isPathUnique($path) {
  123. $result = true;
  124. if (!empty($this->allStorages)) {
  125. foreach ($this->allStorages as $io => $each) {
  126. if ($each['path'] == $path) {
  127. $result = false;
  128. }
  129. }
  130. }
  131. return ($result);
  132. }
  133. /**
  134. * Creates new storage in database
  135. *
  136. * @param string $path
  137. * @param string $name
  138. *
  139. * @return void/string on error
  140. */
  141. public function create($path, $name) {
  142. $result = '';
  143. $pathF = ubRouting::filters($path, 'mres');
  144. $nameF = ubRouting::filters($name, 'mres');
  145. if (!empty($pathF) and ! empty($nameF)) {
  146. if ($this->isPathUnique($pathF)) {
  147. if (file_exists($pathF)) {
  148. if (is_dir($pathF)) {
  149. if (is_writable($pathF)) {
  150. $this->storagesDb->data('path', $pathF);
  151. $this->storagesDb->data('name', $nameF);
  152. $this->storagesDb->create();
  153. $storageId = $this->storagesDb->getLastId();
  154. log_register('STORAGE CREATE [' . $storageId . '] PATH `' . $path . '` NAME `' . $name . '`');
  155. } else {
  156. $result = __('Storage path is not writable');
  157. }
  158. } else {
  159. $result = __('Storage path is not directory');
  160. }
  161. } else {
  162. $result = __('Storage path not exists');
  163. }
  164. } else {
  165. $result = __('Another storage with such path is already exists');
  166. }
  167. } else {
  168. $result = __('Storage path or name is empty');
  169. }
  170. return ($result);
  171. }
  172. /**
  173. * Deletes some storage from database
  174. *
  175. * @param int $storageId
  176. *
  177. * @return void/string on error
  178. */
  179. public function delete($storageId) {
  180. $result = '';
  181. $storageId = ubRouting::filters($storageId, 'int');
  182. if (isset($this->allStorages[$storageId])) {
  183. if (!$this->isProtected($storageId)) {
  184. $this->storagesDb->where('id', '=', $storageId);
  185. $this->storagesDb->delete();
  186. log_register('STORAGE DELETE [' . $storageId . ']');
  187. } else {
  188. $result = __('You can not delete storage which is in usage');
  189. }
  190. } else {
  191. $result = __('No such storage') . ' [' . $storageId . ']';
  192. }
  193. return ($result);
  194. }
  195. /**
  196. * Renders storage creation form
  197. *
  198. * @return string
  199. */
  200. public function renderCreationForm() {
  201. $result = '';
  202. $inputs = wf_TextInput(self::PROUTE_PATH, __('Path'), '', false, 20);
  203. $inputs .= wf_TextInput(self::PROUTE_NAME, __('Name'), '', false, 20);
  204. $inputs .= wf_Submit(__('Create'));
  205. $result .= wf_Form('', 'POST', $inputs, 'glamour');
  206. return ($result);
  207. }
  208. /**
  209. * Checks is storage path exists, valid and writtable?
  210. *
  211. * @param string $path
  212. *
  213. * @return bool
  214. */
  215. public function checkPath($path) {
  216. $result = false;
  217. if (file_exists($path)) {
  218. if (is_dir($path)) {
  219. if (is_writable($path)) {
  220. $result = true;
  221. }
  222. }
  223. }
  224. return ($result);
  225. }
  226. /**
  227. * Renders storage editing form
  228. *
  229. * @param int $storageId
  230. *
  231. * @return string
  232. */
  233. protected function renderEditForm($storageId) {
  234. $result = '';
  235. $storageId = ubRouting::filters($storageId, 'int');
  236. if (isset($this->allStorages[$storageId])) {
  237. $inputs = wf_HiddenInput(self::PROUTE_ED_STORAGE, $storageId);
  238. $inputs .= wf_TextInput(self::PROUTE_ED_NAME, __('Name'), $this->allStorages[$storageId]['name'], false, 20) . ' ';
  239. $inputs .= wf_Submit(__('Save'));
  240. $result .= wf_Form('', 'POST', $inputs, 'glamour');
  241. }
  242. return ($result);
  243. }
  244. /**
  245. * Changes storage name in database
  246. *
  247. * @param int $storageId
  248. * @param string $storageName
  249. *
  250. * @return void
  251. */
  252. public function saveName($storageId, $storageName) {
  253. $storageId = ubRouting::filters($storageId, 'int');
  254. $storageNameF = ubRouting::filters($storageName, 'mres');
  255. if ($storageId and $storageNameF) {
  256. if (isset($this->allStorages[$storageId])) {
  257. $storagePath = $this->allStorages[$storageId]['path'];
  258. $this->storagesDb->where('id', '=', $storageId);
  259. $this->storagesDb->data('name', $storageNameF);
  260. $this->storagesDb->save();
  261. log_register('STORAGE EDIT [' . $storageId . '] PATH `' . $storagePath . '` NAME `' . $storageName . '`');
  262. }
  263. }
  264. }
  265. /**
  266. * Renders available storages list
  267. *
  268. * @return string
  269. */
  270. public function renderList() {
  271. $hwInfo = new SystemHwInfo();
  272. $result = '';
  273. if (!empty($this->allStorages)) {
  274. $allStoragesCams = $this->getAllStoragesCamerasCount();
  275. $cells = wf_TableCell(__('ID'));
  276. $cells .= wf_TableCell(__('Path'));
  277. $cells .= wf_TableCell(__('Name'));
  278. $cells .= wf_TableCell(__('State'));
  279. $cells .= wf_TableCell(__('Cameras'));
  280. $cells .= wf_TableCell(__('Capacity'));
  281. $cells .= wf_TableCell(__('Free'));
  282. $cells .= wf_TableCell(__('Actions'));
  283. $rows = wf_TableRow($cells, 'row1');
  284. foreach ($this->allStorages as $io => $each) {
  285. $storageSizeLabel = '-';
  286. $storageFreeLabel = '-';
  287. $cells = wf_TableCell($each['id']);
  288. $cells .= wf_TableCell($each['path']);
  289. $cells .= wf_TableCell(__($each['name']));
  290. $storageState = ($this->checkPath($each['path'])) ? true : false;
  291. $stateIcon = web_bool_led($storageState);
  292. if ($storageState) {
  293. $diskStats = $hwInfo->getDiskStat($each['path']);
  294. $storageSizeLabel = wr_convertSize( $diskStats['total']);
  295. $storageFreeLabel = wr_convertSize($diskStats['free']);
  296. }
  297. $cells .= wf_TableCell($stateIcon);
  298. $cells .= wf_TableCell($allStoragesCams[$each['id']]);
  299. $cells .= wf_TableCell($storageSizeLabel);
  300. $cells .= wf_TableCell($storageFreeLabel);
  301. $actControls = wf_JSAlert(self::URL_ME . '&' . self::ROUTE_DEL . '=' . $each['id'], web_delete_icon(), $this->messages->getDeleteAlert());
  302. $actControls .= wf_modalAuto(web_edit_icon(), __('Edit') . ' `' . __($each['name']) . '`', $this->renderEditForm($each['id']));
  303. $cells .= wf_TableCell($actControls);
  304. $rows .= wf_TableRow($cells, 'row5');
  305. }
  306. $result .= wf_TableBody($rows, '100%', 0, 'sortable resp-table');
  307. } else {
  308. $result .= $this->messages->getStyledMessage(__('Nothing to show'), 'warning');
  309. }
  310. return ($result);
  311. }
  312. /**
  313. * Returns all storages cameras counters as storeageId=>camerasCount
  314. *
  315. * @return array
  316. */
  317. protected function getAllStoragesCamerasCount() {
  318. $result = array();
  319. if (!empty($this->allStorages)) {
  320. foreach ($this->allStorages as $io => $each) {
  321. $result[$each['id']] = 0;
  322. }
  323. $camerasDb = new NyanORM(Cameras::DATA_TABLE);
  324. $camerasDb->selectable('id,storageid');
  325. $allCamerasStorages = $camerasDb->getAll();
  326. if (!empty($allCamerasStorages)) {
  327. foreach ($allCamerasStorages as $io => $eachCam) {
  328. $result[$eachCam['storageid']]++;
  329. }
  330. }
  331. }
  332. return ($result);
  333. }
  334. /**
  335. * Returns Id of least used storage
  336. *
  337. * @return int
  338. */
  339. public function getLeastUsedStorage() {
  340. $result = 0;
  341. $allStoragesCamerasCount = $this->getAllStoragesCamerasCount();
  342. if (!empty($allStoragesCamerasCount)) {
  343. $result = array_search(min($allStoragesCamerasCount), $allStoragesCamerasCount);
  344. }
  345. return ($result);
  346. }
  347. /**
  348. * Checks is some storage used by some cameras?
  349. *
  350. * @param int $storageId
  351. *
  352. * @return bool
  353. */
  354. protected function isProtected($storageId) {
  355. $result = true;
  356. $storageId = ubRouting::filters($storageId, 'int');
  357. $camerasDb = new NyanORM(Cameras::DATA_TABLE);
  358. $camerasDb->where('storageid', '=', $storageId);
  359. $camerasDb->selectable('id');
  360. $usedByCameras = $camerasDb->getAll();
  361. if (!$usedByCameras) {
  362. $result = false;
  363. }
  364. return ($result);
  365. }
  366. /**
  367. * Returns all chunks stored in some channel as timestamp=>fullPath
  368. *
  369. * @param int $storageId
  370. * @param string $channel
  371. *
  372. * @return array
  373. */
  374. public function getChannelChunks($storageId, $channel) {
  375. $result = array();
  376. $storageId = ubRouting::filters($storageId, 'int');
  377. $channel = ubRouting::filters($channel, 'mres');
  378. if ($storageId and $channel) {
  379. if (isset($this->allStorages[$storageId])) {
  380. $storagePath = $this->allStorages[$storageId]['path'];
  381. $storageLastChar = substr($storagePath, -1);
  382. if ($storageLastChar != '/') {
  383. $storagePath .= '/';
  384. }
  385. if (file_exists($storagePath)) {
  386. if (file_exists($storagePath . $channel)) {
  387. $chunksExt = Recorder::CHUNKS_EXT;
  388. $allChunksNames = scandir($storagePath . $channel);
  389. if (!empty($allChunksNames)) {
  390. foreach ($allChunksNames as $io => $eachFileName) {
  391. if ($eachFileName != '.' and $eachFileName != '..') {
  392. $cleanChunkTimeStamp = str_replace($chunksExt, '', $eachFileName);
  393. $result[$cleanChunkTimeStamp] = $storagePath . $channel . '/' . $eachFileName;
  394. }
  395. }
  396. }
  397. }
  398. }
  399. }
  400. }
  401. return ($result);
  402. }
  403. /**
  404. * Returns all chunks stored in some channel with their allocated space as timestamp=>path/size
  405. *
  406. * @param int $storageId
  407. * @param string $channel
  408. *
  409. * @return array
  410. */
  411. public function getChunksAllocSpaces($storageId, $channel) {
  412. $result = array();
  413. $storageId = ubRouting::filters($storageId, 'int');
  414. $channel = ubRouting::filters($channel, 'mres');
  415. if ($storageId and $channel) {
  416. $channelChunks = $this->getChannelChunks($storageId, $channel);
  417. if (!empty($channelChunks)) {
  418. foreach ($channelChunks as $chunkTimeStamp => $eachChunkPath) {
  419. $result[$chunkTimeStamp]['path'] = $eachChunkPath;
  420. $result[$chunkTimeStamp]['size'] = filesize($eachChunkPath);
  421. }
  422. }
  423. }
  424. return ($result);
  425. }
  426. /**
  427. * Returns total size of all chunks in getChunksAllocSpaces data
  428. *
  429. * @param array $chunksListAlloc
  430. *
  431. * @return int
  432. */
  433. public function calcChunksListSize($chunksListAlloc) {
  434. $result = 0;
  435. if (!empty($chunksListAlloc)) {
  436. foreach ($chunksListAlloc as $timeStamp => $chunksData) {
  437. $result += $chunksData['size'];
  438. }
  439. }
  440. return ($result);
  441. }
  442. /**
  443. * Returns bytes count that some channel currently stores
  444. *
  445. * @param int $storageId
  446. * @param string $channel
  447. *
  448. * @return string
  449. */
  450. public function getChannelSize($storageId, $channel) {
  451. $result = 0;
  452. $storageId = ubRouting::filters($storageId, 'int');
  453. $channel = ubRouting::filters($channel, 'mres');
  454. if ($storageId and $channel) {
  455. $channelChunks = $this->getChannelChunks($storageId, $channel);
  456. if (!empty($channelChunks)) {
  457. foreach ($channelChunks as $io => $eachChunk) {
  458. $result += filesize($eachChunk);
  459. }
  460. }
  461. }
  462. return ($result);
  463. }
  464. /**
  465. * Returns bytes count that some channel chunks list contains
  466. *
  467. * @param array $chunksList
  468. *
  469. * @return string
  470. */
  471. public function getChannelChunksSize($chunksList) {
  472. $result = 0;
  473. if (!empty($chunksList)) {
  474. foreach ($chunksList as $io => $eachChunk) {
  475. $result += filesize($eachChunk);
  476. }
  477. }
  478. return ($result);
  479. }
  480. /**
  481. * Filters some chunks array and lefts only chunks between some timestamps in range
  482. *
  483. * @param array $chunksList
  484. * @param int $timeFrom
  485. * @param int $timeTo
  486. *
  487. * @return array
  488. */
  489. public function filterChunksTimeRange($chunksList, $timeFrom, $timeTo) {
  490. $result = array();
  491. if (!empty($chunksList)) {
  492. foreach ($chunksList as $eachTimestamp => $eachChunkPath) {
  493. if (($eachTimestamp > $timeFrom) and ($eachTimestamp < $timeTo)) {
  494. $result[$eachTimestamp] = $eachChunkPath;
  495. }
  496. }
  497. }
  498. return ($result);
  499. }
  500. /**
  501. * Returns size in bytes of all chunks in list
  502. *
  503. * @param array $chunksList
  504. *
  505. * @return int
  506. */
  507. public function getChunksSize($chunksList) {
  508. $result = 0;
  509. if (!empty($chunksList)) {
  510. foreach ($chunksList as $timeStamp => $eachChunk) {
  511. if (file_exists($eachChunk)) {
  512. $result += filesize($eachChunk);
  513. }
  514. }
  515. }
  516. return ($result);
  517. }
  518. /**
  519. * Allocates path in storage for channel recording if required
  520. *
  521. * @param string $storagePath
  522. * @param string $channel
  523. *
  524. * @return string/bool
  525. */
  526. protected function allocateChannel($storagePath, $channel) {
  527. $result = false;
  528. $delimiter = '';
  529. if (!empty($storagePath) and ! empty($channel)) {
  530. if (file_exists($storagePath)) {
  531. if (is_dir($storagePath)) {
  532. if (is_writable($storagePath)) {
  533. $pathLastChar = substr($storagePath, -1);
  534. if ($pathLastChar != '/') {
  535. $delimiter = '/';
  536. }
  537. $chanDirName = $storagePath . $delimiter . $channel;
  538. $fullPath = $chanDirName . '/';
  539. $howlLink = self::PATH_HOWL . $channel;
  540. //allocate channel dir
  541. if (!file_exists($fullPath)) {
  542. //creating new directory
  543. mkdir($fullPath, 0777);
  544. chmod($fullPath, 0777);
  545. log_register('STORAGE ALLOCATED `' . $storagePath . '` CHANNEL `' . $channel . '`');
  546. }
  547. //linking to howl?
  548. if (!file_exists($howlLink)) {
  549. symlink($chanDirName, $howlLink);
  550. chmod($howlLink, 0777);
  551. log_register('STORAGE LINKED `' . $storagePath . '` HOWL `' . $channel . '`');
  552. }
  553. //seems ok?
  554. if (is_writable($fullPath)) {
  555. $result = $fullPath;
  556. }
  557. }
  558. }
  559. }
  560. }
  561. return ($result);
  562. }
  563. /**
  564. * Migrates howl symlink to new storage
  565. *
  566. * @param int $newStorageId
  567. * @param string $channel
  568. *
  569. * @return void
  570. */
  571. public function migrateChannel($newStorageId, $channel) {
  572. $newStorageId = ubRouting::filters($newStorageId, 'int');
  573. $channel = ubRouting::filters($channel, 'mres');
  574. if (isset($this->allStorages[$newStorageId])) {
  575. $storageData = $this->getStorageData($newStorageId);
  576. $storagePath = $storageData['path'];
  577. $howlLink = self::PATH_HOWL . $channel;
  578. if (file_exists($howlLink)) {
  579. //cleanin old howl link
  580. unlink($howlLink);
  581. //new storage channel alocation
  582. $this->allocateChannel($storagePath, $channel);
  583. log_register('STORAGE MIGRATED `' . $storagePath . '` HOWL `' . $channel . '`');
  584. }
  585. }
  586. }
  587. /**
  588. * Allocates path in storage for channel recording if required
  589. *
  590. * @param int $storageId
  591. * @param string $channel
  592. *
  593. * @return string/bool
  594. */
  595. public function initChannel($storageId, $channel) {
  596. $result = false;
  597. $storageId = ubRouting::filters($storageId, 'int');
  598. $channel = ubRouting::filters($channel, 'mres');
  599. if (isset($this->allStorages[$storageId])) {
  600. $storagePath = $this->allStorages[$storageId]['path'];
  601. $result = $this->allocateChannel($storagePath, $channel);
  602. }
  603. return ($result);
  604. }
  605. /**
  606. * Deletes allocated channel with all data inside
  607. *
  608. * @param int $storageId
  609. * @param string $channel
  610. *
  611. * @return void
  612. */
  613. public function flushChannel($storageId, $channel) {
  614. $storageId = ubRouting::filters($storageId, 'int');
  615. $channel = ubRouting::filters($channel, 'mres');
  616. if (isset($this->allStorages[$storageId])) {
  617. $storagePath = $this->allStorages[$storageId]['path'];
  618. $delimiter = '';
  619. if (!empty($storagePath) and ! empty($channel)) {
  620. if (file_exists($storagePath)) {
  621. if (is_dir($storagePath)) {
  622. if (is_writable($storagePath)) {
  623. $pathLastChar = substr($storagePath, -1);
  624. if ($pathLastChar != '/') {
  625. $delimiter = '/';
  626. }
  627. $fullPath = $storagePath . $delimiter . $channel;
  628. //seems ok?
  629. if (is_writable($fullPath)) {
  630. //unlink howl
  631. unlink(self::PATH_HOWL . $channel);
  632. //destroy channel dir
  633. rcms_delete_files($fullPath, true);
  634. //archive playlist cleanup
  635. $archPlaylistName = self::PATH_HOWL . $channel . Archive::PLAYLIST_MASK;
  636. if (file_exists($archPlaylistName)) {
  637. rcms_delete_files($archPlaylistName);
  638. }
  639. log_register('STORAGE FLUSH [' . $storageId . '] PATH `' . $storagePath . '` CHANNEL `' . $channel . '`');
  640. }
  641. }
  642. }
  643. }
  644. }
  645. }
  646. }
  647. }