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