Ticket #23: FileSystem.2.php

File FileSystem.2.php, 23.3 kB (added by norman@sefiroth.de, 3 years ago)
Line 
1 <?php
2
3 /*
4  *  $Id: FileSystem.php,v 1.11 2005/12/01 20:56:59 hlellelid Exp $
5  *
6  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
7  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
8  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
9  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
10  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
11  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
12  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
13  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
14  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
15  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
16  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
17  *
18  * This software consists of voluntary contributions made by many individuals
19  * and is licensed under the LGPL. For more information please see
20  * <http://phing.info>.
21  */
22
23 /**
24  * This is an abstract class for platform specific filesystem implementations
25  * you have to implement each method in the platform specific filesystem implementation
26  * classes Your local filesytem implementation must extend this class.
27  * You should also use this class as a template to write your local implementation
28  * Some native PHP filesystem specific methods are abstracted here as well. Anyway
29  * you _must_ always use this methods via a PhingFile object (that by nature uses the
30  * *FileSystem drivers to access the real filesystem via this class using natives.
31  *
32  * FIXME:
33  *  - Error handling reduced to min fallthrough runtime excetions
34  *    more precise errorhandling is done by the PhingFile class
35  *   
36  * @author Charlie Killian <charlie@tizac.com>
37  * @author Hans Lellelid <hans@xmpl.org>
38  * @version $Revision: 1.11 $
39  * @package phing.system.io
40  */
41 abstract class FileSystem {   
42
43     /* properties for simple boolean attributes */
44     const BA_EXISTS    = 0x01;
45     const BA_REGULAR   = 0x02;
46     const BA_DIRECTORY = 0x04;
47     const BA_HIDDEN    = 0x08;
48     
49     /** Instance for getFileSystem() method. */
50     private static $fs;
51     
52     /**
53      * Static method to return the FileSystem singelton representing
54      * this platform's local filesystem driver.
55      */
56     function getFileSystem() {
57         if (self::$fs === null) {
58             switch(Phing::getProperty('host.fstype')) {
59                 case 'UNIX':
60                     include_once 'phing/system/io/UnixFileSystem.php';
61                     self::$fs = new UnixFileSystem();
62                 break;
63                 case 'WIN32':
64                     include_once 'phing/system/io/Win32FileSystem.php';
65                     self::$fs = new Win32FileSystem();
66                 break;
67                 case 'WINNT':
68                     include_once 'phing/system/io/WinNTFileSystem.php';
69                     self::$fs = new WinNTFileSystem();
70                 break;
71                 default:
72                     throw new Exception("Host uses unsupported filesystem, unable to proceed");
73             }
74         }
75         return self::$fs;
76     }
77
78     /* -- Normalization and construction -- */
79
80     /**
81      * Return the local filesystem's name-separator character.
82      */
83     abstract function getSeparator();
84
85     /**
86      * Return the local filesystem's path-separator character.
87      */
88     abstract function getPathSeparator();
89
90     /**
91      * Convert the given pathname string to normal form.  If the string is
92      * already in normal form then it is simply returned.
93      */
94     abstract function normalize($strPath);
95
96     /**
97      * Compute the length of this pathname string's prefix.  The pathname
98      * string must be in normal form.
99      */
100     abstract function prefixLength($pathname);
101
102     /**
103      * Resolve the child pathname string against the parent.
104      * Both strings must be in normal form, and the result
105      * will be a string in normal form.
106      */
107     abstract function resolve($parent, $child);
108     
109     /**
110      * Resolve the given abstract pathname into absolute form.  Invoked by the
111      * getAbsolutePath and getCanonicalPath methods in the PhingFile class.
112      */
113     abstract function resolveFile(PhingFile $f);
114
115     /**
116      * Return the parent pathname string to be used when the parent-directory
117      * argument in one of the two-argument PhingFile constructors is the empty
118      * pathname.
119      */
120     abstract function getDefaultParent();
121
122     /**
123      * Post-process the given URI path string if necessary.  This is used on
124      * win32, e.g., to transform "/c:/foo" into "c:/foo".  The path string
125      * still has slash separators; code in the PhingFile class will translate them
126      * after this method returns.
127      */
128     abstract function fromURIPath($path);
129
130     /* -- Path operations -- */
131
132     /**
133      * Tell whether or not the given abstract pathname is absolute.
134      */
135     abstract function isAbsolute(PhingFile $f);
136
137     /**
138      * canonicalize filename by checking on disk
139      * @return mixed Canonical path or false if the file doesn't exist.
140      */
141     function canonicalize($strPath) {
142         return @realpath($strPath);       
143     }
144
145     /* -- Attribute accessors -- */
146
147     /**
148      * Return the simple boolean attributes for the file or directory denoted
149      * by the given abstract pathname, or zero if it does not exist or some
150      * other I/O error occurs.
151      */
152     function getBooleanAttributes($f) {
153         throw new Exception("SYSTEM ERROR method getBooleanAttributes() not implemented by fs driver");
154     }
155
156     /**
157      * Check whether the file or directory denoted by the given abstract
158      * pathname may be accessed by this process.  If the second argument is
159      * false, then a check for read access is made; if the second
160      * argument is true, then a check for write (not read-write)
161      * access is made.  Return false if access is denied or an I/O error
162      * occurs.
163      */
164     function checkAccess(PhingFile $f, $write = false) {
165         // we clear stat cache, its expensive to look up from scratch,
166         // but we need to be sure
167         @clearstatcache();
168
169
170         // Shouldn't this be $f->GetAbsolutePath() ?
171         // And why doesn't GetAbsolutePath() work?
172
173         $strPath = (string) $f->getPath();
174
175         // FIXME
176         // if file object does denote a file that yet not existst
177         // path rights are checked
178         if (!@file_exists($strPath) && !is_dir($strPath)) {
179             $strPath = $f->getParent();
180             if ($strPath === null || !is_dir($strPath)) {
181                 $strPath = Phing::getProperty("user.dir");
182             }
183             //$strPath = dirname($strPath);
184         }
185
186         if (!$write) {
187             return (boolean) @is_readable($strPath);
188         } else {
189             return (boolean) @is_writable($strPath);
190         }
191     }
192
193     /**
194      * Return the time at which the file or directory denoted by the given
195      * abstract pathname was last modified, or zero if it does not exist or
196      * some other I/O error occurs.
197      */
198     function getLastModifiedTime(PhingFile $f) {
199         
200         if (!$f->exists()) {
201             return 0;
202         }
203
204         @clearstatcache();
205         $strPath = (string) $f->getPath();
206         $mtime = @filemtime($strPath);
207         if (false === $mtime) {
208             // FAILED. Log and return err.
209             $msg = "FileSystem::Filemtime() FAILED. Cannot can not get modified time of $strPath. $php_errormsg";
210             throw new Exception($msg);
211         } else {
212             return (int) $mtime;
213         }
214     }
215
216     /**
217      * Return the length in bytes of the file denoted by the given abstract
218      * pathname, or zero if it does not exist, is a directory, or some other
219      * I/O error occurs.
220      */
221     function getLength(PhingFile $f) {
222         $strPath = (string) $f->getAbsolutePath();
223         $fs = filesize((string) $strPath);
224         if ($fs !== false) {
225             return $fs;
226         } else {
227             $msg = "FileSystem::Read() FAILED. Cannot get filesize of $strPath. $php_errormsg";
228             throw new Exception($msg);
229         }
230     }
231
232     /* -- File operations -- */
233
234     /**
235      * Create a new empty file with the given pathname.  Return
236      * true if the file was created and false if a
237      * file or directory with the given pathname already exists.  Throw an
238      * IOException if an I/O error occurs.
239      *
240      * @param       string      Path of the file to be created.
241      *     
242      * @throws      IOException
243      */
244     function createNewFile($strPathname) {
245         if (@file_exists($strPathname))
246             return false;
247             
248         // Create new file
249         $fp = @fopen($strPathname, "w");
250         if ($fp === false) {
251             throw new IOException("The file \"$strPathname\" could not be created");           
252         }
253         @fclose($fp);       
254         return true;
255     }
256
257     /**
258      * Delete the file or directory denoted by the given abstract pathname,
259      * returning true if and only if the operation succeeds.
260      */
261     function delete(PhingFile $f) {
262         if ($f->isDirectory()) {
263             return $this->rmdir($f->getPath());
264         } else {
265             return $this->unlink($f->getPath());
266         }
267     }
268
269     /**
270      * Arrange for the file or directory denoted by the given abstract
271      * pathname to be deleted when Phing::shutdown is called, returning
272     * true if and only if the operation succeeds.
273      */
274     function deleteOnExit($f) {
275         throw new Exception("deleteOnExit() not implemented by local fs driver");
276     }
277
278     /**
279      * List the elements of the directory denoted by the given abstract
280      * pathname.  Return an array of strings naming the elements of the
281      * directory if successful; otherwise, return <code>null</code>.
282      */
283     function listDir(PhingFile $f) {
284         $strPath = (string) $f->getAbsolutePath();
285         $d = @dir($strPath);
286         if (!$d) {
287             return null;
288         }
289         $list = array();
290         while($entry = $d->read()) {
291             if ($entry != "." && $entry != "..") {
292                 array_push($list, $entry);
293             }
294         }
295         $d->close();
296         unset($d);
297         return $list;
298     }
299
300     /**
301      * Create a new directory denoted by the given abstract pathname,
302      * returning true if and only if the operation succeeds.
303      */
304     function createDirectory(&$f) {
305         return @mkdir($f->getAbsolutePath(),0755);
306     }
307
308     /**
309      * Rename the file or directory denoted by the first abstract pathname to
310      * the second abstract pathname, returning true if and only if
311      * the operation succeeds.
312      *
313      * @param PhingFile $f1 abstract source file
314      * @param PhingFile $f2 abstract destination file
315      * @return void   
316      * @throws Exception if rename cannot be performed
317      */
318     function rename(PhingFile $f1, PhingFile $f2) {       
319         // get the canonical paths of the file to rename
320         $src = $f1->getAbsolutePath();
321         $dest = $f2->getAbsolutePath();
322         if (false === @rename($src, $dest)) {
323             $msg = "Rename FAILED. Cannot rename $src to $dest. $php_errormsg";
324             throw new Exception($msg);
325         }
326     }
327
328     /**
329      * Set the last-modified time of the file or directory denoted by the
330      * given abstract pathname returning true if and only if the
331      * operation succeeds.
332      * @return void
333      * @throws Exception
334      */
335     function setLastModifiedTime(PhingFile $f, $time) {       
336         $path = $f->getPath();
337         $success = @touch($path, $time);
338         if (!$success) {
339             throw new Exception("Could not create directory due to: $php_errormsg");
340         }
341     }
342
343     /**
344      * Mark the file or directory denoted by the given abstract pathname as
345      * read-only, returning <code>true</code> if and only if the operation
346      * succeeds.
347      */
348     function setReadOnly($f) {
349         throw new Exception("setReadonle() not implemented by local fs driver");
350     }
351
352     /* -- Filesystem interface -- */
353
354     /**
355      * List the available filesystem roots, return array of PhingFile objects
356      */
357     function listRoots() {
358         throw new Exception("SYSTEM ERROR [listRoots() not implemented by local fs driver]");
359     }
360
361     /* -- Basic infrastructure -- */
362
363     /**
364      * Compare two abstract pathnames lexicographically.
365      */
366     function compare($f1, $f2) {
367         throw new Exception("SYSTEM ERROR [compare() not implemented by local fs driver]");
368     }
369
370     /**
371      * Copy a file.
372      *
373      * @param PhingFile $src Source path and name file to copy.
374      * @param PhingFile $dest Destination path and name of new file.
375      *
376      * @return void     
377      * @throws Exception if file cannot be copied.
378      */
379     function copy(PhingFile $src, PhingFile $dest) {
380         global $php_errormsg;
381         $srcPath  = $src->getAbsolutePath();
382         $destPath = $dest->getAbsolutePath();
383
384         if (false === @copy($srcPath, $destPath)) { // Copy FAILED. Log and return err.
385             // Add error from php to end of log message. $php_errormsg.
386             $msg = "FileSystem::copy() FAILED. Cannot copy $srcPath to $destPath. $php_errormsg";
387             throw new Exception($msg);
388         }
389         
390         try {
391             $dest->setMode($src->getMode());
392         } catch(Exception $exc) {
393             // [MA] does chmod returns an error on systems that do not support it ?
394             // eat it up for now.
395         }
396     }
397
398     /**
399      * Append to a file.
400      *
401      * @param PhingFile $src Source path and name file to copy.
402      * @param PhingFile $dest Destination path and name of file to append to.
403      *
404      * @return void     
405      * @throws Exception if file cannot be copied.
406      */
407     function append(PhingFile $src, PhingFile $dest) {
408         global $php_errormsg;
409         $srcPath  = $src->getAbsolutePath();
410         $destPath = $dest->getAbsolutePath();
411
412         $destHandle = fopen( $destPath, "a+" );
413         $srcHandle = fopen( $srcPath, "rb" );
414         while (!feof($srcHandle)) {
415           fwrite($destHandle, fgetc($srcHandle));
416         }
417         
418         try {
419             $dest->setMode($src->getMode());
420         } catch(Exception $exc) {
421             // [MA] does chmod returns an error on systems that do not support it ?
422             // eat it up for now.
423         }
424     }
425
426
427     /**
428      * Change the permissions on a file or directory.
429      *
430      * @param    pathname    String. Path and name of file or directory.
431      * @param    mode        Int. The mode (permissions) of the file or
432      *                        directory. If using octal add leading 0. eg. 0777.
433      *                        Mode is affected by the umask system setting.
434      *
435      * @return void     
436      * @throws Exception if operation failed.
437      */
438     function chmod($pathname, $mode) {   
439         $str_mode = decoct($mode); // Show octal in messages.   
440         if (false === @chmod($pathname, $mode)) {// FAILED.
441             $msg = "FileSystem::chmod() FAILED. Cannot chmod $pathname. Mode $str_mode. $php_errormsg";
442             throw new Exception($msg);
443         }
444     }
445
446     /**
447      * Locks a file and throws an Exception if this is not possible.
448      * @return void
449      * @throws Exception
450      */
451     function lock(PhingFile $f) {
452         $filename = $f->getPath();
453         $fp = @fopen($filename, "w");
454         $result = @flock($fp, LOCK_EX);
455         @fclose($fp);
456         if (!$result) {
457             throw new Exception("Could not lock file '$filename'");
458         }
459     }
460
461     /**
462      * Unlocks a file and throws an IO Error if this is not possible.
463      *
464      * @throws Exception
465      * @return void
466      */
467     function unlock(PhingFile $f) {
468         $filename = $f->getPath();
469         $fp = @fopen($filename, "w");
470         $result = @flock($fp, LOCK_UN);
471         fclose($fp);
472         if (!$result) {
473             throw new Exception("Could not unlock file '$filename'");
474         }
475     }
476
477     /**
478      * Delete a file.
479      *
480      * @param    file    String. Path and/or name of file to delete.
481      *
482      * @return void
483      * @throws Exception - if an error is encountered.
484      */
485     function unlink($file) {
486         global $php_errormsg;
487         if (false === @unlink($file)) {
488             $msg = "FileSystem::unlink() FAILED. Cannot unlink '$file'. $php_errormsg";
489             throw new Exception($msg);
490         }
491     }
492
493     /**
494      * Symbolically link a file to another name.
495      *
496      * Currently symlink is not implemented on Windows. Don't use if the application is to be portable.
497      *
498      * @param string $target Path and/or name of file to link.
499      * @param string $link Path and/or name of link to be created.
500      * @return void
501      */
502     function symlink($target, $link) {
503     
504         // If Windows OS then symlink() will report it is not supported in
505         // the build. Use this error instead of checking for Windows as the OS.
506
507         if (false === @symlink($target, $link)) {
508             // Add error from php to end of log message. $php_errormsg.
509             $msg = "FileSystem::Symlink() FAILED. Cannot symlink '$target' to '$link'. $php_errormsg";
510             throw new Exception($msg);
511         }
512
513     }
514
515     /**
516      * Set the modification and access time on a file to the present time.
517      *
518      * @param string $file Path and/or name of file to touch.
519      * @param int $time
520      * @return void
521      */
522     function touch($file, $time = null) {
523         global $php_errormsg;
524         
525         if (null === $time) {
526             $error = @touch($file);
527         } else {
528             $error = @touch($file, $time);
529         }
530
531         if (false === $error) { // FAILED.
532             // Add error from php to end of log message. $php_errormsg.
533             $msg = "FileSystem::touch() FAILED. Cannot touch '$file'. $php_errormsg";           
534             throw new Exception($msg);           
535         }
536     }
537
538     /**
539      * Delete an empty directory OR a directory and all of its contents.
540      *
541      * @param    dir    String. Path and/or name of directory to delete.
542      * @param    children    Boolean.    False: don't delete directory contents.
543      *                                    True: delete directory contents.
544      *
545      * @return void
546      */
547     function rmdir($dir, $children = false) {
548         global $php_errormsg;
549         
550         // If children=FALSE only delete dir if empty.
551         if (false === $children) {
552         
553             if (false === @rmdir($dir)) { // FAILED.
554                 // Add error from php to end of log message. $php_errormsg.
555                 $msg = "FileSystem::rmdir() FAILED. Cannot rmdir $dir. $php_errormsg";
556                 throw new Exception($msg);
557             }
558             
559         } else { // delete contents and dir.
560
561             $handle = @opendir($dir);
562
563             if (false === $handle) { // Error.
564
565                 $msg = "FileSystem::rmdir() FAILED. Cannot opendir() $dir. $php_errormsg";               
566                 throw new Exception($msg);
567
568             } else { // Read from handle.
569
570                 // Don't error on readdir().
571                 while (false !== ($entry = @readdir($handle))) {
572
573                     if ($entry != '.' && $entry != '..') {
574
575                         // Only add / if it isn't already the last char.
576                         // This ONLY serves the purpose of making the Logger
577                         // output look nice:)
578
579                         if (strpos(strrev($dir), DIRECTORY_SEPARATOR) === 0) {// there is a /
580                             $next_entry = $dir . $entry;
581                         } else { // no /
582                             $next_entry = $dir . DIRECTORY_SEPARATOR . $entry;
583                         }
584
585                         // NOTE: As of php 4.1.1 is_dir doesn't return FALSE it
586                         // returns 0. So use == not ===.
587
588                         // Don't error on is_dir()
589                         if (false == @is_dir($next_entry)) { // Is file.
590                             
591                             try {
592                                 self::unlink($next_entry); // Delete.
593                             } catch (Exception $e) {                           
594                                 $msg = "FileSystem::Rmdir() FAILED. Cannot FileSystem::Unlink() $next_entry. ". $e->getMessage();
595                                 throw new Exception($msg);
596                             }
597
598                         } else { // Is directory.
599                             
600                             try {
601                                 self::rmdir($next_entry, true); // Delete
602                             } catch (Exception $e) {
603                                 $msg = "FileSystem::rmdir() FAILED. Cannot FileSystem::rmdir() $next_entry. ". $e->getMessage();
604                                 throw new Exception($msg);
605                             }
606
607                         } // end is_dir else
608                     } // end .. if
609                 } // end while
610             } // end handle if
611
612             // Don't error on closedir()
613             @closedir($handle);
614             
615             if (false === @rmdir($dir)) { // FAILED.
616                 // Add error from php to end of log message. $php_errormsg.
617                 $msg = "FileSystem::rmdir() FAILED. Cannot rmdir $dir. $php_errormsg";
618                 throw new Exception($msg);
619             }
620             
621         }
622                 
623     }
624
625     /**
626      * Set the umask for file and directory creation