3 namespace Org\Mxchange\CoreFramework\Stream\Filesystem;
5 // Import framework stuff
6 use Org\Mxchange\CoreFramework\EntryPoint\ApplicationEntryPoint;
7 use Org\Mxchange\CoreFramework\Factory\ObjectFactory;
8 use Org\Mxchange\CoreFramework\Filesystem\FileNotFoundException;
9 use Org\Mxchange\CoreFramework\Generic\UnsupportedOperationException;
10 use Org\Mxchange\CoreFramework\Object\BaseFrameworkSystem;
11 use Org\Mxchange\CoreFramework\Stream\Filesystem\FileInputStreamer;
12 use Org\Mxchange\CoreFramework\Stream\Filesystem\FileOutputStreamer;
18 * An universal class for file input/output streams.
20 * @author Roland Haeder <webmaster@shipsimu.org>
22 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2017 Core Developer Team
23 * @license GNU GPL 3.0 or any newer version
24 * @link http://www.shipsimu.org
26 * This program is free software: you can redistribute it and/or modify
27 * it under the terms of the GNU General Public License as published by
28 * the Free Software Foundation, either version 3 of the License, or
29 * (at your option) any later version.
31 * This program is distributed in the hope that it will be useful,
32 * but WITHOUT ANY WARRANTY; without even the implied warranty of
33 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34 * GNU General Public License for more details.
36 * You should have received a copy of the GNU General Public License
37 * along with this program. If not, see <http://www.gnu.org/licenses/>.
39 class FileIoStream extends BaseFrameworkSystem implements FileInputStreamer, FileOutputStreamer {
41 * File header indicator
43 const FILE_IO_FILE_HEADER_ID = '@head';
46 * Data block indicator
48 const FILE_IO_DATA_BLOCK_ID = '@data';
53 const FILE_IO_CHUNKER = ':';
58 const FILE_IO_SEPARATOR = '^';
61 * Protected constructor
63 protected function __construct () {
64 // Call parent constructor
65 parent::__construct(__CLASS__);
69 * Create a file IO stream. This is a class for performing all actions
70 * on files like creating, deleting and loading them.
72 * @return $ioInstance An instance of a FileIoStream class
74 public static final function createFileIoStream () {
75 // Create new instance
76 $ioInstance = new FileIoStream();
78 // Return the instance
83 * Saves data to a given local file and create missing directory structures
85 * @param $fileInfoInstance An instance of a SplFileInfo class
86 * @param $dataArray The data we shall store to the file
88 * @see FileOutputStreamer
89 * @todo This method needs heavy rewrite
91 public final function saveFile (SplFileInfo $fileInfoInstance, array $dataArray) {
96 for ($idx = 0; $idx < 5; $idx++) {
97 // Get a file output pointer
99 $fileInstance = ObjectFactory::createObjectByConfiguredName('file_raw_output_class', array($fileInfoInstance, 'wb'));
100 } catch (FileNotFoundException $e) {
102 ApplicationEntryPoint::exitApplication('The application has made a fatal error. Exception: ' . $e->__toString() . ' with message: ' . $e->getMessage());
106 // Write a header information for validation purposes
107 $fileInstance->writeToFile(sprintf('%s%s%s%s%s%s%s%s%s' . PHP_EOL,
108 self::FILE_IO_FILE_HEADER_ID,
109 self::FILE_IO_SEPARATOR,
111 self::FILE_IO_CHUNKER,
113 self::FILE_IO_CHUNKER,
114 strlen($dataArray[1]),
115 self::FILE_IO_CHUNKER,
119 // Encode the (maybe) binary stream with Base64
120 $b64Stream = base64_encode($dataArray[1]);
122 // write the data line by line
123 $line = str_repeat(' ', 50); $idx = 0;
124 while (strlen($line) == 50) {
125 // Get 50 chars or less
126 $line = substr($b64Stream, $idx, 50);
128 // Save it to the stream
129 $fileInstance->writeToFile(sprintf('%s%s%s%s%s' . PHP_EOL,
130 self::FILE_IO_DATA_BLOCK_ID,
131 self::FILE_IO_SEPARATOR,
133 self::FILE_IO_CHUNKER,
137 // Advance to the next 50-chars block
142 unset($fileInstance);
146 * Reads from a local file
148 * @param $infoInstance An instance of a SplFileInfo class
149 * @return $array An array with the element 'header' and 'data'
150 * @see FileInputStreamer
152 public final function loadFileContents (SplFileInfo $infoInstance) {
153 // Initialize some variables and arrays
158 $readData = ''; // This will contain our read data
160 // Get a file input handler
161 $fileInstance = ObjectFactory::createObjectByConfiguredName('file_raw_input_class', array($infoInstance));
163 // Read all it's contents (we very and transparently decompress it below)
164 while ($readRawLine = $fileInstance->readFromFile()) {
165 // Add the read line to the buffer
166 $inputBuffer .= $readRawLine;
168 // Break infinite loop maybe caused by the input handler
169 if ($lastBuffer == $inputBuffer) {
173 // Remember last read line for avoiding possible infinite loops
174 $lastBuffer = $inputBuffer;
177 // Close directory handle
178 unset($fileInstance);
180 // Convert it into an array
181 $inputBuffer = explode(chr(10), $inputBuffer);
183 // Now process the read lines and verify it's content
184 foreach ($inputBuffer as $rawLine) {
185 // Trim it a little but not the leading spaces/tab-stops
186 $rawLine = rtrim($rawLine);
189 if (substr($rawLine, 0, 5) == self::FILE_IO_FILE_HEADER_ID) {
190 // Header found, so let's extract it
191 $header = explode(self::FILE_IO_SEPARATOR, $rawLine);
192 $header = trim($header[1]);
194 // Now we must convert it again into an array
195 $header = explode(self::FILE_IO_CHUNKER, $header);
197 // Is the header (maybe) valid?
198 if (count($header) != 4) {
199 // Throw an exception
200 throw new InvalidArrayCountException(array($this, 'header', count($header), 4), self::EXCEPTION_ARRAY_HAS_INVALID_COUNT);
202 } elseif (substr($rawLine, 0, 5) == self::FILE_IO_DATA_BLOCK_ID) {
204 $data = explode(self::FILE_IO_SEPARATOR, $rawLine);
207 // First element is the data, second the MD5 checksum
208 $data = explode(self::FILE_IO_CHUNKER, $data);
210 // Validate the read line
211 if (count($data) == 2) {
212 if (md5($data[0]) != $data[1]) {
213 // MD5 hash did not match!
214 throw new InvalidMD5ChecksumException(array($this, md5($data[0]), $data[1]), self::EXCEPTION_MD5_CHECKSUMS_MISMATCH);
218 throw new InvalidArrayCountException(array($this, 'data', count($data), 2), self::EXCEPTION_ARRAY_HAS_INVALID_COUNT);
221 // Add this to the readData string
222 $readData .= $data[0];
224 // Other raw lines than header/data tagged lines and re-add the new-line char
225 $readData .= $rawLine . PHP_EOL;
229 // Was raw lines read and no header/data?
230 if ((!empty($readData)) && (count($header) == 0) && (count($data) == 0)) {
231 // Return raw lines back
235 // Was a header found?
236 if (count($header) != 4) {
237 // Throw an exception
238 throw new InvalidArrayCountException(array($this, 'header', count($header), 4), self::EXCEPTION_ARRAY_HAS_INVALID_COUNT);
241 // Decode all from Base64
242 $readData = @base64_decode($readData);
244 // Does the size match?
245 if (strlen($readData) != $header[2]) {
246 // Size did not match
247 throw new InvalidDataLengthException(array($this, strlen($readData), $header[2]), self::EXCEPTION_UNEXPECTED_STRING_SIZE);
250 // Validate the decoded data with the final MD5 hash
251 if (md5($readData) != $header[3]) {
252 // MD5 hash did not match!
253 throw new InvalidMD5ChecksumException(array($this, md5($readData), $header[3]), self::EXCEPTION_MD5_CHECKSUMS_MISMATCH);
256 // Return all in an array
264 * Streams the data and maybe does something to it
266 * @param $data The data (string mostly) to "stream"
267 * @return $data The data (string mostly) to "stream"
268 * @throws UnsupportedOperationException If this method is called
270 public function streamData ($data) {
271 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('Unhandled ' . strlen($data) . ' bytes in this stream.');
272 throw new UnsupportedOperationException(array($this, __FUNCTION__), self::EXCEPTION_UNSPPORTED_OPERATION);
276 * Determines seek position
278 * @return $seekPosition Current seek position
281 public function determineSeekPosition () {
282 $this->partialStub();
286 * Seek to given offset (default) or other possibilities as fseek() gives.
288 * @param $offset Offset to seek to (or used as "base" for other seeks)
289 * @param $whence Added to offset (default: only use offset to seek to)
290 * @return $status Status of file seek: 0 = success, -1 = failed
292 public function seek ($offset, $whence = SEEK_SET) {
293 $this->partialStub('offset=' . $offset . ',whence=' . $whence);
299 * @return $size Size (in bytes) of file
301 public function size () {
302 $this->partialStub();