PageRenderTime 51ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/libs/Nette/Utils/SafeStream.php

https://github.com/simekadam/bzukotbook
PHP | 287 lines | 128 code | 58 blank | 101 comment | 16 complexity | 2a7ee95b53ee5a9a2b0780bce5d11988 MD5 | raw file
  1. <?php
  2. /**
  3. * This file is part of the Nette Framework.
  4. *
  5. * Copyright (c) 2004, 2010 David Grudl (http://davidgrudl.com)
  6. *
  7. * This source file is subject to the "Nette license", and/or
  8. * GPL license. For more information please see http://nette.org
  9. */
  10. namespace Nette;
  11. use Nette;
  12. /**
  13. * Thread safe / atomic file manipulation. Stream safe://
  14. *
  15. * <code>
  16. * file_put_contents('safe://myfile.txt', $content);
  17. *
  18. * $content = file_get_contents('safe://myfile.txt');
  19. *
  20. * unlink('safe://myfile.txt');
  21. * </code>
  22. *
  23. * @author David Grudl
  24. */
  25. final class SafeStream
  26. {
  27. /**
  28. * Name of stream protocol - safe://
  29. */
  30. const PROTOCOL = 'safe';
  31. /**
  32. * Current file handle.
  33. */
  34. private $handle;
  35. /**
  36. * Renaming of temporary file.
  37. */
  38. private $filePath;
  39. private $tempFile;
  40. /**
  41. * Starting position in file (for appending).
  42. */
  43. private $startPos = 0;
  44. /**
  45. * Write-error detected?
  46. */
  47. private $writeError = FALSE;
  48. /**
  49. * Registers protocol 'safe://'.
  50. * @return bool
  51. */
  52. public static function register()
  53. {
  54. return stream_wrapper_register(self::PROTOCOL, __CLASS__);
  55. }
  56. /**
  57. * Opens file.
  58. * @param string file name with stream protocol
  59. * @param string mode - see fopen()
  60. * @param int STREAM_USE_PATH, STREAM_REPORT_ERRORS
  61. * @param string full path
  62. * @return bool TRUE on success or FALSE on failure
  63. */
  64. public function stream_open($path, $mode, $options, &$opened_path)
  65. {
  66. $path = substr($path, strlen(self::PROTOCOL)+3); // trim protocol safe://
  67. $flag = trim($mode, 'rwax+'); // text | binary mode
  68. $mode = trim($mode, 'tb'); // mode
  69. $use_path = (bool) (STREAM_USE_PATH & $options); // use include_path?
  70. $append = FALSE;
  71. switch ($mode) {
  72. case 'r':
  73. case 'r+':
  74. // enter critical section: open and lock EXISTING file for reading/writing
  75. $handle = @fopen($path, $mode.$flag, $use_path); // intentionally @
  76. if (!$handle) return FALSE;
  77. if (flock($handle, $mode == 'r' ? LOCK_SH : LOCK_EX)) {
  78. $this->handle = $handle;
  79. return TRUE;
  80. }
  81. fclose($handle);
  82. return FALSE;
  83. case 'a':
  84. case 'a+': $append = TRUE;
  85. case 'w':
  86. case 'w+':
  87. // try enter critical section: open and lock EXISTING file for rewriting
  88. $handle = @fopen($path, 'r+'.$flag, $use_path); // intentionally @
  89. if ($handle) {
  90. if (flock($handle, LOCK_EX)) {
  91. if ($append) {
  92. fseek($handle, 0, SEEK_END);
  93. $this->startPos = ftell($handle);
  94. } else {
  95. ftruncate($handle, 0);
  96. }
  97. $this->handle = $handle;
  98. return TRUE;
  99. }
  100. fclose($handle);
  101. }
  102. // file doesn't exists, continue...
  103. $mode{0} = 'x'; // x || x+
  104. case 'x':
  105. case 'x+':
  106. if (file_exists($path)) return FALSE;
  107. // create temporary file in the same directory
  108. $tmp = '~~' . time() . '.tmp';
  109. // enter critical section: create temporary file
  110. $handle = @fopen($path . $tmp, $mode . $flag, $use_path); // intentionally @
  111. if ($handle) {
  112. if (flock($handle, LOCK_EX)) {
  113. $this->handle = $handle;
  114. if (!@rename($path . $tmp, $path)) { // intentionally @
  115. // rename later - for windows
  116. $this->tempFile = realpath($path . $tmp);
  117. $this->filePath = substr($this->tempFile, 0, -strlen($tmp));
  118. }
  119. return TRUE;
  120. }
  121. fclose($handle);
  122. unlink($path . $tmp);
  123. }
  124. return FALSE;
  125. default:
  126. trigger_error("Unsupported mode $mode", E_USER_WARNING);
  127. return FALSE;
  128. } // switch
  129. } // stream_open
  130. /**
  131. * Closes file.
  132. * @return void
  133. */
  134. public function stream_close()
  135. {
  136. if ($this->writeError) {
  137. ftruncate($this->handle, $this->startPos);
  138. }
  139. flock($this->handle, LOCK_UN);
  140. fclose($this->handle);
  141. // are we working with temporary file?
  142. if ($this->tempFile) {
  143. // try to rename temp file, otherwise delete temp file
  144. if (!@rename($this->tempFile, $this->filePath)) { // intentionally @
  145. unlink($this->tempFile);
  146. }
  147. }
  148. }
  149. /**
  150. * Reads up to length bytes from the file.
  151. * @param int length
  152. * @return string
  153. */
  154. public function stream_read($length)
  155. {
  156. return fread($this->handle, $length);
  157. }
  158. /**
  159. * Writes the string to the file.
  160. * @param string data to write
  161. * @return int number of bytes that were successfully stored
  162. */
  163. public function stream_write($data)
  164. {
  165. $len = strlen($data);
  166. $res = fwrite($this->handle, $data, $len);
  167. if ($res !== $len) { // disk full?
  168. $this->writeError = TRUE;
  169. }
  170. return $res;
  171. }
  172. /**
  173. * Returns the position of the file.
  174. * @return int
  175. */
  176. public function stream_tell()
  177. {
  178. return ftell($this->handle);
  179. }
  180. /**
  181. * Returns TRUE if the file pointer is at end-of-file.
  182. * @return bool
  183. */
  184. public function stream_eof()
  185. {
  186. return feof($this->handle);
  187. }
  188. /**
  189. * Sets the file position indicator for the file.
  190. * @param int position
  191. * @param int see fseek()
  192. * @return int Return TRUE on success
  193. */
  194. public function stream_seek($offset, $whence)
  195. {
  196. return fseek($this->handle, $offset, $whence) === 0; // ???
  197. }
  198. /**
  199. * Gets information about a file referenced by $this->handle.
  200. * @return array
  201. */
  202. public function stream_stat()
  203. {
  204. return fstat($this->handle);
  205. }
  206. /**
  207. * Gets information about a file referenced by filename.
  208. * @param string file name
  209. * @param int STREAM_URL_STAT_LINK, STREAM_URL_STAT_QUIET
  210. * @return array
  211. */
  212. public function url_stat($path, $flags)
  213. {
  214. // This is not thread safe
  215. $path = substr($path, strlen(self::PROTOCOL)+3);
  216. return ($flags & STREAM_URL_STAT_LINK) ? @lstat($path) : @stat($path); // intentionally @
  217. }
  218. /**
  219. * Deletes a file.
  220. * On Windows unlink is not allowed till file is opened
  221. * @param string file name with stream protocol
  222. * @return bool TRUE on success or FALSE on failure
  223. */
  224. public function unlink($path)
  225. {
  226. $path = substr($path, strlen(self::PROTOCOL)+3);
  227. return unlink($path);
  228. }
  229. }