]> git.street.me.uk Git - andy/gpx.git/blob - src/libgpx/peekstream.php
Fix: PeakStream fails to function correctly after the first file
[andy/gpx.git] / src / libgpx / peekstream.php
1 <?php
2 /**
3  * peekstream.php
4  *
5  * Copyright 2018 Andy Street <andy@street.me.uk>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20  * MA 02110-1301, USA.
21  *
22  *
23  */
24
25 namespace libgpx;
26
27 /**
28  * A PHP stream wrapper with the ability to peek at the first read.
29  *
30  * Note: This class is used to workaround an issue with the `XMLReader` class.
31  *       It should not be called by code outside of libgpx.
32  *
33  * @internal
34  *
35  * @author Andy Street <andy@street.me.uk>
36  */
37 class PeekStream
38 {
39
40   /**
41    * All instances of this class with open streams.
42    *
43    * @var PeekStream[]
44    */
45   public static $instances = [];
46
47   /**
48    * The handle to the stream to read from.
49    *
50    * @var resource
51    */
52   protected $handle;
53
54   /**
55    * The stream path where data is loaded from.
56    *
57    * @var string
58    */
59   protected $path;
60
61   /**
62    * Cache of the first successful read from the stream.
63    *
64    * @var mixed
65    */
66   protected $first_read;
67
68   /**
69    * The context for this stream.
70    *
71    * Unused but set by PHP so included for completeness.
72    *
73    * @var resource
74    */
75   public $context;
76
77   /**
78    * Remove the protocol from a path.
79    *
80    * @param string $path The path including the protocol for this stream wrapper.
81    * @return string The path without the protocol.
82    */
83   protected function stripProtocol(string $path)
84   {
85     $pos = strpos($path, ':');
86     return ($pos === false ? null : substr($path, $pos + 3));
87   }
88
89   /**
90    * Open a new stream.
91    *
92    * @param string $path The path to open.
93    * @param string $mode The mode used to open the stream.
94    * @param int $options Streams API flags.
95    * @param string $opened_path The path that was actually opened.
96    * @return boolean If it was possible to open a stream.
97    */
98   public function stream_open(
99     string $path,
100     string $mode,
101     int $options,
102     &$opened_path
103   ) {
104     $this->path = $this->stripProtocol($path);
105     if ($this->path === null) return false;
106
107     if (!in_array($mode, ['r', 'rb'])) return false;
108
109     $this->handle = fopen($this->path, $mode);
110     if (!is_resource($this->handle)) return false;
111     self::$instances[] = $this;
112
113     return true;
114   }
115
116   /**
117    * Close a stream.
118    *
119    * @return boolean If the stream was closed successfully.
120    */
121   public function stream_close()
122   {
123     foreach (self::$instances as $key => $value) {
124       if ($value === $this) {
125         unset(self::$instances[$key]);
126         break;
127       }
128     }
129     return fclose($this->handle);
130   }
131
132   /**
133    * Check if the pointer is at the end of the stream.
134    *
135    * @return boolean If the pointer is at the end of the stream.
136    */
137   public function stream_eof()
138   {
139     return feof($this->handle);
140   }
141
142   /**
143    * Read data from the stream.
144    *
145    * @param int $count The number of bytes to read.
146    * @return string The bytes read or false on failure.
147    */
148   public function stream_read(int $count)
149   {
150     $data = fread($this->handle, $count);
151     if ($data !== false && $this->first_read === null)
152       $this->first_read = $data;
153     return $data;
154   }
155
156   /**
157    * Fetch file information.
158    *
159    * @param string $path The path to check.
160    * @param int $flags PHP streams API flags.
161    * @return array A `stat` compatible array.
162    */
163   public function url_stat(string $path, int $flags)
164   {
165     $path = $this->stripProtocol($path);
166     if ($path === null) return 0;
167     return stat($path);
168   }
169
170   /**
171    * Fetch the contents of the first read from a stream.
172    *
173    * @param string $path The path to peek at.
174    * @return string|null The first read or null if not available.
175    */
176   public static function peek(string $path)
177   {
178     $result = null;
179     $instance = null;
180     foreach (self::$instances as $i) {
181       if ($i->path == $path) {
182         $instance = $i;
183         break;
184       }
185     }
186     if ($instance instanceof PeekStream && is_string($instance->first_read)) {
187       $result = $instance->first_read;
188       $instance->first_read = false;
189     }
190     return $result;
191   }
192
193 }