dda95f0f65eb338af2d991dca308e9ff8e57661b
[core.git] / inc / classes / main / io / class_FileIoStream.php
1 <?php
2 /**
3  * An universal class for file input/output streams.
4  *
5  * @author              Roland Haeder <webmaster@ship-simu.org>
6  * @version             0.0.0
7  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 Core Developer Team
8  * @license             GNU GPL 3.0 or any newer version
9  * @link                http://www.ship-simu.org
10  *
11  * This program is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation, either version 3 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program. If not, see <http://www.gnu.org/licenses/>.
23  */
24 class FileIoStream extends BaseFrameworkSystem implements FileInputStreamer, FileOutputStreamer {
25         /**
26          * File header indicator
27          */
28         private $fileHeader = '@head';
29
30         /**
31          * Data block indicator
32          */
33         private $dataBlock = '@data';
34
35         /**
36          * Seperator #1
37          */
38         private $chunker = ':';
39
40         /**
41          * Seperator #2
42          */
43         private $seperator = '^';
44
45         /**
46          * Protected constructor
47          */
48         protected function __construct () {
49                 // Call parent constructor
50                 parent::__construct(__CLASS__);
51         }
52
53         /**
54          * Create a file IO stream. This is a class for performing all actions
55          * on files like creating, deleting and loading them.
56          *
57          * @return      $ioInstance     An instance of FileIoStream
58          */
59         public final static function createFileIoStream () {
60                 // Create new instance
61                 $ioInstance = new FileIoStream();
62
63                 // Return the instance
64                 return $ioInstance;
65         }
66
67         /**
68          * Saves data to a given local file and create missing directory structures
69          *
70          * @param       $fileName       The file name for the to be saved file
71          * @param       $dataArray      The data we shall store to the file
72          * @return      void
73          * @see         FileOutputStreamer
74          * @todo        This method needs heavy rewrite
75          */
76         public final function saveFile ($fileName, $dataArray) {
77                 // Try it five times
78                 $dirName = ''; $fileInstance = null;
79                 for ($idx = 0; $idx < 5; $idx++) {
80                         // Get a file output pointer
81                         try {
82                                 $fileInstance = FrameworkFileOutputPointer::createFrameworkFileOutputPointer($fileName, 'w');
83                         } catch (FileIoException $e) {
84                                 // Create missing directory
85                                 $dirName = dirname($fileName);
86                                 for ($idx2 = 0; $idx2 < (2 - $idx); $idx2++) {
87                                         $dirName = dirname($dirName);
88                                 } // END - for
89
90                                 // Try to create it
91                                 @mkdir($dirName);
92                         }
93                 } // END - for
94
95                 // Write a header information for validation purposes
96                 $fileInstance->writeToFile(sprintf("%s%s%s%s%s%s%s%s%s\n",
97                         $this->fileHeader,
98                         $this->seperator,
99                         $dataArray[0],
100                         $this->chunker,
101                         time(),
102                         $this->chunker,
103                         strlen($dataArray[1]),
104                         $this->chunker,
105                         md5($dataArray[1])
106                 ));
107
108                 // Encode the (maybe) binary stream with Base64
109                 $b64Stream = base64_encode($dataArray[1]);
110
111                 // write the data line by line
112                 $line = str_repeat(' ', 50); $idx = 0;
113                 while (strlen($line) == 50) {
114                         // Get 50 chars or less
115                         $line = substr($b64Stream, $idx, 50);
116
117                         // Save it to the stream
118                         $fileInstance->writeToFile(sprintf("%s%s%s%s%s\n",
119                                 $this->dataBlock,
120                                 $this->seperator,
121                                 $line,
122                                 $this->chunker,
123                                 md5($line)
124                         ));
125
126                         // Advance to the next 50-chars block
127                         $idx += 50;
128                 } // END - while
129
130                 // Close the file
131                 $fileInstance->closeFile();
132         }
133
134         /**
135          * Reads from a local file
136          *
137          * @param               $fqfn   The full-qualified file-name which we shall load
138          * @return      $array  An array with the element 'header' and 'data'
139          * @see         FileInputStreamer
140          */
141         public final function loadFileContents ($fqfn) {
142                 // Initialize some variables and arrays
143                 $inputBuffer = '';
144                 $lastBuffer = '';
145                 $header = array();
146                 $data = array();
147                 $readData = ''; // This will contain our read data
148
149                 // Get a file input handler
150                 $fileInstance = FrameworkFileInputPointer::createFrameworkFileInputPointer($fqfn);
151
152                 // Read all it's contents (we very and transparently decompress it below)
153                 while ($readRawLine = $fileInstance->readFromFile()) {
154                         // Add the read line to the buffer
155                         $inputBuffer .= $readRawLine;
156
157                         // Break infinite loop maybe caused by the input handler
158                         if ($lastBuffer == $inputBuffer) break;
159
160                         // Remember last read line for avoiding possible infinite loops
161                         $lastBuffer = $inputBuffer;
162                 }
163
164                 // Close directory handle
165                 $fileInstance->closeFile();
166
167                 // Convert it into an array
168                 $inputBuffer = explode("\n", $inputBuffer);
169
170                 // Now process the read lines and verify it's content
171                 foreach ($inputBuffer as $rawLine) {
172                         // Trim it a little but not the leading spaces/tab-stops
173                         $rawLine = rtrim($rawLine);
174
175                         // Analyze this line
176                         if (substr($rawLine, 0, 5) == $this->fileHeader) {
177                                 // Header found, so let's extract it
178                                 $header = explode($this->seperator, $rawLine);
179                                 $header = trim($header[1]);
180
181                                 // Now we must convert it again into an array
182                                 $header = explode($this->chunker, $header);
183
184                                 // Is the header (maybe) valid?
185                                 if (count($header) != 4) {
186                                         // Throw an exception
187                                         throw new InvalidArrayCountException(array($this, 'header', count($header), 4), self::EXCEPTION_ARRAY_HAS_INVALID_COUNT);
188                                 }
189                         } elseif (substr($rawLine, 0, 5) == $this->dataBlock) {
190                                 // Is a data line!
191                                 $data = explode($this->seperator, $rawLine);
192                                 $data = $data[1];
193
194                                 // First element is the data, second the MD5 checksum
195                                 $data = explode($this->chunker, $data);
196
197                                 // Validate the read line
198                                 if (count($data) == 2) {
199                                         if (md5($data[0]) != $data[1]) {
200                                                 // MD5 hash did not match!
201                                                 throw new InvalidMD5ChecksumException(array($this, md5($data[0]), $data[1]), self::EXCEPTION_MD5_CHECKSUMS_MISMATCH);
202                                         }
203                                 } else {
204                                         // Invalid count!
205                                         throw new InvalidArrayCountException(array($this, "data", count($data), 2), self::EXCEPTION_ARRAY_HAS_INVALID_COUNT);
206                                 }
207
208                                 // Add this to the readData string
209                                 $readData .= $data[0];
210                         } else {
211                                 // Other raw lines than header/data tagged lines and re-add the new-line char
212                                 $readData .= $rawLine . "\n";
213                         }
214                 }
215
216                 // Was raw lines read and no header/data?
217                 if ((!empty($readData)) && (count($header) == 0) && (count($data) == 0)) {
218                         // Return raw lines back
219                         return $readData;
220                 }
221
222                 // Was a header found?
223                 if (count($header) != 4) {
224                         // Throw an exception
225                         throw new InvalidArrayCountException(array($this, 'header', count($header), 4), self::EXCEPTION_ARRAY_HAS_INVALID_COUNT);
226                 }
227
228                 // Decode all from Base64
229                 $readData = @base64_decode($readData);
230
231                 // Does the size match?
232                 if (strlen($readData) != $header[2]) {
233                         // Size did not match
234                         throw new InvalidDataLengthException(array($this, strlen($readData), $header[2]), self::EXCEPTION_UNEXPECTED_STRING_SIZE);
235                 }
236
237                 // Validate the decoded data with the final MD5 hash
238                 if (md5($readData) != $header[3]) {
239                         // MD5 hash did not match!
240                         throw new InvalidMD5ChecksumException(array($this, md5($readData), $header[3]), self::EXCEPTION_MD5_CHECKSUMS_MISMATCH);
241                 }
242
243                 // Return all in an array
244                 return array(
245                         'header' => $header,
246                         'data'   => $readData
247                 );
248         }
249 }
250
251 // [EOF]
252 ?>