3 * An universal class for file input/output streams.
5 * @author Roland Haeder <webmaster@shipsimu.org>
7 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2013 Core Developer Team
8 * @license GNU GPL 3.0 or any newer version
9 * @link http://www.shipsimu.org
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.
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.
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/>.
24 class FileIoStream extends BaseFrameworkSystem implements FileInputStreamer, FileOutputStreamer {
26 * File header indicator
28 const FILE_IO_FILE_HEADER_ID = '@head';
31 * Data block indicator
33 const FILE_IO_DATA_BLOCK_ID = '@data';
38 const FILE_IO_CHUNKER = ':';
43 const FILE_IO_SEPARATOR = '^';
46 * Protected constructor
48 protected function __construct () {
49 // Call parent constructor
50 parent::__construct(__CLASS__);
54 * Create a file IO stream. This is a class for performing all actions
55 * on files like creating, deleting and loading them.
57 * @return $ioInstance An instance of FileIoStream
59 public static final function createFileIoStream () {
60 // Create new instance
61 $ioInstance = new FileIoStream();
63 // Return the instance
68 * Saves data to a given local file and create missing directory structures
70 * @param $fileName The file name for the to be saved file
71 * @param $dataArray The data we shall store to the file
73 * @see FileOutputStreamer
74 * @todo This method needs heavy rewrite
76 public final function saveFile ($fileName, array $dataArray) {
78 $dirName = ''; $fileInstance = NULL;
79 for ($idx = 0; $idx < 5; $idx++) {
80 // Get a file output pointer
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);
95 // Write a header information for validation purposes
96 $fileInstance->writeToFile(sprintf("%s%s%s%s%s%s%s%s%s\n",
97 self::FILE_IO_FILE_HEADER_ID,
98 self::FILE_IO_SEPARATOR,
100 self::FILE_IO_CHUNKER,
102 self::FILE_IO_CHUNKER,
103 strlen($dataArray[1]),
104 self::FILE_IO_CHUNKER,
108 // Encode the (maybe) binary stream with Base64
109 $b64Stream = base64_encode($dataArray[1]);
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);
117 // Save it to the stream
118 $fileInstance->writeToFile(sprintf("%s%s%s%s%s\n",
119 self::FILE_IO_DATA_BLOCK_ID,
120 self::FILE_IO_SEPARATOR,
122 self::FILE_IO_CHUNKER,
126 // Advance to the next 50-chars block
131 $fileInstance->closeFile();
135 * Reads from a local file
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
141 public final function loadFileContents ($fqfn) {
142 // Initialize some variables and arrays
147 $readData = ''; // This will contain our read data
149 // Get a file input handler
150 $fileInstance = FrameworkFileInputPointer::createFrameworkFileInputPointer($fqfn);
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;
157 // Break infinite loop maybe caused by the input handler
158 if ($lastBuffer == $inputBuffer) break;
160 // Remember last read line for avoiding possible infinite loops
161 $lastBuffer = $inputBuffer;
164 // Close directory handle
165 $fileInstance->closeFile();
167 // Convert it into an array
168 $inputBuffer = explode(chr(10), $inputBuffer);
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);
176 if (substr($rawLine, 0, 5) == self::FILE_IO_FILE_HEADER_ID) {
177 // Header found, so let's extract it
178 $header = explode(self::FILE_IO_SEPARATOR, $rawLine);
179 $header = trim($header[1]);
181 // Now we must convert it again into an array
182 $header = explode(self::FILE_IO_CHUNKER, $header);
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);
189 } elseif (substr($rawLine, 0, 5) == self::FILE_IO_DATA_BLOCK_ID) {
191 $data = explode(self::FILE_IO_SEPARATOR, $rawLine);
194 // First element is the data, second the MD5 checksum
195 $data = explode(self::FILE_IO_CHUNKER, $data);
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);
205 throw new InvalidArrayCountException(array($this, 'data', count($data), 2), self::EXCEPTION_ARRAY_HAS_INVALID_COUNT);
208 // Add this to the readData string
209 $readData .= $data[0];
211 // Other raw lines than header/data tagged lines and re-add the new-line char
212 $readData .= $rawLine . PHP_EOL;
216 // Was raw lines read and no header/data?
217 if ((!empty($readData)) && (count($header) == 0) && (count($data) == 0)) {
218 // Return raw lines back
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);
228 // Decode all from Base64
229 $readData = @base64_decode($readData);
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);
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);
243 // Return all in an array
251 * Streams the data and maybe does something to it
253 * @param $data The data (string mostly) to "stream"
254 * @return $data The data (string mostly) to "stream"
255 * @throws UnsupportedOperationException If this method is called
257 public function streamData ($data) {
258 self::createDebugInstance(__CLASS__)->debugOutput('Unhandled ' . strlen($data) . ' bytes in this stream.');
259 throw new UnsupportedOperationException(array($this, __FUNCTION__), self::EXCEPTION_UNSPPORTED_OPERATION);