f6752b27f0c9d93bbf2f4029232b28d11092b569
[core.git] / inc / classes / main / file_directories / io_stream / class_FileIoStream.php
1 <?php
2 /**
3  * An universal class for file input/output streams.
4  *
5  * @author              Roland Haeder <webmaster@shipsimu.org>
6  * @version             0.0.0
7  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2015 Core Developer Team
8  * @license             GNU GPL 3.0 or any newer version
9  * @link                http://www.shipsimu.org
10  *
11  * This program is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation, either version 3 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program. If not, see <http://www.gnu.org/licenses/>.
23  */
24 class FileIoStream extends BaseFrameworkSystem implements FileInputStreamer, FileOutputStreamer {
25         /**
26          * File header indicator
27          */
28         const FILE_IO_FILE_HEADER_ID = '@head';
29
30         /**
31          * Data block indicator
32          */
33         const FILE_IO_DATA_BLOCK_ID = '@data';
34
35         /**
36          * Separator #1
37          */
38         const FILE_IO_CHUNKER = ':';
39
40         /**
41          * Separator #2
42          */
43         const FILE_IO_SEPARATOR = '^';
44
45         /**
46          * Protected constructor
47          */
48         protected function __construct () {
49                 // Call parent constructor
50                 parent::__construct(__CLASS__);
51         }
52
53         /**
54          * Create a file IO stream. This is a class for performing all actions
55          * on files like creating, deleting and loading them.
56          *
57          * @return      $ioInstance     An instance of FileIoStream
58          */
59         public static final function createFileIoStream () {
60                 // Create new instance
61                 $ioInstance = new FileIoStream();
62
63                 // Return the instance
64                 return $ioInstance;
65         }
66
67         /**
68          * Saves data to a given local file and create missing directory structures
69          *
70          * @param       $fileName       The file name for the to be saved file
71          * @param       $dataArray      The data we shall store to the file
72          * @return      void
73          * @see         FileOutputStreamer
74          * @todo        This method needs heavy rewrite
75          */
76         public final function saveFile ($fileName, array $dataArray) {
77                 // Try it five times
78                 $dirName = '';
79                 $fileInstance = NULL;
80
81                 for ($idx = 0; $idx < 5; $idx++) {
82                         // Get a file output pointer
83                         try {
84                                 $fileInstance = ObjectFactory::createObjectByConfiguredName('file_raw_output_class', array($fileName, 'wb'));
85                         } catch (FileIoException $e) {
86                                 // Bail out
87                                 ApplicationEntryPoint::app_exit('The application has made a fatal error. Exception: ' $e->__toString() . ' with message: ' . $e->getMessage());
88                         }
89                 } // END - for
90
91                 // Write a header information for validation purposes
92                 $fileInstance->writeToFile(sprintf('%s%s%s%s%s%s%s%s%s' . PHP_EOL,
93                         self::FILE_IO_FILE_HEADER_ID,
94                         self::FILE_IO_SEPARATOR,
95                         $dataArray[0],
96                         self::FILE_IO_CHUNKER,
97                         time(),
98                         self::FILE_IO_CHUNKER,
99                         strlen($dataArray[1]),
100                         self::FILE_IO_CHUNKER,
101                         md5($dataArray[1])
102                 ));
103
104                 // Encode the (maybe) binary stream with Base64
105                 $b64Stream = base64_encode($dataArray[1]);
106
107                 // write the data line by line
108                 $line = str_repeat(' ', 50); $idx = 0;
109                 while (strlen($line) == 50) {
110                         // Get 50 chars or less
111                         $line = substr($b64Stream, $idx, 50);
112
113                         // Save it to the stream
114                         $fileInstance->writeToFile(sprintf('%s%s%s%s%s' . PHP_EOL,
115                                 self::FILE_IO_DATA_BLOCK_ID,
116                                 self::FILE_IO_SEPARATOR,
117                                 $line,
118                                 self::FILE_IO_CHUNKER,
119                                 md5($line)
120                         ));
121
122                         // Advance to the next 50-chars block
123                         $idx += 50;
124                 } // END - while
125
126                 // Close the file
127                 unset($fileInstance);
128         }
129
130         /**
131          * Reads from a local file
132          *
133          * @param       $fqfn   The full-qualified file-name which we shall load
134          * @return      $array  An array with the element 'header' and 'data'
135          * @see         FileInputStreamer
136          */
137         public final function loadFileContents ($fqfn) {
138                 // Initialize some variables and arrays
139                 $inputBuffer = '';
140                 $lastBuffer = '';
141                 $header = array();
142                 $data = array();
143                 $readData = ''; // This will contain our read data
144
145                 // Get a file input handler
146                 $fileInstance = ObjectFactory::createObjectByConfiguredName('file_raw_input_class', array($fqfn));
147
148                 // Read all it's contents (we very and transparently decompress it below)
149                 while ($readRawLine = $fileInstance->readFromFile()) {
150                         // Add the read line to the buffer
151                         $inputBuffer .= $readRawLine;
152
153                         // Break infinite loop maybe caused by the input handler
154                         if ($lastBuffer == $inputBuffer) {
155                                 break;
156                         } // END - if
157
158                         // Remember last read line for avoiding possible infinite loops
159                         $lastBuffer = $inputBuffer;
160                 } // END - while
161
162                 // Close directory handle
163                 unset($fileInstance);
164
165                 // Convert it into an array
166                 $inputBuffer = explode(chr(10), $inputBuffer);
167
168                 // Now process the read lines and verify it's content
169                 foreach ($inputBuffer as $rawLine) {
170                         // Trim it a little but not the leading spaces/tab-stops
171                         $rawLine = rtrim($rawLine);
172
173                         // Analyze this line
174                         if (substr($rawLine, 0, 5) == self::FILE_IO_FILE_HEADER_ID) {
175                                 // Header found, so let's extract it
176                                 $header = explode(self::FILE_IO_SEPARATOR, $rawLine);
177                                 $header = trim($header[1]);
178
179                                 // Now we must convert it again into an array
180                                 $header = explode(self::FILE_IO_CHUNKER, $header);
181
182                                 // Is the header (maybe) valid?
183                                 if (count($header) != 4) {
184                                         // Throw an exception
185                                         throw new InvalidArrayCountException(array($this, 'header', count($header), 4), self::EXCEPTION_ARRAY_HAS_INVALID_COUNT);
186                                 } // END - if
187                         } elseif (substr($rawLine, 0, 5) == self::FILE_IO_DATA_BLOCK_ID) {
188                                 // Is a data line!
189                                 $data = explode(self::FILE_IO_SEPARATOR, $rawLine);
190                                 $data = $data[1];
191
192                                 // First element is the data, second the MD5 checksum
193                                 $data = explode(self::FILE_IO_CHUNKER, $data);
194
195                                 // Validate the read line
196                                 if (count($data) == 2) {
197                                         if (md5($data[0]) != $data[1]) {
198                                                 // MD5 hash did not match!
199                                                 throw new InvalidMD5ChecksumException(array($this, md5($data[0]), $data[1]), self::EXCEPTION_MD5_CHECKSUMS_MISMATCH);
200                                         } // END - if
201                                 } else {
202                                         // Invalid count!
203                                         throw new InvalidArrayCountException(array($this, 'data', count($data), 2), self::EXCEPTION_ARRAY_HAS_INVALID_COUNT);
204                                 }
205
206                                 // Add this to the readData string
207                                 $readData .= $data[0];
208                         } else {
209                                 // Other raw lines than header/data tagged lines and re-add the new-line char
210                                 $readData .= $rawLine . PHP_EOL;
211                         }
212                 } // END - foreach
213
214                 // Was raw lines read and no header/data?
215                 if ((!empty($readData)) && (count($header) == 0) && (count($data) == 0)) {
216                         // Return raw lines back
217                         return $readData;
218                 } // END - if
219
220                 // Was a header found?
221                 if (count($header) != 4) {
222                         // Throw an exception
223                         throw new InvalidArrayCountException(array($this, 'header', count($header), 4), self::EXCEPTION_ARRAY_HAS_INVALID_COUNT);
224                 } // END - if
225
226                 // Decode all from Base64
227                 $readData = @base64_decode($readData);
228
229                 // Does the size match?
230                 if (strlen($readData) != $header[2]) {
231                         // Size did not match
232                         throw new InvalidDataLengthException(array($this, strlen($readData), $header[2]), self::EXCEPTION_UNEXPECTED_STRING_SIZE);
233                 } // END - if
234
235                 // Validate the decoded data with the final MD5 hash
236                 if (md5($readData) != $header[3]) {
237                         // MD5 hash did not match!
238                         throw new InvalidMD5ChecksumException(array($this, md5($readData), $header[3]), self::EXCEPTION_MD5_CHECKSUMS_MISMATCH);
239                 } // END - if
240
241                 // Return all in an array
242                 return array(
243                         'header' => $header,
244                         'data'   => $readData
245                 );
246         }
247
248         /**
249          * Streams the data and maybe does something to it
250          *
251          * @param       $data   The data (string mostly) to "stream"
252          * @return      $data   The data (string mostly) to "stream"
253          * @throws      UnsupportedOperationException   If this method is called
254          */
255         public function streamData ($data) {
256                 self::createDebugInstance(__CLASS__)->debugOutput('Unhandled ' . strlen($data) . ' bytes in this stream.');
257                 throw new UnsupportedOperationException(array($this, __FUNCTION__), self::EXCEPTION_UNSPPORTED_OPERATION);
258         }
259
260         /**
261          * Determines seek position
262          *
263          * @return      $seekPosition   Current seek position
264          * @todo        0% done
265          */
266         public function determineSeekPosition () {
267                 $this->partialStub();
268         }
269
270         /**
271          * Seek to given offset (default) or other possibilities as fseek() gives.
272          *
273          * @param       $offset         Offset to seek to (or used as "base" for other seeks)
274          * @param       $whence         Added to offset (default: only use offset to seek to)
275          * @return      $status         Status of file seek: 0 = success, -1 = failed
276          */
277         public function seek ($offset, $whence = SEEK_SET) {
278                 $this->partialStub('offset=' . $offset . ',whence=' . $whence);
279         }
280
281         /**
282          * Size of file stack
283          *
284          * @return      $size   Size (in bytes) of file
285          */
286         public function size () {
287                 $this->partialStub();
288         }
289 }
290
291 // [EOF]
292 ?>