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