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