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