Ticket #23: FileUtils.php

File FileUtils.php, 11.8 kB (added by norman@sefiroth.de, 3 years ago)
Line 
1 <?php
2 /*
3  *  $Id: FileUtils.php,v 1.10 2005/05/26 13:10:53 mrook Exp $
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
22 include_once 'phing/system/lang/Character.php';
23 include_once 'phing/util/StringHelper.php';
24 include_once 'phing/system/io/BufferedReader.php';
25 include_once 'phing/system/io/BufferedWriter.php';
26 include_once 'phing/filters/util/ChainReaderHelper.php';
27 include_once 'phing/system/io/PhingFile.php';
28
29 /**
30  * File utility class.
31  * - handles os independent stuff etc
32  * - mapper stuff
33  * - filter stuff
34  *
35  * @package  phing.util
36  * @version  $Revision: 1.10 $
37  */
38 class FileUtils {
39         
40     /**
41      * Returns a new Reader with filterchains applied.  If filterchains are empty,
42      * simply returns passed reader.
43      *
44      * @param Reader $in Reader to modify (if appropriate).
45      * @param array &$filterChains filter chains to apply.
46      * @param Project $project
47      * @return Reader Assembled Reader (w/ filter chains).
48      */
49     function getChainedReader(Reader $in, &$filterChains, Project $project) {
50         if (!empty($filterChains)) {
51             $crh = new ChainReaderHelper();
52             $crh->setBufferSize(65536); // 64k buffer, but isn't being used (yet?)
53             $crh->setPrimaryReader($in);
54             $crh->setFilterChains($filterChains);
55             $crh->setProject($project);
56             $rdr = $crh->getAssembledReader();
57             return $rdr;
58         } else {
59             return $in;
60         }
61     }
62     
63     /**
64      * Copies a file using filter chains.
65      *
66      * @param PhingFile $sourceFile
67      * @param PhingFile $destFile
68      * @param boolean $overwrite
69      * @param boolean $preserveLastModified
70      * @param array $filterChains
71      * @param Project $project
72      * @return void
73      */
74     function copyFile(PhingFile $sourceFile, PhingFile $destFile, $overwrite = false, $preserveLastModified = true, &$filterChains = null, Project $project) {
75       
76         if ($overwrite || !$destFile->exists() || $destFile->lastModified() < $sourceFile->lastModified()) {
77             if ($destFile->exists() && $destFile->isFile()) {
78                 $destFile->delete();
79             }
80
81             // ensure that parent dir of dest file exists!
82             $parent = $destFile->getParentFile();
83             if ($parent !== null && !$parent->exists()) {
84                 $parent->mkdirs();
85             }
86
87             if ((is_array($filterChains)) && (!empty($filterChains))) {
88                 
89                 $in = self::getChainedReader(new BufferedReader(new FileReader($sourceFile)), $filterChains, $project);
90                 $out = new BufferedWriter(new FileWriter($destFile));               
91                 
92                 // New read() methods returns a big buffer.               
93                 while(-1 !== ($buffer = $in->read())) { // -1 indicates EOF
94                     $out->write($buffer);
95                 }
96                 
97                 if ( $in !== null )
98                     $in->close();
99                 if ( $out !== null )
100                     $out->close();
101             } else {
102                 // simple copy (no filtering)
103                 $sourceFile->copyTo($destFile);
104             }
105
106             if ($preserveLastModified) {
107                 $destFile->setLastModified($sourceFile->lastModified());
108             }
109
110         }
111     }
112
113     /**
114      * Append a file using filter chains.
115      *
116      * @param PhingFile $sourceFile
117      * @param PhingFile $destFile
118      * @param boolean $force
119      * @param array $filterChains
120      * @param Project $project
121      * @return void
122      */
123     function appendFile(PhingFile $sourceFile, PhingFile $destFile, $force = true, &$filterChains = null, Project $project) {
124       
125         if ($force || !$destFile->exists()) {
126
127             // ensure that parent dir of dest file exists!
128             $parent = $destFile->getParentFile();
129             if ($parent !== null && !$parent->exists()) {
130                 $parent->mkdirs();
131             }
132
133             if ((is_array($filterChains)) && (!empty($filterChains))) {
134                 
135                 $in = self::getChainedReader(new BufferedReader(new FileReader($sourceFile)), $filterChains, $project);
136                 $out = new BufferedWriter(new FileWriter($destFile, true));               
137                 
138                 // New read() methods returns a big buffer.               
139                 while(-1 !== ($buffer = $in->read())) { // -1 indicates EOF
140                     $out->write($buffer);
141                 }
142                 
143                 if ( $in !== null )
144                     $in->close();
145                 if ( $out !== null )
146                     $out->close();
147             } else {
148                 // simple copy (no filtering)
149                 $sourceFile->appendTo($destFile);
150             }
151
152         }
153     }
154
155
156     /**
157      * Interpret the filename as a file relative to the given file -
158      * unless the filename already represents an absolute filename.
159      *
160      * @param  $file the "reference" file for relative paths. This
161      *         instance must be an absolute file and must not contain
162      *         ./ or ../ sequences (same for \ instead of /).
163      * @param  $filename a file name
164      *
165      * @return PhingFile A PhingFile object pointing to an absolute file that doesn't contain ./ or ../ sequences
166      *         and uses the correct separator for the current platform.
167      */
168     function resolveFile($file, $filename) {
169         // remove this and use the static class constant File::seperator
170         // as soon as ZE2 is ready
171         $fs = FileSystem::getFileSystem();
172
173         $filename = str_replace('/', $fs->getSeparator(), str_replace('\\', $fs->getSeparator(), $filename));
174
175         // deal with absolute files
176         if (StringHelper::startsWith($fs->getSeparator(), $filename) ||
177                 (strlen($filename) >= 2 && Character::isLetter($filename{0}) && $filename{1} === ':')) {
178             return new PhingFile($this->normalize($filename));
179         }
180
181         if (strlen($filename) >= 2 && Character::isLetter($filename{0}) && $filename{1} === ':') {
182             return new PhingFile($this->normalize($filename));
183         }
184
185         $helpFile = new PhingFile($file->getAbsolutePath());
186
187         $tok = strtok($filename, $fs->getSeparator());
188         while ($tok !== false) {
189             $part = $tok;
190             if ($part === '..') {
191                 $parentFile = $helpFile->getParent();
192                 if ($parentFile === null) {
193                     $msg = "The file or path you specified ($filename) is invalid relative to ".$file->getPath();
194                     throw new IOException($msg);
195                 }
196                 $helpFile = new PhingFile($parentFile);
197             } else if ($part === '.') {
198                 // Do nothing here
199             } else {
200                 $helpFile = new PhingFile($helpFile, $part);
201             }
202             $tok = strtok($fs->getSeparator());
203         }
204         return new PhingFile($helpFile->getAbsolutePath());
205     }
206
207     /**
208      * Normalize the given absolute path.
209      *
210      * This includes:
211      *   - Uppercase the drive letter if there is one.
212      *   - Remove redundant slashes after the drive spec.
213      *   - resolve all ./, .\, ../ and ..\ sequences.
214      *   - DOS style paths that start with a drive letter will have
215      *     \ as the separator.
216      * @param string $path Path to normalize.
217      * @return string
218      */
219     function normalize($path) {
220     
221         $path = (string) $path;
222         $orig = $path;
223
224         $path = str_replace('/', DIRECTORY_SEPARATOR, str_replace('\\', DIRECTORY_SEPARATOR, $path));
225
226         // make sure we are dealing with an absolute path
227         if (!StringHelper::startsWith(DIRECTORY_SEPARATOR, $path)
228                 && !(strlen($path) >= 2 && Character::isLetter($path{0}) && $path{1} === ':')) {
229             throw new IOException("$path is not an absolute path");
230         }
231
232         $dosWithDrive = false;
233         $root = null;
234
235         // Eliminate consecutive slashes after the drive spec
236
237         if (strlen($path) >= 2 && Character::isLetter($path{0}) && $path{1} === ':') {
238             $dosWithDrive = true;
239
240             $ca = str_replace('/', '\\', $path);
241             $ca = StringHelper::toCharArray($ca);
242
243             $path = strtoupper($ca[0]).':';
244             
245             for ($i=2, $_i=count($ca); $i < $_i; $i++) {
246                 if (($ca[$i] !== '\\') ||
247                         ($ca[$i] === '\\' && $ca[$i - 1] !== '\\')
248                    ) {
249                     $path .= $ca[$i];
250                 }
251             }
252         
253             $path = str_replace('\\', DIRECTORY_SEPARATOR, $path);
254
255             if (strlen($path) == 2) {
256                 $root = $path;
257                 $path = "";
258             } else {
259                 $root = substr($path, 0, 3);
260                 $path = substr($path, 3);
261             }
262
263         } else {
264             if (strlen($path) == 1) {
265                 $root = DIRECTORY_SEPARATOR;
266                 $path = "";
267             } else if ($path{1} == DIRECTORY_SEPARATOR) {
268                 // UNC drive
269                 $root = DIRECTORY_SEPARATOR.DIRECTORY_SEPARATOR;
270                 $path = substr($path, 2);
271             }
272             else {
273                 $root = DIRECTORY_SEPARATOR;
274                 $path = substr($path, 1);
275             }
276         }
277
278         $s = array();
279         array_push($s, $root);
280         $tok = strtok($path, DIRECTORY_SEPARATOR);
281         while ($tok !== false) {           
282             $thisToken = $tok;
283             if ("." === $thisToken) {
284                 $tok = strtok(DIRECTORY_SEPARATOR);
285                 continue;
286             } elseif (".." === $thisToken) {
287                 if (count($s) < 2) {
288                     // using '..' in path that is too short
289                     throw new IOException("Cannot resolve path: $orig");
290                 } else {
291                     array_pop($s);
292                 }
293             } else { // plain component
294                 array_push($s, $thisToken);
295             }
296             $tok = strtok(DIRECTORY_SEPARATOR);
297         }
298
299         $sb = "";
300         for ($i=0,$_i=count($s); $i < $_i; $i++) {
301             if ($i > 1) {
302                 // not before the filesystem root and not after it, since root
303                 // already contains one
304                 $sb .= DIRECTORY_SEPARATOR;
305             }
306             $sb .= (string) $s[$i];
307         }
308
309
310         $path = (string) $sb;
311         if ($dosWithDrive === true) {
312             $path = str_replace('/', '\\', $path);
313         }
314         return $path;
315     }
316     
317     /**
318      * @return boolean Whether contents of two files is the same.
319      */
320     public function contentEquals(PhingFile $file1, PhingFile $file2) {
321         
322         if (!($file1->exists() || $file2->exists())) {
323             return false;
324         }
325
326         if (!($file1->canRead() || $file2->canRead())) {
327             return false;
328         }
329         
330         $c1 = file_get_contents($file1->getAbsolutePath());
331         $c2 = file_get_contents($file2->getAbsolutePath());
332         
333         return trim($c1) == trim($c2);   
334     }
335     
336 }
337 ?>
338