Ticket #219: File.php

File File.php, 28.3 KB (added by hans, 4 years ago)

updated File class (trunk)

Line 
1<?php
2/*
3 *  $Id: File.php 313 2007-11-17 04:20:58Z hans $
4 *
5 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16 *
17 * This software consists of voluntary contributions made by many individuals
18 * and is licensed under the LGPL. For more information please see
19 * <http://phing.info>.
20 */
21
22namespace phing::system::io;
23use phing::system::io::FileSystem;
24use phing::system::lang::NullPointerException;
25
26/**
27 * An representation of file and directory pathnames.
28 *
29 * @package   phing.system.io
30 */
31class File {
32
33    /**
34     * Separator string, static, obtained from FileSystem.
35     * @see FileSystem::getSeparator()
36     */
37    public static $separator;
38
39    /**
40     * Path separator string, static, obtained from FileSystem (; or :)
41     * @see FileSystem::getSeparator()
42     */
43    public static $pathSeparator;
44
45    /**
46     * This abstract pathname's normalized pathname string.  A normalized
47     * pathname string uses the default name-separator character and does not
48     * contain any duplicate or redundant separators.
49     * @var string
50     */
51    private $path;
52
53    /**
54     * The length of this abstract pathname's prefix, or zero if it has no prefix.
55     * @var int
56     */
57    private $prefixLength = 0;
58
59    /**
60     * Create a new File object.
61     *
62     * This method supports sevarl valid signatures:
63     *     new File(File parent, string filename)
64     *  new File(string filename)
65     *  new File(string parent, string filename)
66     */
67    function __construct($arg1, $arg2 = null) {
68
69        if (self::$separator === null || self::$pathSeparator === null) {
70            $fs = FileSystem::getFileSystem();
71            self::$separator = $fs->getSeparator();
72            self::$pathSeparator = $fs->getPathSeparator();
73        }
74
75        /* simulate constructor overloading */
76        if ($arg1 instanceof File && is_string($arg2)) {
77            $this->__constructFileParentStringChild($arg1, $arg2);
78        } elseif (is_string($arg1) && ($arg2 === null)) {
79            $this->__constructPathname($arg1);
80        } elseif(is_string($arg1) && is_string($arg2)) {
81            $this->__constructStringParentStringChild($arg1, $arg2);
82        } else {
83            if ($arg1 === null) {
84                throw new NullPointerException("Argument1 to function must not be null");
85            }
86            $this->path = (string) $arg1;
87            $this->prefixLength = (int) $arg2;
88        }
89    }
90
91    /**
92     * Private overloaded constructor when passed a File parent path and string child path.
93     * @param File $parent The parent path
94     * @param string $child (optional) The child path
95     */
96    private function __constructFileParentStringChild(File $parent, $child) {
97        // obtain ref to the filesystem layer
98        $fs = FileSystem::getFileSystem();
99
100        if ($child === null) {
101            throw new NullPointerException("Argument to function must not be null");
102        }
103
104        if ($parent !== null) {
105            if ($parent->getPath() === "") {
106                $this->path = $fs->resolve($fs->getDefaultParent(), $fs->normalize($child));
107            } else {
108                $this->path = $fs->resolve($parent->getPath(), $fs->normalize($child));
109            }
110        } else {
111            $this->path = $fs->normalize($child);
112        }
113        $this->prefixLength = $fs->prefixLength($this->path);
114    }
115
116    /**
117     * Private constructor when passed a single path string.
118     *
119     * @param string $pathname
120     */
121    private function __constructPathname($pathname) {
122        // obtain ref to the filesystem layer
123        $fs = FileSystem::getFileSystem();
124
125        if ($pathname === null) {
126            throw new NullPointerException("Argument to function must not be null");
127        }
128
129        $this->path = (string) $fs->normalize($pathname);
130        $this->prefixLength = (int) $fs->prefixLength($this->path);
131    }
132
133    /**
134     * Private overloaded constructor when passed a string parent path and child paths.
135     * @param string $parent The parent path
136     * @param string $child (optional) The child path
137     */
138    private function __constructStringParentStringChild($parent, $child) {
139        // obtain ref to the filesystem layer
140        $fs = FileSystem::getFileSystem();
141
142        if ($child === null) {
143            throw new NullPointerException("Argument to function must not be null");
144        }
145        if ($parent !== null) {
146            if ($parent === "") {
147                $this->path = $fs->resolve($fs->getDefaultParent(), $fs->normalize($child));
148            } else {
149                $this->path = $fs->resolve($fs->normalize($parent), $fs->normalize($child));
150            }
151        } else {
152            $this->path = (string) $fs->normalize($child);
153        }
154        $this->prefixLength = (int) $fs->prefixLength($this->path);
155    }
156
157    /**
158     * Returns the length of this abstract pathname's prefix.
159     * @return int
160     */
161    function getPrefixLength() {
162        return (int) $this->prefixLength;
163    }
164
165    /* -- Path-component accessors -- */
166
167    /**
168     * Returns the name of the file or directory denoted by this abstract
169     * pathname.  This is just the last name in the pathname's name
170     * sequence.  If the pathname's name sequence is empty, then the empty
171     * string is returned.
172     *
173     * @return string The name of the file or directory.
174     */
175    public function getName() {
176        // that's a lastIndexOf
177        $index = ((($res = strrpos($this->path, self::$separator)) === false) ? -1 : $res);
178        if ($index < $this->prefixLength) {
179            return substr($this->path, $this->prefixLength);
180        }
181        return substr($this->path, $index + 1);
182    }
183
184    /**
185     * Returns the pathname string of this abstract pathname's parent, or
186     * null if this pathname does not name a parent directory.
187     *
188     * The parent of an abstract pathname consists of the pathname's prefix,
189     * if any, and each name in the pathname's name sequence except for the last.
190     * If the name sequence is empty then the pathname does not name a parent
191     * directory.
192     *
193     * @return string The pathname string of the parent directory.
194     */
195    public function getParent() {
196        // that's a lastIndexOf
197        $index = ((($res = strrpos($this->path, self::$separator)) === false) ? -1 : $res);
198        if ($index < $this->prefixLength) {
199            if (($this->prefixLength > 0) && (strlen($this->path > $this->prefixLength))) {
200                return substr($this->path, 0, $this->prefixLength);
201            }
202            return null;
203        }
204        return substr($this->path, 0, $index);
205    }
206
207    /**
208     * Returns the parent directory as File object.
209     *
210     * @return File A File of the parent directory for this file.
211     */
212    public function getParentFile() {
213        $p = $this->getParent();
214        if ($p === null) {
215            return null;
216        }
217        return new File((string) $p, (int) $this->prefixLength);
218    }
219
220    /**
221     * Converts this abstract pathname into a pathname string.  The resulting
222     * string uses the default name-separator character to separate the names
223     * in the name sequence.
224     *
225     * @return string The string form of this abstract pathname
226     */
227    public function getPath() {
228        return (string) $this->path;
229    }
230
231    /**
232     * Tests whether this abstract pathname is absolute.  The definition of
233     * absolute pathname is system dependent.  On UNIX systems, a pathname is
234     * absolute if its prefix is "/".  On Win32 systems, a pathname is absolute
235     * if its prefix is a drive specifier followed by "\\", or if its prefix
236     * is "\\".
237     *
238     * @return boolean true if this abstract pathname is absolute, false otherwise
239     */
240    public function isAbsolute() {
241        return ($this->prefixLength !== 0);
242    }
243
244    /**
245     * Returns the absolute pathname string of this abstract pathname.
246     *
247     * If this abstract pathname is already absolute, then the pathname
248     * string is simply returned as if by the getPath method.
249     * If this abstract pathname is the empty abstract pathname then
250     * the pathname string of the current user directory, which is named by the
251     * system property user.dir, is returned.  Otherwise this
252     * pathname is resolved in a system-dependent way.  On UNIX systems, a
253     * relative pathname is made absolute by resolving it against the current
254     * user directory.  On Win32 systems, a relative pathname is made absolute
255     * by resolving it against the current directory of the drive named by the
256     * pathname, if any; if not, it is resolved against the current user
257     * directory.
258     *
259     * @return  string The absolute pathname of this file/directory.
260     * @see     isAbsolute()
261     */
262    public function getAbsolutePath() {
263        $fs = FileSystem::getFileSystem();
264        return $fs->resolveFile($this);
265    }
266
267    /**
268     * Returns a File object containing abs path to this file/dir.
269     *
270     * @see getAbsolutePath()
271     * @return File A File object containing the absolute path to this file/dir.
272     */
273    public function getAbsoluteFile() {
274        return new File((string) $this->getAbsolutePath());
275    }
276
277    /**
278     * Returns the canonical pathname string of this abstract pathname.
279     *
280     * A canonical pathname is both absolute and unique. The precise
281     * definition of canonical form is system-dependent. This method first
282     * converts this pathname to absolute form if necessary, as if by invoking the
283     * getAbsolutePath() method, and then maps it to its unique form in a
284     * system-dependent way.  This typically involves removing redundant names
285     * such as "." and .. from the pathname, resolving symbolic links
286     * (on UNIX platforms), and converting drive letters to a standard case
287     * (on Win32 platforms).
288     *
289     * Every pathname that denotes an existing file or directory has a
290     * unique canonical form.  Every pathname that denotes a nonexistent file
291     * or directory also has a unique canonical form.  The canonical form of
292     * the pathname of a nonexistent file or directory may be different from
293     * the canonical form of the same pathname after the file or directory is
294     * created.  Similarly, the canonical form of the pathname of an existing
295     * file or directory may be different from the canonical form of the same
296     * pathname after the file or directory is deleted.
297     *
298     * @return string The canonical path to this file/dir.
299     */
300    public function getCanonicalPath() {
301        $fs = FileSystem::getFileSystem();
302        return $fs->canonicalize($this->path);
303    }
304
305    /**
306     * Returns the canonical form of this abstract pathname.
307     *
308     * @see getCanonicalPath()
309     * @return File The canonical File to tihs file/dir.
310     */
311    public function getCanonicalFile() {
312        return new File($this->getCanonicalPath());
313    }
314
315    /**
316     * Normalizes the directory separators in the path and adds a trailing '/' to directories.
317     *
318     * @param string $path
319     * @param boolean $isDirectory
320     * @return string
321     */
322    private function _slashify($path, $isDirectory) {
323        $p = (string) $path;
324
325        if (self::$separator !== '/') {
326            $p = str_replace(self::$separator, '/', $p);
327        }
328
329        if (!StringHelper::startsWith('/', $p)) {
330            $p = '/'.$p;
331        }
332
333        if (!StringHelper::endsWith('/', $p) && $isDirectory) {
334            $p = $p.'/';
335        }
336
337        return $p;
338    }
339
340    /* -- Attribute accessors -- */
341
342    /**
343     * Tests whether the application can read the file denoted by this
344     * abstract pathname.
345     *
346     * @return boolean Whether file/dir can be read by application.
347     */
348    public function canRead() {
349        $fs = FileSystem::getFileSystem();
350
351        if ($fs->checkAccess($this)) {
352            return (boolean) @is_readable($this->getAbsolutePath());
353        }
354        return false;
355    }
356
357    /**
358     * Tests whether the application can modify to the file denoted by this
359     * abstract pathname.
360     *
361     * @return boolean Whether file/dir can be written to.
362     *
363     */
364    public function canWrite() {
365        $fs = FileSystem::getFileSystem();
366        return $fs->checkAccess($this, true);
367    }
368
369    /**
370     * Tests whether the file denoted by this abstract pathname exists.
371     *
372     * @return boolean Whether file/dir exists.
373     */
374    public function exists() {
375        clearstatcache();
376        if ($this->isFile()) {
377            return @file_exists($this->path);
378        } else {
379            return @is_dir($this->path);
380        }
381    }
382
383    /**
384     * Tests whether the path represented by this object corresponds to a directory.
385     *
386     * @return boolean Whether path represented is a directory.
387     */
388    public function isDirectory() {
389        clearstatcache();
390        $fs = FileSystem::getFileSystem();
391        if ($fs->checkAccess($this) !== true) {
392            throw new IOException("No read access to ".$this->path);
393        }
394        return @is_dir($this->path);
395    }
396
397    /**
398     * Tests whether the path represented by this object corresponds to a normal file.
399     *
400     * @return boolean Whether path represents a file.
401     */
402    public function isFile() {
403        clearstatcache();
404        //$fs = FileSystem::getFileSystem();
405        return @is_file($this->path);
406    }
407
408    /**
409     * Tests whether the path represented by this object is a hidden file.
410     *
411     * @return boolean Whether file/dir is hidden.
412     */
413    public function isHidden() {
414        $fs = FileSystem::getFileSystem();
415        if ($fs->checkAccess($this) !== true) {
416            throw new IOException("No read access to ".$this->path);
417        }
418        return (($fs->getBooleanAttributes($this) & $fs->BA_HIDDEN) !== 0);
419    }
420
421    /**
422     * Returns the time that the file denoted by this abstract pathname was
423     * last modified.
424     *
425     * @return int  A integer value representing the time the file was
426     *          last modified, measured in milliseconds since the epoch
427     *          (00:00:00 GMT, January 1, 1970), or 0 if the
428     *          file does not exist or if an I/O error occurs
429     */
430    public function lastModified() {
431        $fs = FileSystem::getFileSystem();
432        if ($fs->checkAccess($this) !== true) {
433            throw new IOException("No read access to " . $this->path);
434        }
435        return $fs->getLastModifiedTime($this);
436    }
437
438    /**
439     * Returns the length of the file denoted by this abstract pathname.
440     * The return value is unspecified if this pathname denotes a directory.
441     *
442     * @return int The length, in bytes, of the file denoted by this abstract
443     *              pathname, or 0 if the file does not exist
444     * @throws IOException - if file cannot be read
445     */
446    public function length() {
447        $fs = FileSystem::getFileSystem();
448        if ($fs->checkAccess($this) !== true) {
449            throw new IOException("No read access to ".$this->path."\n");
450        }
451        return $fs->getLength($this);
452    }
453
454    /**
455     * Convenience method for returning the contents of this file as a string.
456     * This method uses file_get_contents() to read file in an optimized way.
457     * @return string
458     * @throws IOException - if file cannot be read
459     */
460    public function contents() {
461        if (!$this->canRead() || !$this->isFile()) {
462            throw new IOException("Cannot read file contents!");
463        }
464        return file_get_contents($this->getAbsolutePath());
465    }
466
467    /* -- File operations -- */
468
469    /**
470     * Atomically creates a new, empty file named by this abstract pathname if
471     * and only if a file with this name does not yet exist.  The check for the
472     * existence of the file and the creation of the file if it does not exist
473     * are a single operation that is atomic with respect to all other
474     * filesystem activities that might affect the file.
475     *
476     * @return  true if the named file does not exist and was
477     *          successfully created; <code>false</code> if the named file
478     *          already exists
479     * @throws IOException if file can't be created
480     */
481    public function createNewFile($parents=true, $mode=0777) {
482        $file = FileSystem::getFileSystem()->createNewFile($this->path);
483        return $file;
484    }
485
486    /**
487     * Deletes the file or directory denoted by this abstract pathname.  If
488     * this pathname denotes a directory, then the directory must be empty in
489     * order to be deleted.
490     *
491     * @return  true if and only if the file or directory is
492     *          successfully deleted; false otherwise
493     */
494    public function delete() {
495        $fs = FileSystem::getFileSystem();
496        if ($fs->canDelete($this) !== true) {
497            throw new IOException("Cannot delete " . $this->path . "\n");
498        }
499        return $fs->delete($this);
500    }
501
502    /**
503     * Requests that the file or directory denoted by this abstract pathname
504     * be deleted when php terminates.  Deletion will be attempted only for
505     * normal termination of php and if and if only Phing::shutdown() is
506     * called.
507     *
508     * Once deletion has been requested, it is not possible to cancel the
509     * request.  This method should therefore be used with care.
510     *
511     */
512    public function deleteOnExit() {
513        $fs = FileSystem::getFileSystem();
514        $fs->deleteOnExit($this);
515    }
516
517    /**
518     * Return an array of names for contents of directory represented by this object.
519     *
520     * If this abstract pathname does not denote a directory, then this
521     * method returns null.
522     *
523     * @return array string[] An array of file and directory names
524     */
525    public function listDir($filter = null) {
526        $fs = FileSystem::getFileSystem();
527        return $fs->lister($this, $filter);
528    }
529
530    /**
531     * Return an array of File objects for contents of directory represented by this object.
532     *
533     * @param unknown_type $filter
534     * @return array File[]
535     */
536    public function listFiles($filter = null) {
537        $ss = $this->listDir($filter);
538        if ($ss === null) {
539            return null;
540        }
541        $n = count($ss);
542        $fs = array();
543        for ($i = 0; $i < $n; $i++) {
544            $fs[$i] = new File((string)$this->path, (string)$ss[$i]);
545        }
546        return $fs;
547    }
548
549    /**
550     * Creates the directory named by this abstract pathname, including any
551     * necessary but nonexistent parent directories.  Note that if this
552     * operation fails it may have succeeded in creating some of the necessary
553     * parent directories.
554     *
555     * @return  true if and only if the directory was created,
556     *          along with all necessary parent directories; false
557     *          otherwise
558     * @throws  IOException
559     */
560    public function mkdirs() {
561        if ($this->exists()) {
562            return false;
563        }
564        try {
565            if ($this->mkdir()) {
566                return true;
567            }
568        } catch (IOException $ioe) {
569            // IOException from mkdir() means that directory propbably didn't exist.
570        }
571        $parentFile = $this->getParentFile();
572        return (($parentFile !== null) && ($parentFile->mkdirs() && $this->mkdir()));
573    }
574
575    /**
576     * Creates the directory named by this abstract pathname.
577     *
578     * @return  true if and only if the directory was created; false otherwise
579     * @throws  IOException - If no write access
580     */
581    public function mkdir() {
582        $fs = FileSystem::getFileSystem();
583
584        if ($fs->checkAccess(new File($this->path), true) !== true) {
585            throw new IOException("No write access to " . $this->getPath());
586        }
587        return $fs->createDirectory($this);
588    }
589
590    /**
591     * Renames the file denoted by this abstract pathname.
592     *
593     * @param   destFile  The new abstract pathname for the named file
594     * @return  true if and only if the renaming succeeded; false otherwise
595     */
596    public function renameTo(File $destFile) {
597        $fs = FileSystem::getFileSystem();
598        if ($fs->checkAccess($this) !== true) {
599            throw new IOException("No write access to ".$this->getPath());
600        }
601        return $fs->rename($this, $destFile);
602    }
603
604    /**
605     * Simple-copies file denoted by this abstract pathname into another
606     * File
607     *
608     * @param File $destFile  The new abstract pathname for the named file
609     * @return true if and only if the renaming succeeded; false otherwise
610     */
611    public function copyTo(File $destFile) {
612        $fs = FileSystem::getFileSystem();
613
614        if ($fs->checkAccess($this) !== true) {
615            throw new IOException("No read access to ".$this->getPath()."\n");
616        }
617
618        if ($fs->checkAccess($destFile, true) !== true) {
619            throw new IOException("File::copyTo() No write access to ".$destFile->getPath());
620        }
621        return $fs->copy($this, $destFile);
622    }
623
624    /**
625     * Sets the last-modified time of the file or directory named by this
626     * abstract pathname.
627     *
628     * All platforms support file-modification times to the nearest second,
629     * but some provide more precision.  The argument will be truncated to fit
630     * the supported precision.  If the operation succeeds and no intervening
631     * operations on the file take place, then the next invocation of the
632     * lastModified method will return the (possibly truncated) time argument
633     * that was passed to this method.
634     *
635     * @param  int $time  The new last-modified time, measured in milliseconds since
636     *               the epoch (00:00:00 GMT, January 1, 1970)
637     * @return boolean Whether operation succeeded
638     */
639    public function setLastModified($time) {
640        $time = (int) $time;
641        if ($time < 0) {
642            throw new Exception("IllegalArgumentException, Negative $time\n");
643        }
644
645        // FIXME check if accessible
646        $fs = FileSystem::getFileSystem();
647        if ($fs->checkAccess($this, true) !== true) {
648            throw new IOException("File::setLastModified(). No write access to file\n");
649        }
650        return $fs->setLastModifiedTime($this, $time);
651    }
652
653    /**
654     * Marks the file or directory named by this abstract pathname so that
655     * only read operations are allowed.
656     *
657     * @return boolean Whether operation succeeded
658     */
659    public function setReadOnly() {
660        $fs = FileSystem::getFileSystem();
661        if ($fs->checkAccess($this, true) !== true) {
662            // Error, no write access
663            throw new IOException("No write access to " . $this->getPath());
664        }
665        return $fs->setReadOnly($this);
666    }
667    /**
668     * Sets the mode of the file
669     * @param string $user Name of the user
670     */
671        public function setUser($user) {
672            $fs = FileSystem::getFileSystem();
673            return $fs->chown($this->getPath(), $user);
674    }
675
676
677    /**
678     * Sets the mode of the file
679     * @param int $mode Ocatal mode.
680     */
681    public function setMode($mode) {
682        $fs = FileSystem::getFileSystem();
683        return $fs->chmod($this->getPath(), $mode);
684    }
685
686    /**
687     * Retrieve the mode of this file.
688     * @return int
689     */
690    public function getMode() {
691        return @fileperms($this->getPath());
692    }
693
694    /* -- Filesystem interface -- */
695
696    /**
697     * List the available filesystem roots.
698     *
699     * A particular platform may support zero or more hierarchically-organized
700     * file systems.  Each file system has a root  directory from which all
701     * other files in that file system can be reached.
702     * Windows platforms, for example, have a root directory for each active
703     * drive; UNIX platforms have a single root directory, namely "/".
704     * The set of available filesystem roots is affected by various system-level
705     * operations such the insertion or ejection of removable media and the
706     * disconnecting or unmounting of physical or virtual disk drives.
707     *
708     * This method returns an array of File objects that
709     * denote the root directories of the available filesystem roots.  It is
710     * guaranteed that the canonical pathname of any file physically present on
711     * the local machine will begin with one of the roots returned by this
712     * method.
713     *
714     * The canonical pathname of a file that resides on some other machine
715     * and is accessed via a remote-filesystem protocol such as SMB or NFS may
716     * or may not begin with one of the roots returned by this method.  If the
717     * pathname of a remote file is syntactically indistinguishable from the
718     * pathname of a local file then it will begin with one of the roots
719     * returned by this method.  Thus, for example, File objects
720     * denoting the root directories of the mapped network drives of a Windows
721     * platform will be returned by this method, while File
722     * objects containing UNC pathnames will not be returned by this method.
723     *
724     * @return  array File[] An array of File objects denoting the available
725     *          filesystem roots, or null if the set of roots
726     *          could not be determined.  The array will be empty if there are
727     *          no filesystem roots.
728     */
729    public function listRoots() {
730        $fs = FileSystem::getFileSystem();
731        return (array) $fs->listRoots();
732    }
733
734    /* -- Tempfile management -- */
735
736    /**
737     * Returns the path to the temp directory.
738     * @return string
739     */
740    public function getTempDir() {
741        return Phing::getProperty('php.tmpdir');
742    }
743
744    /**
745     * Static method that creates a unique filename whose name begins with
746     * $prefix and ends with $suffix in the directory $directory. $directory
747     * is a reference to a File Object.
748     * Then, the file is locked for exclusive reading/writing.
749     *
750     * @throws IOException
751     */
752    public static function createTempFile($prefix, $suffix, File $directory) {
753
754        // quick but efficient hack to create a unique filename ;-)
755        $result = null;
756        do {
757            $result = new File($directory, $prefix . substr(md5(time()), 0, 8) . $suffix);
758        } while (file_exists($result->getPath()));
759
760        $fs = FileSystem::getFileSystem();
761        $fs->createNewFile($result->getPath());
762        $fs->lock($result);
763
764        return $result;
765    }
766
767    /**
768     * If necessary, $File the lock on $File is removed and then the file is
769     * deleted
770     *
771     * @access      public
772     */
773    public function removeTempFile() {
774        $fs = FileSystem::getFileSystem();
775        // catch IO Exception
776        $fs->unlock($this);
777        $this->delete();
778    }
779
780
781    /* -- Basic infrastructure -- */
782
783    /**
784     * Compares two abstract pathnames lexicographically.  The ordering
785     * defined by this method depends upon the underlying system.  On UNIX
786     * systems, alphabetic case is significant in comparing pathnames; on Win32
787     * systems it is not.
788     *
789     * @param File $file Th file whose pathname sould be compared to the pathname of this file.
790     *
791     * @return int Zero if the argument is equal to this abstract pathname, a
792     *        value less than zero if this abstract pathname is
793     *        lexicographically less than the argument, or a value greater
794     *        than zero if this abstract pathname is lexicographically
795     *        greater than the argument
796     */
797    public function compareTo(File $file) {
798        $fs = FileSystem::getFileSystem();
799        return $fs->compare($this, $file);
800    }
801
802    /**
803     * Tests to see whether two File objects are equal.
804     * @return boolean
805     */
806    public function equals($obj) {
807        if (($obj !== null) && ($obj instanceof File)) {
808            return ($this->compareTo($obj) === 0);
809        }
810        return false;
811    }
812
813    /**
814     * Backwards compatibility -- use PHP5's native __tostring method.
815     * @deprecated
816     */
817    public function toString() {
818        return $this->getPath();
819    }
820
821    /**
822     * PHP5's semi-magic __toString() method.
823     * @return string
824     */
825    public function __toString() {
826        return $this->getPath();
827    }
828}