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