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