Packages

  • Nette
    • Application
    • Caching
    • Collections
    • Config
    • Forms
    • IO
    • Loaders
    • Mail
    • Reflection
    • Security
    • Templates
    • Web
  • None
  • PHP

Classes

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