PageRenderTime 43ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/phpRack/Package/Disc/File.php

https://github.com/positivesum/site-plugin-core
PHP | 420 lines | 312 code | 22 blank | 86 comment | 16 complexity | 23ad9c30e9c97ff38f5dbdd6f5541dde MD5 | raw file
  1. <?php
  2. /**
  3. * phpRack: Integration Testing Framework
  4. *
  5. * This source file is subject to the new BSD license that is bundled
  6. * with this package in the file LICENSE.txt. It is also available
  7. * through the world-wide-web at this URL: http://www.phprack.com/LICENSE.txt
  8. * If you did not receive a copy of the license and are unable to
  9. * obtain it through the world-wide-web, please send an email
  10. * to license@phprack.com so we can send you a copy immediately.
  11. *
  12. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  13. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  14. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  15. * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  16. * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  17. * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  18. * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  19. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  20. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  21. * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  22. * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  23. * POSSIBILITY OF SUCH DAMAGE.
  24. *
  25. * @copyright Copyright (c) phpRack.com
  26. * @version $Id: File.php 588 2010-05-25 15:11:56Z yegor256@yahoo.com $
  27. * @category phpRack
  28. */
  29. /**
  30. * @see phpRack_Package
  31. */
  32. require_once PHPRACK_PATH . '/Package.php';
  33. /**
  34. * @see phpRack_Adapters_File
  35. */
  36. require_once PHPRACK_PATH . '/Adapters/File.php';
  37. /**
  38. * File informations and content
  39. *
  40. * @package Tests
  41. */
  42. class phpRack_Package_Disc_File extends phpRack_Package
  43. {
  44. /**
  45. * Buffer used is tail function to read blocks from file end
  46. */
  47. const READ_BUFFER_SIZE = 1024;
  48. /**
  49. * Default number of lines to show
  50. */
  51. const LINES_TO_SHOW = 25;
  52. /**
  53. * Maximum number of bytes we can render, if more we will skip the rest
  54. *
  55. * @var int
  56. */
  57. protected $_maxBytesToRender = 50000;
  58. /**
  59. * Set another limit for max bytes to render
  60. *
  61. * @param int Number of bytes that is allowed for rendering
  62. * @return $this
  63. */
  64. public function setMaxBytesToRender($maxBytesToRender)
  65. {
  66. $this->_maxBytesToRender = $maxBytesToRender;
  67. return $this;
  68. }
  69. /**
  70. * Show the content of the file
  71. *
  72. * @param string File name to display
  73. * @return $this
  74. */
  75. public function cat($fileName)
  76. {
  77. $fileName = phpRack_Adapters_File::factory($fileName)->getFileName();
  78. // Check that file exists
  79. if (!$this->_isFileExists($fileName)) {
  80. return $this;
  81. }
  82. // too long/big files should not be returned
  83. if (filesize($fileName) > $this->_maxBytesToRender) {
  84. $this->_log(
  85. sprintf(
  86. "File '%s' is too big (%d bytes), we can't render its content in full",
  87. $fileName,
  88. filesize($fileName)
  89. )
  90. );
  91. return $this->tail($fileName);
  92. }
  93. $content = file_get_contents($fileName);
  94. if ($content === false) {
  95. $this->_failure("Failed file_get_contents('{$fileName}')");
  96. return $this;
  97. }
  98. $this->_log($content);
  99. return $this;
  100. }
  101. /**
  102. * Show last x lines from the file
  103. *
  104. * @param string File name
  105. * @param string How many lines to display?
  106. * @return $this
  107. */
  108. public function tail($fileName, $linesCount = self::LINES_TO_SHOW)
  109. {
  110. $fileName = phpRack_Adapters_File::factory($fileName)->getFileName();
  111. // Check that file exists
  112. if (!$this->_isFileExists($fileName)) {
  113. return $this;
  114. }
  115. // Open file and move pointer to end of file
  116. $fp = fopen($fileName, 'rb');
  117. fseek($fp, 0, SEEK_END);
  118. // Read offset of end of file
  119. $offset = ftell($fp);
  120. // set ajax option with file end offset for usage in next Ajax request
  121. $this->_result->getTest()->setAjaxOptions(
  122. array(
  123. 'data' => array('fileLastOffset' => $offset)
  124. )
  125. );
  126. $content = '';
  127. do {
  128. // Move file pointer for new read
  129. $offset = max(0, $offset - self::READ_BUFFER_SIZE);
  130. fseek($fp, $offset, SEEK_SET);
  131. $readBuffer = fread($fp, self::READ_BUFFER_SIZE);
  132. $linesCountInReadBuffer = substr_count($readBuffer, "\n");
  133. // If we have enought lines extract from last readed fragment only required lines
  134. if ($linesCountInReadBuffer >= $linesCount) {
  135. $readBuffer = implode("\n", array_slice(explode("\n", $readBuffer), -$linesCount));
  136. }
  137. // Update how many lines still need to be readed
  138. $linesCount -= $linesCountInReadBuffer;
  139. // Attach last readed lines at beggining of earlier readed fragments
  140. $content = $readBuffer . $content;
  141. if (strlen($content) > $this->_maxBytesToRender) {
  142. $this->_log(
  143. sprintf(
  144. "Content is too long already (%d bytes), we won't render any more",
  145. strlen($content)
  146. )
  147. );
  148. break;
  149. }
  150. } while ($offset > 0 && $linesCount > 0);
  151. $this->_log($content);
  152. return $this;
  153. }
  154. /**
  155. * Show last x lines from the file, and refresh it imediatelly
  156. *
  157. * @param string File name
  158. * @param string How many lines to display?
  159. * @param string How many seconds each line should be visible
  160. * @return $this
  161. * @see phpRack_Runner::run()
  162. */
  163. public function tailf($fileName, $linesCount = self::LINES_TO_SHOW, $secVisible = 5)
  164. {
  165. $fileName = phpRack_Adapters_File::factory($fileName)->getFileName();
  166. $test = $this->_result->getTest();
  167. $test->setAjaxOptions(
  168. array(
  169. 'reload' => 0.5, //500ms I think is okey for delay between requests, can be lower
  170. 'secVisible' => $secVisible,
  171. 'linesCount' => $linesCount,
  172. 'attachOutput' => true
  173. )
  174. );
  175. $options = $test->getAjaxOptions();
  176. clearstatcache();
  177. // get current file size
  178. $fileSize = @filesize($fileName);
  179. if ($fileSize === false) {
  180. $this->_failure("Failed to filesize('{$fileName}')");
  181. return $this;
  182. }
  183. // if it is first request or file was truncated, send all x last lines
  184. if (!isset($options['fileLastOffset']) || $fileSize < $options['fileLastOffset']) {
  185. $this->tail($fileName, $linesCount);
  186. return $this;
  187. }
  188. $fp = @fopen($fileName, 'rb');
  189. if (!$fp) {
  190. $this->_failure("Failed to fopen('{$fileName}')");
  191. return $this;
  192. }
  193. // get only new content since last time
  194. $content = @stream_get_contents($fp, -1, $options['fileLastOffset']);
  195. if ($content === false) {
  196. $this->_failure("Failed to stream_get_contents({$fp}/'{$fileName}', -1, {$options['fileLastOffset']})");
  197. return $this;
  198. }
  199. // save current offset
  200. $offset = @ftell($fp);
  201. if ($offset === false) {
  202. $this->_failure("Failed to ftell({$fp}/'{$fileName}')");
  203. return $this;
  204. }
  205. if (@fclose($fp) === false) {
  206. $this->_failure("Failed to fclose({$fp}/'{$fileName}')");
  207. return $this;
  208. }
  209. $this->_log($content);
  210. // set ajax option with new file end offset for usage in next Ajax request
  211. $test->setAjaxOptions(
  212. array(
  213. 'data' => array('fileLastOffset' => $offset),
  214. )
  215. );
  216. return $this;
  217. }
  218. /**
  219. * Show first x lines from the file
  220. *
  221. * @param string File name
  222. * @param string How many lines to display?
  223. * @return $this
  224. */
  225. public function head($fileName, $linesCount = self::LINES_TO_SHOW)
  226. {
  227. $fileName = phpRack_Adapters_File::factory($fileName)->getFileName();
  228. // Check that file exists
  229. if (!$this->_isFileExists($fileName)) {
  230. return $this;
  231. }
  232. $content = '';
  233. $readedLinesCount = 0;
  234. $fp = @fopen($fileName, 'rb');
  235. if ($fp === false) {
  236. $this->_failure("Failed to fopen('{$fileName}')");
  237. return $this;
  238. }
  239. // Read line by line until we have required count or we reach EOF
  240. while ($readedLinesCount < $linesCount && !feof($fp)) {
  241. $content .= @fgets($fp);
  242. $readedLinesCount++;
  243. if (strlen($content) > $this->_maxBytesToRender) {
  244. $this->_log(
  245. sprintf(
  246. "Content is too long already (%d bytes), we won't render any more",
  247. strlen($content)
  248. )
  249. );
  250. break;
  251. }
  252. }
  253. if (@fclose($fp) === false) {
  254. $this->_failure("Failed to fclose('{$fileName}')");
  255. return $this;
  256. }
  257. $this->_log($content);
  258. return $this;
  259. }
  260. /**
  261. * Checks whether a file exists
  262. *
  263. * @param string File name to check
  264. * @return $this
  265. */
  266. public function exists($fileName)
  267. {
  268. $fileName = phpRack_Adapters_File::factory($fileName)->getFileName();
  269. clearstatcache();
  270. if (file_exists($fileName)) {
  271. $this->_success("File '{$fileName}' exists");
  272. } else {
  273. $this->_failure("File '{$fileName}' does not exist");
  274. }
  275. return $this;
  276. }
  277. /**
  278. * Checks whether a file is readable
  279. *
  280. * @param string File name to check
  281. * @return $this
  282. */
  283. public function isReadable($fileName)
  284. {
  285. $fileName = phpRack_Adapters_File::factory($fileName)->getFileName();
  286. clearstatcache();
  287. if (is_readable($fileName)) {
  288. $this->_success("File '{$fileName}' is readable");
  289. } else {
  290. $this->_failure("File '{$fileName}' is not readable");
  291. }
  292. return $this;
  293. }
  294. /**
  295. * Check whether a file is writable
  296. *
  297. * @param string File name to check
  298. * @return $this
  299. */
  300. public function isWritable($fileName)
  301. {
  302. $fileName = phpRack_Adapters_File::factory($fileName)->getFileName();
  303. clearstatcache();
  304. if (is_writable($fileName)) {
  305. $this->_success("File '{$fileName}' is writable");
  306. } else {
  307. $this->_failure("File '{$fileName}' is not writable");
  308. }
  309. return $this;
  310. }
  311. /**
  312. * Check whether the filename is a directory
  313. *
  314. * @param string File name to check
  315. * @return $this
  316. */
  317. public function isDir($fileName)
  318. {
  319. $fileName = phpRack_Adapters_File::factory($fileName)->getFileName();
  320. clearstatcache();
  321. if (is_dir($fileName)) {
  322. $this->_success("File '{$fileName}' is a directory");
  323. } else {
  324. $this->_failure("File '{$fileName}' is not a directory");
  325. }
  326. return $this;
  327. }
  328. /**
  329. * Check that file exists
  330. *
  331. * @param string File name to check
  332. * @return boolean True if file exists
  333. */
  334. protected function _isFileExists($fileName)
  335. {
  336. if (!file_exists($fileName)) {
  337. $this->_failure("File '{$fileName}' is not found");
  338. return false;
  339. }
  340. $this->_log(
  341. sprintf(
  342. "File '%s' (%d bytes, modified on %s):",
  343. realpath($fileName),
  344. filesize($fileName),
  345. $this->_modifiedOn(filemtime($fileName))
  346. )
  347. );
  348. return true;
  349. }
  350. /**
  351. * Show when this file was modified
  352. *
  353. * @param integer Time/date when this file was modifed, result of filemtime()
  354. * @return string
  355. * @see _isFileExists()
  356. */
  357. protected function _modifiedOn($time)
  358. {
  359. $mins = round((time() - $time)/60, 1);
  360. if ($mins < 1) {
  361. $age = round($mins * 60) . 'sec';
  362. } elseif ($mins < 60) {
  363. $age = $mins . 'min';
  364. } elseif ($mins < 24 * 60) {
  365. $age = round($mins/60) . 'hrs';
  366. } else {
  367. $age = round($mins/(60*24)) . 'days';
  368. }
  369. return date('d-M-y H:i:s', $time) . ', ' . $age . ' ago';
  370. }
  371. }