* @version 0.0.0 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2015 Core Developer Team * @license GNU GPL 3.0 or any newer version * @link http://www.shipsimu.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ class FileIoStream extends BaseFrameworkSystem implements FileInputStreamer, FileOutputStreamer { /** * File header indicator */ const FILE_IO_FILE_HEADER_ID = '@head'; /** * Data block indicator */ const FILE_IO_DATA_BLOCK_ID = '@data'; /** * Separator #1 */ const FILE_IO_CHUNKER = ':'; /** * Separator #2 */ const FILE_IO_SEPARATOR = '^'; /** * Protected constructor */ protected function __construct () { // Call parent constructor parent::__construct(__CLASS__); } /** * Create a file IO stream. This is a class for performing all actions * on files like creating, deleting and loading them. * * @return $ioInstance An instance of FileIoStream */ public static final function createFileIoStream () { // Create new instance $ioInstance = new FileIoStream(); // Return the instance return $ioInstance; } /** * Saves data to a given local file and create missing directory structures * * @param $fileName The file name for the to be saved file * @param $dataArray The data we shall store to the file * @return void * @see FileOutputStreamer * @todo This method needs heavy rewrite */ public final function saveFile ($fileName, array $dataArray) { // Try it five times $dirName = ''; $fileInstance = NULL; for ($idx = 0; $idx < 5; $idx++) { // Get a file output pointer try { $fileInstance = ObjectFactory::createObjectByConfiguredName('file_raw_output_class', array($fileName, 'wb')); } catch (FileIoException $e) { // Bail out ApplicationEntryPoint::app_exit('The application has made a fatal error. Exception: ' $e->__toString() . ' with message: ' . $e->getMessage()); } } // END - for // Write a header information for validation purposes $fileInstance->writeToFile(sprintf('%s%s%s%s%s%s%s%s%s' . PHP_EOL, self::FILE_IO_FILE_HEADER_ID, self::FILE_IO_SEPARATOR, $dataArray[0], self::FILE_IO_CHUNKER, time(), self::FILE_IO_CHUNKER, strlen($dataArray[1]), self::FILE_IO_CHUNKER, md5($dataArray[1]) )); // Encode the (maybe) binary stream with Base64 $b64Stream = base64_encode($dataArray[1]); // write the data line by line $line = str_repeat(' ', 50); $idx = 0; while (strlen($line) == 50) { // Get 50 chars or less $line = substr($b64Stream, $idx, 50); // Save it to the stream $fileInstance->writeToFile(sprintf('%s%s%s%s%s' . PHP_EOL, self::FILE_IO_DATA_BLOCK_ID, self::FILE_IO_SEPARATOR, $line, self::FILE_IO_CHUNKER, md5($line) )); // Advance to the next 50-chars block $idx += 50; } // END - while // Close the file unset($fileInstance); } /** * Reads from a local file * * @param $fqfn The full-qualified file-name which we shall load * @return $array An array with the element 'header' and 'data' * @see FileInputStreamer */ public final function loadFileContents ($fqfn) { // Initialize some variables and arrays $inputBuffer = ''; $lastBuffer = ''; $header = array(); $data = array(); $readData = ''; // This will contain our read data // Get a file input handler $fileInstance = ObjectFactory::createObjectByConfiguredName('file_raw_input_class', array($fqfn)); // Read all it's contents (we very and transparently decompress it below) while ($readRawLine = $fileInstance->readFromFile()) { // Add the read line to the buffer $inputBuffer .= $readRawLine; // Break infinite loop maybe caused by the input handler if ($lastBuffer == $inputBuffer) { break; } // END - if // Remember last read line for avoiding possible infinite loops $lastBuffer = $inputBuffer; } // END - while // Close directory handle unset($fileInstance); // Convert it into an array $inputBuffer = explode(chr(10), $inputBuffer); // Now process the read lines and verify it's content foreach ($inputBuffer as $rawLine) { // Trim it a little but not the leading spaces/tab-stops $rawLine = rtrim($rawLine); // Analyze this line if (substr($rawLine, 0, 5) == self::FILE_IO_FILE_HEADER_ID) { // Header found, so let's extract it $header = explode(self::FILE_IO_SEPARATOR, $rawLine); $header = trim($header[1]); // Now we must convert it again into an array $header = explode(self::FILE_IO_CHUNKER, $header); // Is the header (maybe) valid? if (count($header) != 4) { // Throw an exception throw new InvalidArrayCountException(array($this, 'header', count($header), 4), self::EXCEPTION_ARRAY_HAS_INVALID_COUNT); } // END - if } elseif (substr($rawLine, 0, 5) == self::FILE_IO_DATA_BLOCK_ID) { // Is a data line! $data = explode(self::FILE_IO_SEPARATOR, $rawLine); $data = $data[1]; // First element is the data, second the MD5 checksum $data = explode(self::FILE_IO_CHUNKER, $data); // Validate the read line if (count($data) == 2) { if (md5($data[0]) != $data[1]) { // MD5 hash did not match! throw new InvalidMD5ChecksumException(array($this, md5($data[0]), $data[1]), self::EXCEPTION_MD5_CHECKSUMS_MISMATCH); } // END - if } else { // Invalid count! throw new InvalidArrayCountException(array($this, 'data', count($data), 2), self::EXCEPTION_ARRAY_HAS_INVALID_COUNT); } // Add this to the readData string $readData .= $data[0]; } else { // Other raw lines than header/data tagged lines and re-add the new-line char $readData .= $rawLine . PHP_EOL; } } // END - foreach // Was raw lines read and no header/data? if ((!empty($readData)) && (count($header) == 0) && (count($data) == 0)) { // Return raw lines back return $readData; } // END - if // Was a header found? if (count($header) != 4) { // Throw an exception throw new InvalidArrayCountException(array($this, 'header', count($header), 4), self::EXCEPTION_ARRAY_HAS_INVALID_COUNT); } // END - if // Decode all from Base64 $readData = @base64_decode($readData); // Does the size match? if (strlen($readData) != $header[2]) { // Size did not match throw new InvalidDataLengthException(array($this, strlen($readData), $header[2]), self::EXCEPTION_UNEXPECTED_STRING_SIZE); } // END - if // Validate the decoded data with the final MD5 hash if (md5($readData) != $header[3]) { // MD5 hash did not match! throw new InvalidMD5ChecksumException(array($this, md5($readData), $header[3]), self::EXCEPTION_MD5_CHECKSUMS_MISMATCH); } // END - if // Return all in an array return array( 'header' => $header, 'data' => $readData ); } /** * Streams the data and maybe does something to it * * @param $data The data (string mostly) to "stream" * @return $data The data (string mostly) to "stream" * @throws UnsupportedOperationException If this method is called */ public function streamData ($data) { self::createDebugInstance(__CLASS__)->debugOutput('Unhandled ' . strlen($data) . ' bytes in this stream.'); throw new UnsupportedOperationException(array($this, __FUNCTION__), self::EXCEPTION_UNSPPORTED_OPERATION); } /** * Determines seek position * * @return $seekPosition Current seek position * @todo 0% done */ public function determineSeekPosition () { $this->partialStub(); } /** * Seek to given offset (default) or other possibilities as fseek() gives. * * @param $offset Offset to seek to (or used as "base" for other seeks) * @param $whence Added to offset (default: only use offset to seek to) * @return $status Status of file seek: 0 = success, -1 = failed */ public function seek ($offset, $whence = SEEK_SET) { $this->partialStub('offset=' . $offset . ',whence=' . $whence); } /** * Size of file stack * * @return $size Size (in bytes) of file */ public function size () { $this->partialStub(); } } // [EOF] ?>