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