3 namespace Org\Mxchange\CoreFramework\Filesystem\File;
5 // Import framework stuff
6 use Org\Mxchange\CoreFramework\Bootstrap\FrameworkBootstrap;
7 use Org\Mxchange\CoreFramework\EntryPoint\ApplicationEntryPoint;
8 use Org\Mxchange\CoreFramework\Factory\Object\ObjectFactory;
9 use Org\Mxchange\CoreFramework\Filesystem\File\BaseAbstractFile;
10 use Org\Mxchange\CoreFramework\Filesystem\FilePointer;
11 use Org\Mxchange\CoreFramework\Generic\FrameworkInterface;
12 use Org\Mxchange\CoreFramework\Traits\Index\IndexableTrait;
13 use Org\Mxchange\CoreFramework\Traits\Stack\StackableTrait;
16 use \BadMethodCallException;
17 use \InvalidArgumentException;
19 use \OutOfBoundsException;
21 use \UnexpectedValueException;
24 * A general binary file class
26 * @author Roland Haeder <webmaster@ship-simu.org>
28 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2022 Core Developer Team
29 * @license GNU GPL 3.0 or any newer version
30 * @link http://www.ship-simu.org
32 * This program is free software: you can redistribute it and/or modify
33 * it under the terms of the GNU General Public License as published by
34 * the Free Software Foundation, either version 3 of the License, or
35 * (at your option) any later version.
37 * This program is distributed in the hope that it will be useful,
38 * but WITHOUT ANY WARRANTY; without even the implied warranty of
39 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
40 * GNU General Public License for more details.
42 * You should have received a copy of the GNU General Public License
43 * along with this program. If not, see <http://www.gnu.org/licenses/>.
45 abstract class BaseBinaryFile extends BaseAbstractFile implements BinaryFile {
53 private static $configCache = [];
56 * Current seek position
58 private $seekPosition = 0;
63 private $headerSize = 0;
71 * Seek positions for gaps ("fragmentation")
76 * Seek positions for damaged entries (e.g. mismatching hash sum, ...)
78 private $damagedEntries = [];
83 private $backBuffer = '';
86 * Currently loaded block (will be returned by current())
88 private $currentBlock = '';
91 * Protected constructor
93 * @param $className Name of the class
96 protected function __construct (string $className) {
97 // Call parent constructor
98 parent::__construct($className);
102 * Setter for backBuffer field
104 * @param $backBuffer Characters to "store" in back-buffer
107 private function setBackBuffer (string $backBuffer) {
108 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting backBuffer(%d)=%s - CALLED!', strlen($backBuffer), $backBuffer));
109 $this->backBuffer = $backBuffer;
113 * Getter for backBuffer field
115 * @return $backBuffer Characters "stored" in back-buffer
117 private function getBackBuffer () {
118 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Getting this->backBuffer(%d)=%s - CALLED!', strlen($this->backBuffer), $this->backBuffer));
119 return $this->backBuffer;
123 * Setter for current field
125 * @param $current Characters to set a currently loaded block
128 private function setCurrentBlock (string $currentBlock) {
129 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting currentBlock(%d)=%s - CALLED!', strlen($currentBlock), $currentBlock));
130 $this->currentBlock = $currentBlock;
134 * Gets currently read data
136 * @return $current Currently read data
138 public function getCurrentBlock () {
139 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Getting this->currentBlock(%d)=%s - CALLED!', strlen($this->currentBlock), $this->currentBlock));
140 return $this->currentBlock;
144 * Getter for header size
146 * @return $totalEntries Size of file header
148 public final function getHeaderSize () {
149 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Getting this->headerSize=%d - CALLED!', $this->headerSize));
150 return $this->headerSize;
154 * Setter for header size
156 * @param $headerSize Size of file header
159 public final function setHeaderSize (int $headerSize) {
160 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting headerSize=%d - CALLED!', $headerSize));
161 $this->headerSize = $headerSize;
165 * Getter for header array
167 * @return $totalEntries Size of file header
169 public final function getHeader () {
171 return $this->header;
177 * @param $header Array for a file header
180 public final function setHeader (array $header) {
182 $this->header = $header;
186 * Getter for seek position
188 * @return $seekPosition Current seek position (stored here in object)
190 public final function getSeekPosition () {
191 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Getting this->seekPosition=%d - CALLED!', $this->seekPosition));
192 return $this->seekPosition;
196 * Setter for seek position
198 * @param $seekPosition Current seek position (stored here in object)
201 protected final function setSeekPosition (int $seekPosition) {
202 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting seekPosition=%d - CALLED!', $seekPosition));
203 $this->seekPosition = $seekPosition;
207 * Checks whether the abstracted file only contains gaps by counting all
208 * gaps' bytes together and compare it to total length.
210 * @return $isGapsOnly Whether the abstracted file only contains gaps
211 * @throws OutOfBoundsException If calculated file size is larger than actual
213 public function isFileGapsOnly () {
215 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
217 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->gaps()=%d', count($this->gaps)));
218 foreach ($this->gaps as $gap) {
219 // Calculate size of found gap: end-start including both
220 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: gap[%s]=%d,ga[%s]=%d', BinaryFile::GAPS_INDEX_START, $gap[BinaryFile::GAPS_INDEX_START], BinaryFile::GAPS_INDEX_END, $gap[BinaryFile::GAPS_INDEX_END]));
221 $gapsSize += ($gap[BinaryFile::GAPS_INDEX_END] - $gap[BinaryFile::GAPS_INDEX_START]);
224 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: gapsSize=%d', $gapsSize));
227 // Total gap size + header size + 1 must be same as file size
228 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: gapsSize=%d,this->headerSize=%d,this->fileSize=%d', $gapsSize, $this->getHeaderSize(), $this->getFileSize()));
229 $determinedFileSize = ($gapsSize + $this->getHeaderSize() + 1);
231 // Should not be more!
232 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: determinedFileSize=%d,this->fileSize=%d', $determinedFileSize, $this->getFileSize()));
233 if ($determinedFileSize > $this->getFileSize()) {
235 throw new OutOfBoundsException(sprintf('determinedFileSize=%d is larger than this->fileSize=%d', $determinedFileSize, $this->getFileSize()));
239 $isGapsOnly = ($determinedFileSize == $this->getFileSize());
242 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isGapsOnly=%d - EXIT!', intval($isGapsOnly)));
247 * Marks whole file as gaps-only (freshly created file
249 * @param $type Type of file
250 * @param $minimumBlockLength Minimum block length
253 private function markFileGapsOnly (string $type, int $minimumBlockLength) {
254 // Is config cache there?
255 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: type=%s,minimumBlockLength=%d - CALLED!', $type, $minimumBlockLength));
256 if (!isset(self::$configCache[$type . '_pre_allocate_count'])) {
258 self::$configCache[$type . '_pre_allocate_count'] = FrameworkBootstrap::getConfigurationInstance()->getConfigEntry($type . '_pre_allocate_count');
261 // Very simple to do ...
262 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: self:configCache[%s_pre_allocate_count]()=%d', count(self::$configCache[$type . '_pre_allocate_count'])));
263 for ($idx = 0; $idx < self::$configCache[$type . '_pre_allocate_count']; $idx++) {
264 // Calculate start/end positions
265 $startPosition = $idx * $minimumBlockLength;
266 $endPosition = $idx * $minimumBlockLength + $minimumBlockLength;
268 // Mark start and end position as gap
269 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->addGap(%d, %d) ...', $startPosition, $endPosition));
270 $this->addGap($startPosition, $endPosition);
274 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
278 * Adds a gap for given start and end position
280 * @param $startPosition Start seek position
281 * @param $endPosition End seek position
284 private function addGap(int $startPosition, int $endPosition) {
285 // Push to gaps array
286 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: startPosition=%d,endPosition=%d - CALLED!', $startPosition, $endPosition));
287 array_push($this->gaps, [
288 BinaryFile::GAPS_INDEX_START => $startPosition,
289 BinaryFile::GAPS_INDEX_END => $endPosition,
293 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
297 * Initializes the back-buffer by setting it to an empty string.
301 private function initBackBuffer () {
302 // Simply call the setter
303 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
304 $this->setBackBuffer('');
307 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
311 * Seeks to beginning of file, updates seek position in this object and
312 * flushes the header.
314 * @param $flushHeader Wether the file's header should be flushed (default: false)
317 protected function rewindUpdateSeekPosition (bool $flushHeader = false) {
318 // Seek to beginning of file
319 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: flushHeader=%d - CALLED!', intval($flushHeader)));
322 // And update seek position ...
323 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->updateSeekPosition() ...');
324 $this->updateSeekPosition();
328 // ... to write it back into the file
329 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->flushFileHeader() ...');
330 $this->flushFileHeader();
334 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
338 * Seeks to old position
342 protected function seekToOldPosition () {
343 // Seek to currently ("old") saved position
344 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
345 $this->seek($this->determineSeekPosition());
348 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
352 * Initializes this file class
354 * @param $fileInfoInstance An instance of a SplFileInfo class
357 protected function initFile (SplFileInfo $fileInfoInstance) {
358 // Get a file i/o pointer instance
359 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: fileInfoInstance[%s]=%s - CALLED!', get_class($fileInfoInstance), $fileInfoInstance));
360 $pointerInstance = ObjectFactory::createObjectByConfiguredName('file_raw_input_output_class', array($fileInfoInstance));
362 // ... and set it here
363 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting pointerInstance=%s ...', $pointerInstance->__toString()));
364 $this->setPointerInstance($pointerInstance);
367 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
371 * Marks the currently loaded block as empty (with length of the block)
373 * @param $length Length of the block
375 * @throws InvalidArgumentException If a parameter is invalid
377 protected function markCurrentBlockAsEmpty (int $length) {
378 // Validate parameter
379 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: length=%d - CALLED!', $length));
381 // Length cannot below one
382 throw new InvalidArgumentException(sprintf('length=%d is not valid', $length));
385 // Get current seek position
386 $currentPosition = $this->determineSeekPosition();
388 // Now add it as gap entry
389 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->addGap(%d, %d) ..', ($currentPosition - $length), $currentPosition));
390 $this->addGap(($currentPosition - $length), $currentPosition);
393 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
397 * Initializes counter for valid entries, arrays for damaged entries and
398 * an array for gap seek positions. If you call this method on your own,
399 * please re-analyze the file structure. So you are better to call
400 * analyzeFileStructure() instead of this method.
404 public function initCountersGapsArray () {
405 // Init counter and seek position to header size
406 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->determineSeekPosition() - CALLED!');
407 $seekPosition = $this->getSeekPosition();
410 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d', $seekPosition));
411 $this->setCounter(0);
414 $headerSize = $this->getHeaderSize();
417 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting this->seekPosition=%d ...', $headerSize));
418 $this->setSeekPosition($headerSize);
422 $this->damagedEntries = [];
424 // Seek back to old position
425 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->seek(%d) ...', $seekPosition));
426 $this->seek($seekPosition);
429 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
433 * Updates seekPosition attribute from file to avoid to much access on file.
437 public function updateSeekPosition () {
438 // Get key (= seek position)
439 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
440 $seekPosition = $this->determineSeekPosition();
443 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d', $seekPosition));
444 $this->setSeekPosition($seekPosition);
447 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
451 * Checks whether the block separator has been found
453 * @param $str String to look in
454 * @return $isFound Whether the block separator has been found
455 * @throws InvalidArgumentException If a parameter is not valid
457 public static function isBlockSeparatorFound (string $str) {
458 // Validate parameter
459 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: str=%s - CALLED!', $str));
462 throw new InvalidArgumentException('Parameter "str" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
466 $isFound = (strpos($str, chr(BinaryFile::SEPARATOR_ENTRIES)) !== false);
469 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isFound=%d - EXIT!', intval($isFound)));
474 * Writes data at given position
476 * @param $seekPosition Seek position
477 * @param $data Data to be written
478 * @param $flushHeader Whether to flush the header (default: flush)
480 * @throws OutOfBoundsException If the position is not seekable
481 * @throws InvalidArgumentException If a parameter is invalid
483 public function writeData (int $seekPosition, string $data, bool $flushHeader = true) {
484 // Validate parameter
485 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%s,data()=%d,flushHeader=%d - CALLED!', $seekPosition, strlen($data), intval($flushHeader)));
486 if ($seekPosition < 0) {
487 // Invalid seek position
488 throw new OutOfBoundsException(sprintf('seekPosition=%d is not valid', $seekPosition));
489 } elseif (empty($data)) {
490 // Empty data is invalid, too
491 throw new InvalidArgumentException('Parameter "data" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
494 // Write data at given position
495 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->writeAtPosition(%d,%s) ...', $seekPosition, $data));
496 $this->writeAtPosition($seekPosition, $data);
499 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->incrementCounter() ...');
500 $this->incrementCounter();
502 // Update seek position
503 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->updateSeekPosition() ...');
504 $this->updateSeekPosition();
507 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: flushHeader=%d', intval($flushHeader)));
508 if ($flushHeader === true) {
510 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->flushFileHeader() ...');
511 $this->flushFileHeader();
513 // Seek to old position
514 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->seekToOldPosition() ...');
515 $this->seekToOldPosition();
519 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
523 * Writes at given position by seeking to it.
525 * @param $seekPosition Seek position in file
526 * @param $dataStream Data to be written
527 * @return mixed Number of writes bytes or false on error
528 * @throws OutOfBoundsException If the position is not seekable
529 * @throws InvalidArgumentException If a parameter is not valid
531 public function writeAtPosition (int $seekPosition, string $dataStream) {
532 // Validate parameter
533 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d,dataStream(%d)=%s - CALLED!', $seekPosition, strlen($dataStream), $dataStream));
534 if ($seekPosition < 0) {
535 // Invalid seek position
536 throw new OutOfBoundsException(sprintf('seekPosition=%d is not valid.', $seekPosition));
537 } elseif (empty($dataStream)) {
539 throw new InvalidArgumentException('Parameter "dataStream" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
542 // Call pointer's method
543 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->pointerInstance->writeAtPosition(%d, %s) ...', $seekPosition, $dataStream));
544 $status = $this->getPointerInstance()->writeAtPosition($seekPosition, $dataStream);
547 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: status[%s]=%d - EXIT!', gettype($status), $status));
552 * Checks whether the file header is initialized
554 * @return $isInitialized Whether the file header is initialized
556 public function isFileHeaderInitialized () {
557 // Default is not initialized
558 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
559 $isInitialized = false;
561 // Is the file initialized?
562 if ($this->isFileInitialized()) {
563 // Some bytes has been written, so rewind to start of it.
567 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->readFileHeader() ...');
568 $this->readFileHeader();
571 $headerCount = count($this->getHeader());
573 // The above method does already check the header
574 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: headerCount=%d', $headerCount));
575 $isInitialized = ($headerCount > 0);
579 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isInitialized=%d - EXIT!', intval($isInitialized)));
580 return $isInitialized;
584 * Checks whether the assigned file has been initialized
586 * @return $isInitialized Whether the file's size is zero
587 * @throws UnexpectedValueException If an unexpected value was returned
589 public function isFileInitialized () {
590 // Get it from iterator which holds the pointer instance. If false is returned
591 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
592 $fileSize = $this->size();
595 * The returned file size should not be false or NULL as this means
596 * that the pointer class does not work correctly.
598 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: fileSize[%s]=%d', gettype($fileSize), $fileSize));
599 if (!is_int($fileSize)) {
601 throw new UnexpectedValueException(sprintf('fileSize[]=%s is unexpected', gettype($fileSize)));
604 // Is more than 0 returned?
605 $isInitialized = ($fileSize > 0);
608 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isInitialized=%d - EXIT!', intval($isInitialized)));
609 return $isInitialized;
613 * Creates the assigned file
616 * @throws BadMethodCallException If this file's header is already initialized
618 public function createFileHeader () {
619 // The file's header should not be initialized here
620 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
621 if ($this->isFileHeaderInitialized()) {
623 //* DEBUG-DIE: */ ApplicationEntryPoint::exitApplication(sprintf('[%s:%d]: this=%s', __METHOD__, __LINE__, print_r($this, TRUE)));
624 throw new BadMethodCallException('File header is already initialized but method called');
627 // Simple flush file header which will create it.
628 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->flushFileHeader() ...');
629 $this->flushFileHeader();
631 // Rewind seek position (to beginning of file) and update/flush file header
632 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->rewindUpdateSeekPosition() ...');
633 $this->rewindUpdateSeekPosition();
636 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
640 * Determines seek position
642 * @return $seekPosition Current seek position
644 public function determineSeekPosition () {
645 // Call pointer instance
646 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
647 $seekPosition = $this->getPointerInstance()->determineSeekPosition();
650 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d - EXIT!', $seekPosition));
651 return $seekPosition;
655 * Seek to given offset (default) or other possibilities as fseek() gives.
657 * @param $offset Offset to seek to (or used as "base" for other seeks)
658 * @param $whence Added to offset (default: only use offset to seek to)
659 * @return $status Status of file seek: 0 = success, -1 = failed
660 * @throws OutOfBoundsException If the position is not seekable
662 public function seek (int $offset, int $whence = SEEK_SET) {
663 // Validate parameter
664 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: offset=%d,whence=%d - CALLED!', $offset, $whence));
666 // No offset is smaller than zero
667 throw new OutOfBoundsException(sprintf('offset=%d is not valid', $offset));
670 // Call pointer instance
671 $status = $this->getPointerInstance()->seek($offset, $whence);
674 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: status[%s]=%d - EXIT!', gettype($status), $status));
679 * Reads given amount of bytes from file.
681 * @param $bytes Amount of bytes to read
682 * @return $data Data read from file
683 * @throws OutOfBoundsException If the position is not seekable
685 public function read (int $bytes = 0) {
686 // Validate parameter
687 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: bytes=%d - CALLED!', $bytes));
690 throw new OutOfBoundsException(sprintf('bytes=%d is not valid', $bytes));
693 // Call pointer instance
694 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->pointerInstance->read(%d) ...', $bytes));
695 $data = $this->getPointerInstance()->read($bytes);
697 // Update seek position
698 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->updateSeekPosition() ...');
699 $this->updateSeekPosition();
702 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: data[%s]=%s - EXIT!', gettype($data), $data));
707 * Rewinds to the beginning of the file
711 public function rewind () {
712 // Call pointer instance
713 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
714 $this->getPointerInstance()->rewind();
717 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
721 * Analyzes entries in index file. This will count all found (and valid)
722 * entries, mark invalid as damaged and count gaps ("fragmentation"). If
723 * only gaps are found, the file is considered as "virgin" (no entries).
726 * @throws BadMethodCallException If this method is called but file is not initialized
728 public function analyzeFileStructure () {
729 // Make sure the file is initialized
730 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
731 if (!$this->isFileInitialized()) {
733 throw new BadMethodCallException('Method called but file is not initialized.');
736 // Init counters and gaps array
737 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->initCounterGapsArrays() ...');
738 $this->initCountersGapsArray();
740 // Output message (as this may take some time)
741 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('Analyzing file structure ... (this may take some time)'));
743 // First Seek to right after file header
744 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->seek(%d) ...', $this->getHeaderSize() + 1));
745 $this->seek($this->getHeaderSize() + 1);
747 // Then try to load all entries
748 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, looping through file ...', $this->getSeekPosition()));
749 while ($this->isValid()) {
751 $current = $this->getCurrentBlock();
754 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: current=%s, calling this->readNextBlock() ...', $current));
755 $this->readNextBlock();
758 * If the block is empty, maybe the whole file is? This could mean
759 * that the file has been pre-allocated.
761 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: current(%d)[]=%s', strlen($current), gettype($current)));
762 if (empty(trim($current, chr(0)))) {
763 // Then skip this part
764 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: current[]=%s is empty - CONTINUE!', gettype($current)));
768 // Handle current record
769 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: current(%d)[%s]=%s', strlen($current), gettype($current), $current));
772 // If the last read block is empty, check gaps
773 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: current()=%d', strlen($current)));
774 if (empty(trim($current, chr(0)))) {
776 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Found a total of %d gaps.', count($this->gaps)));
778 // Check gaps, if the whole file is empty.
779 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->isFileGapsOnly() ...');
780 if ($this->isFileGapsOnly()) {
781 // Only gaps, so don't continue here.
782 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: File is gaps-only - EXIT!');
788 * The above call has calculated a total size of all gaps. If the
789 * percentage of gaps passes a "soft" limit and last
790 * defragmentation is to far in the past, or if a "hard" limit has
791 * reached, run defragmentation.
793 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->isDefragmentationNeeded() ...');
794 if ($this->isDefragmentationNeeded()) {
795 // Run "defragmentation"
796 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->doRunDefragmentation() ...');
797 $this->doRunDefragmentation();
801 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
805 * Reads next "block" of given bytes into $currentBlock field. THis method
806 * loads the whole file into memory when the file is just freshly
807 * initialized (only zeros in it).
810 * @throws InvalidArgumentException If a parameter is not valid
812 protected function readNextBlockByLength (int $length) {
813 // Validate parameter
814 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d,length=%d - CALLED!', $this->getSeekPosition(), $length));
817 throw new InvalidArgumentException(sprintf('length=%d is not valid', $length));
820 // Read possibly back-buffered bytes from previous call of next().
821 $data = $this->getBackBuffer();
824 * Read until a entry/block separator has been found. The next read
825 * "block" may not fit, so this loop will continue until the EOB or EOF
826 * has been reached whatever comes first.
828 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d,data()=%d', $this->getSeekPosition(), strlen($data)));
829 while ((!$this->isEndOfFileReached()) && (empty($data) || !self::isBlockSeparatorFound($data))) {
830 // Then read the next possible block
831 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, calling this->read(%d) ...', $this->getSeekPosition(), $length));
832 $block = $this->read($length);
834 // Is the block empty?
835 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: block()=%d,length=%d', strlen($block), $length));
836 if (strlen($block) == 0) {
837 // Read empty block, maybe EOF
838 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, block is empty, maybe EOF reached - BREAK!', $this->getSeekPosition()));
840 } elseif (empty(trim($block, chr(0)))) {
842 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, calling this->markCurrentBlockAsEmpty(%d) ...', $this->getSeekPosition(), $length));
843 $this->markCurrentBlockAsEmpty($length);
846 // At this block then
850 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d,data()=%d', $this->getSeekPosition(), strlen($data)));
854 * Init back-buffer which is the data that has been found beyond the
857 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->initBackBuffer(), clearing this->currentBlock ...');
858 $this->initBackBuffer();
859 $this->setCurrentBlock('');
862 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: data(%d)=%s', strlen($data), $data));
863 if (empty(trim($data, chr(0)))) {
864 // Yes, maybe whole file was ...
865 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, maybe empty file found - EXIT!', $this->getSeekPosition()));
870 $dataArray = explode(chr(BinaryFile::SEPARATOR_ENTRIES), $data);
872 // Left part is the actual block, right one the back-buffer data, if found
873 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: dataArray()=%d', count($dataArray)));
874 //* PRINTR-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: dataArray=%s', print_r($dataArray, true)));
875 $this->setCurrentBlock($dataArray[0]);
877 // Is back buffere data found?
878 if (isset($dataArray[1]) && !empty(trim($dataArray[1], chr(0)))) {
880 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting this->backBuffer=%s ...', $dataArray[1]));
881 $this->setBackBuffer($dataArray[1]);
885 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
889 * Pre-allocates file (if enabled) with some space for later faster write access.
891 * @param $type Type of the file
893 * @throws InvalidArgumentException If a parameter is empty
894 * @throws BadMethodCallException If this->stackInstance is not properly set
896 protected function preAllocateFileByTypeLength (string $type, int $minimumBlockLength) {
898 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: type=%s,minimumBlockLength=%d - CALLED!', $type, $minimumBlockLength));
901 throw new InvalidArgumentException('Parameter "type" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
902 } elseif ($minimumBlockLength < 1) {
903 // Invalid block length
904 throw new InvalidArgumentException(sprintf('Parameter minimumBlockLength=%d is not valid', $minimumBlockLength));
905 } elseif (FrameworkBootstrap::getConfigurationInstance()->getConfigEntry($type . '_pre_allocate_enabled') != 'Y') {
906 // Don't continue here.
907 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Not pre-allocating file.'));
912 $fileSize = $this->getFileSize();
914 // Calulcate seek position
915 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: minimumBlockLength=%d,fileSize=%d', $minimumBlockLength, $fileSize));
916 $seekPosition = $this->getHeaderSize() + $minimumBlockLength * FrameworkBootstrap::getConfigurationInstance()->getConfigEntry($type . '_pre_allocate_count') + $fileSize ;
918 // Now simply write a NUL there. This will pre-allocate the file.
919 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->writeAtPosition(%d,NUL) ...', $seekPosition));
920 $this->writeAtPosition($seekPosition, chr(0));
922 // Is the seek position zero?
923 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: fileSize=%d', $fileSize));
924 if ($fileSize == 0) {
925 // Mark file as gaps-only
926 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->markGapsOnly(%s,%d) ...', $type, $minimumBlockLength));
927 $this->markFileGapsOnly($type, $minimumBlockLength);
929 // Analyze file structure
930 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->analyzeFileStructure() ...');
931 $this->analyzeFileStructure();
934 // Rewind seek position
935 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->rewind() ...');
939 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
943 * Checks wether the current entry is valid (not at the end of the file).
944 * This method will return true if an emptied (nulled) entry has been found.
946 * @return $isValid Whether the next entry is valid
947 * @throws InvalidArgumentException If a parameter is not valid
949 protected function isValidByLength (int $length) {
950 // Validate parameter
951 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: length=%d - CALLED!', $length));
954 throw new InvalidArgumentException(sprintf('Parameter length=%d is not valid', $length));
957 // Get current seek position
958 $seekPosition = $this->determineSeekPosition();
960 // Then try to read it
961 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d', $seekPosition));
962 $data = $this->read($length);
964 // If some bytes could be read, all is fine
965 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: data[%s]()=%d', gettype($data), strlen($data)));
966 $isValid = ((is_string($data)) && (strlen($data) > 0));
969 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isValid=%d', intval($isValid)));
970 $headerSize = $this->getHeaderSize();
972 // Is the seek position at or beyond the header?
973 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d,headerSize=%d', $seekPosition, $headerSize));
974 if ($seekPosition >= $headerSize) {
975 // Seek back to old position
976 $isValid = ($isValid && $this->seek($seekPosition) === 0);
978 // Seek directly behind the header
979 $isValid = ($isValid && $this->seek($headerSize) === 0);
983 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isValid=%d - EXIT!', intval($isValid)));
988 * Reads next "block" of bytes into $currentBlock field. THis method loads
989 * the whole file into memory when the file is just freshly initialized
990 * (only zeros in it).
994 protected abstract function readNextBlock ();
997 * Reads the file header
1000 * @throws LogicException If both instances are not set
1002 public abstract function readFileHeader ();
1005 * Searches for next suitable gap the given length of data can fit in
1006 * including padding bytes.
1008 * @param $length Length of raw data
1009 * @return $seekPosition Found next gap's seek position
1010 * @throws InvalidArgumentException If the parameter is not valid
1012 public function searchNextGap (int $length) {
1013 // If the file is only gaps, no need to seek
1014 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: length=%d - CALLED!', $length));
1017 throw new InvalidArgumentException(sprintf('length=%d is not valid', $length));
1018 } elseif ($this->isFileGapsOnly()) {
1020 * The first empty block is the 2nd one right after the header, so
1021 * one byte gap to the header.
1023 $seekPosition = ($this->getHeaderSize() + 2);
1026 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d - EXIT!', $seekPosition));
1027 return $seekPosition;
1031 $this->partialStub('length=' . $length);