3 namespace CoreFramework\Stream\Filesystem;
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;
15 * An universal class for file input/output streams.
17 * @author Roland Haeder <webmaster@shipsimu.org>
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
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.
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.
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/>.
36 class FileIoStream extends BaseFrameworkSystem implements FileInputStreamer, FileOutputStreamer {
38 * File header indicator
40 const FILE_IO_FILE_HEADER_ID = '@head';
43 * Data block indicator
45 const FILE_IO_DATA_BLOCK_ID = '@data';
50 const FILE_IO_CHUNKER = ':';
55 const FILE_IO_SEPARATOR = '^';
58 * Protected constructor
60 protected function __construct () {
61 // Call parent constructor
62 parent::__construct(__CLASS__);
66 * Create a file IO stream. This is a class for performing all actions
67 * on files like creating, deleting and loading them.
69 * @return $ioInstance An instance of a FileIoStream class
71 public static final function createFileIoStream () {
72 // Create new instance
73 $ioInstance = new FileIoStream();
75 // Return the instance
80 * Saves data to a given local file and create missing directory structures
82 * @param $fileName The file name for the to be saved file
83 * @param $dataArray The data we shall store to the file
85 * @see FileOutputStreamer
86 * @todo This method needs heavy rewrite
88 public final function saveFile ($fileName, array $dataArray) {
93 for ($idx = 0; $idx < 5; $idx++) {
94 // Get a file output pointer
96 $fileInstance = ObjectFactory::createObjectByConfiguredName('file_raw_output_class', array($fileName, 'wb'));
97 } catch (FileNotFoundException $e) {
99 ApplicationEntryPoint::exitApplication('The application has made a fatal error. Exception: ' . $e->__toString() . ' with message: ' . $e->getMessage());
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,
108 self::FILE_IO_CHUNKER,
110 self::FILE_IO_CHUNKER,
111 strlen($dataArray[1]),
112 self::FILE_IO_CHUNKER,
116 // Encode the (maybe) binary stream with Base64
117 $b64Stream = base64_encode($dataArray[1]);
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);
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,
130 self::FILE_IO_CHUNKER,
134 // Advance to the next 50-chars block
139 unset($fileInstance);
143 * Reads from a local file
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
149 public final function loadFileContents ($fqfn) {
150 // Initialize some variables and arrays
155 $readData = ''; // This will contain our read data
157 // Get a file input handler
158 $fileInstance = ObjectFactory::createObjectByConfiguredName('file_raw_input_class', array($fqfn));
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;
165 // Break infinite loop maybe caused by the input handler
166 if ($lastBuffer == $inputBuffer) {
170 // Remember last read line for avoiding possible infinite loops
171 $lastBuffer = $inputBuffer;
174 // Close directory handle
175 unset($fileInstance);
177 // Convert it into an array
178 $inputBuffer = explode(chr(10), $inputBuffer);
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);
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]);
191 // Now we must convert it again into an array
192 $header = explode(self::FILE_IO_CHUNKER, $header);
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);
199 } elseif (substr($rawLine, 0, 5) == self::FILE_IO_DATA_BLOCK_ID) {
201 $data = explode(self::FILE_IO_SEPARATOR, $rawLine);
204 // First element is the data, second the MD5 checksum
205 $data = explode(self::FILE_IO_CHUNKER, $data);
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);
215 throw new InvalidArrayCountException(array($this, 'data', count($data), 2), self::EXCEPTION_ARRAY_HAS_INVALID_COUNT);
218 // Add this to the readData string
219 $readData .= $data[0];
221 // Other raw lines than header/data tagged lines and re-add the new-line char
222 $readData .= $rawLine . PHP_EOL;
226 // Was raw lines read and no header/data?
227 if ((!empty($readData)) && (count($header) == 0) && (count($data) == 0)) {
228 // Return raw lines back
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);
238 // Decode all from Base64
239 $readData = @base64_decode($readData);
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);
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);
253 // Return all in an array
261 * Streams the data and maybe does something to it
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
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);
273 * Determines seek position
275 * @return $seekPosition Current seek position
278 public function determineSeekPosition () {
279 $this->partialStub();
283 * Seek to given offset (default) or other possibilities as fseek() gives.
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
289 public function seek ($offset, $whence = SEEK_SET) {
290 $this->partialStub('offset=' . $offset . ',whence=' . $whence);
296 * @return $size Size (in bytes) of file
298 public function size () {
299 $this->partialStub();