Source for file SafeStream.php

Documentation is available at SafeStream.php

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