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\Index\Indexable;
12 use Org\Mxchange\CoreFramework\Stack\File\StackableFile;
13 use Org\Mxchange\CoreFramework\Traits\Index\IndexableTrait;
14 use Org\Mxchange\CoreFramework\Traits\Stack\StackableTrait;
17 use \BadMethodCallException;
18 use \InvalidArgumentException;
20 use \OutOfBoundsException;
22 use \UnexpectedValueException;
25 * A general binary file class
27 * @author Roland Haeder <webmaster@ship-simu.org>
29 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2021 Core Developer Team
30 * @license GNU GPL 3.0 or any newer version
31 * @link http://www.ship-simu.org
33 * This program is free software: you can redistribute it and/or modify
34 * it under the terms of the GNU General Public License as published by
35 * the Free Software Foundation, either version 3 of the License, or
36 * (at your option) any later version.
38 * This program is distributed in the hope that it will be useful,
39 * but WITHOUT ANY WARRANTY; without even the implied warranty of
40 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
41 * GNU General Public License for more details.
43 * You should have received a copy of the GNU General Public License
44 * along with this program. If not, see <http://www.gnu.org/licenses/>.
46 abstract class BaseBinaryFile extends BaseAbstractFile implements BinaryFile {
52 * Separator for header data
54 const SEPARATOR_HEADER_DATA = 0x01;
57 * Separator header->entries
59 const SEPARATOR_HEADER_ENTRIES = 0x02;
62 * Separator group->hash
64 const SEPARATOR_GROUP_HASH = 0x03;
67 * Separator hash->value
69 const SEPARATOR_HASH_VALUE = 0x04;
72 * Separator entry->entry
74 const SEPARATOR_ENTRIES = 0x05;
77 * Separator type->position
79 const SEPARATOR_TYPE_POSITION = 0x06;
84 const LENGTH_COUNT = 20;
89 const LENGTH_POSITION = 20;
94 const LENGTH_GROUP = 10;
97 * Maximum length of entry type
99 const LENGTH_TYPE = 20;
101 //***** Array elements for 'gaps' array *****
106 const GAPS_INDEX_START = 'start';
111 const GAPS_INDEX_END = 'end';
114 * Current seek position
116 private $seekPosition = 0;
121 private $headerSize = 0;
126 private $header = [];
129 * Seek positions for gaps ("fragmentation")
134 * Seek positions for damaged entries (e.g. mismatching hash sum, ...)
136 private $damagedEntries = [];
141 private $backBuffer = '';
144 * Currently loaded block (will be returned by current())
146 private $currentBlock = '';
149 * Protected constructor
151 * @param $className Name of the class
154 protected function __construct (string $className) {
155 // Call parent constructor
156 parent::__construct($className);
160 * Setter for backBuffer field
162 * @param $backBuffer Characters to "store" in back-buffer
165 private function setBackBuffer (string $backBuffer) {
166 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting backBuffer(%d)=%s - CALLED!', strlen($backBuffer), $backBuffer));
167 $this->backBuffer = $backBuffer;
171 * Getter for backBuffer field
173 * @return $backBuffer Characters "stored" in back-buffer
175 private function getBackBuffer () {
176 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Getting this->backBuffer(%d)=%s - CALLED!', strlen($this->backBuffer), $this->backBuffer));
177 return $this->backBuffer;
181 * Setter for current field
183 * @param $current Characters to set a currently loaded block
186 private function setCurrentBlock (string $currentBlock) {
187 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting currentBlock(%d)=%s - CALLED!', strlen($currentBlock), $currentBlock));
188 $this->currentBlock = $currentBlock;
192 * Gets currently read data
194 * @return $current Currently read data
196 public function getCurrentBlock () {
197 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Getting this->currentBlock(%d)=%s - CALLED!', strlen($this->currentBlock), $this->currentBlock));
198 return $this->currentBlock;
202 * Getter for header size
204 * @return $totalEntries Size of file header
206 public final function getHeaderSize () {
207 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Getting this->headerSize=%d - CALLED!', $this->headerSize));
208 return $this->headerSize;
212 * Setter for header size
214 * @param $headerSize Size of file header
217 public final function setHeaderSize (int $headerSize) {
218 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting headerSize=%d - CALLED!', $headerSize));
219 $this->headerSize = $headerSize;
223 * Getter for header array
225 * @return $totalEntries Size of file header
227 public final function getHeader () {
229 return $this->header;
235 * @param $header Array for a file header
238 public final function setHeader (array $header) {
240 $this->header = $header;
244 * Getter for seek position
246 * @return $seekPosition Current seek position (stored here in object)
248 public final function getSeekPosition () {
249 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Getting this->seekPosition=%d - CALLED!', $this->seekPosition));
250 return $this->seekPosition;
254 * Setter for seek position
256 * @param $seekPosition Current seek position (stored here in object)
259 protected final function setSeekPosition (int $seekPosition) {
260 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting seekPosition=%d - CALLED!', $seekPosition));
261 $this->seekPosition = $seekPosition;
265 * Checks whether the abstracted file only contains gaps by counting all
266 * gaps' bytes together and compare it to total length.
268 * @return $isGapsOnly Whether the abstracted file only contains gaps
269 * @throws OutOfBoundsException If calculated file size is larger than actual
271 public function isFileGapsOnly () {
273 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
275 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->gaps()=%d', count($this->gaps)));
276 foreach ($this->gaps as $gap) {
277 // Calculate size of found gap: end-start including both
278 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: gap[%s]=%d,ga[%s]=%d', self::GAPS_INDEX_START, $gap[self::GAPS_INDEX_START], self::GAPS_INDEX_END, $gap[self::GAPS_INDEX_END]));
279 $gapsSize += ($gap[self::GAPS_INDEX_END] - $gap[self::GAPS_INDEX_START]);
282 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: gapsSize=%d', $gapsSize));
285 // Total gap size + header size + 1 must be same as file size
286 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: gapsSize=%d,this->headerSize=%d,this->fileSize=%d', $gapsSize, $this->getHeaderSize(), $this->getFileSize()));
287 $determinedFileSize = ($gapsSize + $this->getHeaderSize() + 1);
289 // Should not be more!
290 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: determinedFileSize=%d,this->fileSize=%d', $determinedFileSize, $this->getFileSize()));
291 if ($determinedFileSize > $this->getFileSize()) {
293 throw new OutOfBoundsException(sprintf('determinedFileSize=%d is larger than this->fileSize=%d', $determinedFileSize, $this->getFileSize()));
297 $isGapsOnly = ($determinedFileSize == $this->getFileSize());
300 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isGapsOnly=%d - EXIT!', intval($isGapsOnly)));
305 * Marks whole file as gaps-only (freshly created file
307 * @param $type Type of file
308 * @param $minimumBlockLength Minimum block length
311 private function markFileGapsOnly (string $type, int $minimumBlockLength) {
312 // Very simple to do ...
313 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: type=%s,minimumBlockLength=%d - CALLED!', $type, $minimumBlockLength));
314 for ($idx = 0; $idx < FrameworkBootstrap::getConfigurationInstance()->getConfigEntry($type . '_pre_allocate_count'); $idx++) {
315 // Calculate start/end positions
316 $startPosition = $idx * $minimumBlockLength;
317 $endPosition = $idx * $minimumBlockLength + $minimumBlockLength;
319 // Mark start and end position as gap
320 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->addGap(%d, %d) ...', $startPosition, $endPosition));
321 $this->addGap($startPosition, $endPosition);
325 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
329 * Adds a gap for given start and end position
331 * @param $startPosition Start seek position
332 * @param $endPosition End seek position
335 private function addGap(int $startPosition, int $endPosition) {
336 // Push to gaps array
337 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: startPosition=%d,endPosition=%d - CALLED!', $startPosition, $endPosition));
338 array_push($this->gaps, [
339 self::GAPS_INDEX_START => $startPosition,
340 self::GAPS_INDEX_END => $endPosition,
344 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
348 * Initializes the back-buffer by setting it to an empty string.
352 private function initBackBuffer () {
353 // Simply call the setter
354 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
355 $this->setBackBuffer('');
358 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
362 * Seeks to beginning of file, updates seek position in this object and
363 * flushes the header.
365 * @param $flushHeader Wether the file's header should be flushed (default: false)
368 protected function rewindUpdateSeekPosition (bool $flushHeader = false) {
369 // Seek to beginning of file
370 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: flushHeader=%d - CALLED!', intval($flushHeader)));
373 // And update seek position ...
374 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->updateSeekPosition() ...');
375 $this->updateSeekPosition();
379 // ... to write it back into the file
380 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->flushFileHeader() ...');
381 $this->flushFileHeader();
385 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
389 * Seeks to old position
393 protected function seekToOldPosition () {
394 // Seek to currently ("old") saved position
395 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
396 $this->seek($this->determineSeekPosition());
399 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
403 * Initializes this file class
405 * @param $fileInfoInstance An instance of a SplFileInfo class
408 protected function initFile (SplFileInfo $fileInfoInstance) {
409 // Get a file i/o pointer instance
410 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: fileInfoInstance[%s]=%s - CALLED!', get_class($fileInfoInstance), $fileInfoInstance));
411 $pointerInstance = ObjectFactory::createObjectByConfiguredName('file_raw_input_output_class', array($fileInfoInstance));
413 // ... and set it here
414 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting pointerInstance=%s ...', $pointerInstance->__toString()));
415 $this->setPointerInstance($pointerInstance);
418 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
422 * Marks the currently loaded block as empty (with length of the block)
424 * @param $length Length of the block
426 * @throws InvalidArgumentException If a parameter is invalid
428 protected function markCurrentBlockAsEmpty (int $length) {
429 // Validate parameter
430 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: length=%d - CALLED!', $length));
432 // Length cannot below one
433 throw new InvalidArgumentException(sprintf('length=%d is not valid', $length));
436 // Get current seek position
437 $currentPosition = $this->determineSeekPosition();
439 // Now add it as gap entry
440 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->addGap(%d, %d) ..', ($currentPosition - $length), $currentPosition));
441 $this->addGap(($currentPosition - $length), $currentPosition);
444 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
448 * Initializes counter for valid entries, arrays for damaged entries and
449 * an array for gap seek positions. If you call this method on your own,
450 * please re-analyze the file structure. So you are better to call
451 * analyzeFileStructure() instead of this method.
455 public function initCountersGapsArray () {
456 // Init counter and seek position to header size
457 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->determineSeekPosition() - CALLED!');
458 $seekPosition = $this->getSeekPosition();
461 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d', $seekPosition));
462 $this->setCounter(0);
465 $headerSize = $this->getHeaderSize();
468 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting this->seekPosition=%d ...', $headerSize));
469 $this->setSeekPosition($headerSize);
473 $this->damagedEntries = [];
475 // Seek back to old position
476 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->seek(%d) ...', $seekPosition));
477 $this->seek($seekPosition);
480 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
484 * Updates seekPosition attribute from file to avoid to much access on file.
488 public function updateSeekPosition () {
489 // Get key (= seek position)
490 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
491 $seekPosition = $this->determineSeekPosition();
494 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d', $seekPosition));
495 $this->setSeekPosition($seekPosition);
498 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
502 * Checks whether the block separator has been found
504 * @param $str String to look in
505 * @return $isFound Whether the block separator has been found
506 * @throws InvalidArgumentException If a parameter is not valid
508 public static function isBlockSeparatorFound (string $str) {
509 // Validate parameter
510 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: str=%s - CALLED!', $str));
513 throw new InvalidArgumentException('Parameter "str" is empty');
517 $isFound = (strpos($str, chr(self::SEPARATOR_ENTRIES)) !== false);
520 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isFound=%d - EXIT!', intval($isFound)));
525 * Writes data at given position
527 * @param $seekPosition Seek position
528 * @param $data Data to be written
529 * @param $flushHeader Whether to flush the header (default: flush)
531 * @throws OutOfBoundsException If the position is not seekable
532 * @throws InvalidArgumentException If a parameter is invalid
534 public function writeData (int $seekPosition, string $data, bool $flushHeader = true) {
535 // Validate parameter
536 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%s,data()=%d,flushHeader=%d - CALLED!', $seekPosition, strlen($data), intval($flushHeader)));
537 if ($seekPosition < 0) {
538 // Invalid seek position
539 throw new OutOfBoundsException(sprintf('seekPosition=%d is not valid', $seekPosition));
540 } elseif (empty($data)) {
541 // Empty data is invalid, too
542 throw new InvalidArgumentException('Parameter "data" is empty');
545 // Write data at given position
546 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->writeAtPosition(%d,%s) ...', $seekPosition, $data));
547 $this->writeAtPosition($seekPosition, $data);
550 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->incrementCounter() ...');
551 $this->incrementCounter();
553 // Update seek position
554 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->updateSeekPosition() ...');
555 $this->updateSeekPosition();
558 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: flushHeader=%d', intval($flushHeader)));
559 if ($flushHeader === true) {
561 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->flushFileHeader() ...');
562 $this->flushFileHeader();
564 // Seek to old position
565 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->seekToOldPosition() ...');
566 $this->seekToOldPosition();
570 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
574 * Writes at given position by seeking to it.
576 * @param $seekPosition Seek position in file
577 * @param $dataStream Data to be written
578 * @return mixed Number of writes bytes or false on error
579 * @throws OutOfBoundsException If the position is not seekable
580 * @throws InvalidArgumentException If a parameter is not valid
582 public function writeAtPosition (int $seekPosition, string $dataStream) {
583 // Validate parameter
584 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d,dataStream(%d)=%s - CALLED!', $seekPosition, strlen($dataStream), $dataStream));
585 if ($seekPosition < 0) {
586 // Invalid seek position
587 throw new OutOfBoundsException(sprintf('seekPosition=%d is not valid.', $seekPosition));
588 } elseif (empty($dataStream)) {
590 throw new InvalidArgumentException('Parameter "dataStream" is empty');
593 // Call pointer's method
594 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->pointerInstance->writeAtPosition(%d, %s) ...', $seekPosition, $dataStream));
595 $status = $this->getPointerInstance()->writeAtPosition($seekPosition, $dataStream);
598 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: status[%s]=%d - EXIT!', gettype($status), $status));
603 * Checks whether the file header is initialized
605 * @return $isInitialized Whether the file header is initialized
607 public function isFileHeaderInitialized () {
608 // Default is not initialized
609 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
610 $isInitialized = false;
612 // Is the file initialized?
613 if ($this->isFileInitialized()) {
614 // Some bytes has been written, so rewind to start of it.
618 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->readFileHeader() ...');
619 $this->readFileHeader();
622 $headerCount = count($this->getHeader());
624 // The above method does already check the header
625 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: headerCount=%d', $headerCount));
626 $isInitialized = ($headerCount > 0);
630 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isInitialized=%d - EXIT!', intval($isInitialized)));
631 return $isInitialized;
635 * Checks whether the assigned file has been initialized
637 * @return $isInitialized Whether the file's size is zero
638 * @throws UnexpectedValueException If an unexpected value was returned
640 public function isFileInitialized () {
641 // Get it from iterator which holds the pointer instance. If false is returned
642 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
643 $fileSize = $this->size();
646 * The returned file size should not be false or NULL as this means
647 * that the pointer class does not work correctly.
649 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: fileSize[%s]=%d', gettype($fileSize), $fileSize));
650 if (!is_int($fileSize)) {
652 throw new UnexpectedValueException(sprintf('fileSize[]=%s is unexpected', gettype($fileSize)));
655 // Is more than 0 returned?
656 $isInitialized = ($fileSize > 0);
659 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isInitialized=%d - EXIT!', intval($isInitialized)));
660 return $isInitialized;
664 * Creates the assigned file
667 * @throws BadMethodCallException If this file's header is already initialized
669 public function createFileHeader () {
670 // The file's header should not be initialized here
671 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
672 if ($this->isFileHeaderInitialized()) {
674 //* DEBUG-DIE: */ ApplicationEntryPoint::exitApplication(sprintf('[%s:%d]: this=%s', __METHOD__, __LINE__, print_r($this, TRUE)));
675 throw new BadMethodCallException('File header is already initialized but method called');
678 // Simple flush file header which will create it.
679 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->flushFileHeader() ...');
680 $this->flushFileHeader();
682 // Rewind seek position (to beginning of file) and update/flush file header
683 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->rewindUpdateSeekPosition() ...');
684 $this->rewindUpdateSeekPosition();
687 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
691 * Determines seek position
693 * @return $seekPosition Current seek position
695 public function determineSeekPosition () {
696 // Call pointer instance
697 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
698 $seekPosition = $this->getPointerInstance()->determineSeekPosition();
701 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d - EXIT!', $seekPosition));
702 return $seekPosition;
706 * Seek to given offset (default) or other possibilities as fseek() gives.
708 * @param $offset Offset to seek to (or used as "base" for other seeks)
709 * @param $whence Added to offset (default: only use offset to seek to)
710 * @return $status Status of file seek: 0 = success, -1 = failed
711 * @throws OutOfBoundsException If the position is not seekable
713 public function seek (int $offset, int $whence = SEEK_SET) {
714 // Validate parameter
715 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: offset=%d,whence=%d - CALLED!', $offset, $whence));
717 // No offset is smaller than zero
718 throw new OutOfBoundsException(sprintf('offset=%d is not valid', $offset));
721 // Call pointer instance
722 $status = $this->getPointerInstance()->seek($offset, $whence);
725 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: status[%s]=%d - EXIT!', gettype($status), $status));
730 * Reads given amount of bytes from file.
732 * @param $bytes Amount of bytes to read
733 * @return $data Data read from file
734 * @throws OutOfBoundsException If the position is not seekable
736 public function read (int $bytes = 0) {
737 // Validate parameter
738 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: bytes=%d - CALLED!', $bytes));
741 throw new OutOfBoundsException(sprintf('bytes=%d is not valid', $bytes));
744 // Call pointer instance
745 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->pointerInstance->read(%d) ...', $bytes));
746 $data = $this->getPointerInstance()->read($bytes);
748 // Update seek position
749 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->updateSeekPosition() ...');
750 $this->updateSeekPosition();
753 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: data[%s]=%s - EXIT!', gettype($data), $data));
758 * Rewinds to the beginning of the file
762 public function rewind () {
763 // Call pointer instance
764 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
765 $this->getPointerInstance()->rewind();
768 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
772 * Analyzes entries in index file. This will count all found (and valid)
773 * entries, mark invalid as damaged and count gaps ("fragmentation"). If
774 * only gaps are found, the file is considered as "virgin" (no entries).
777 * @throws BadMethodCallException If this method is called but file is not initialized
779 public function analyzeFileStructure () {
780 // Make sure the file is initialized
781 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
782 if (!$this->isFileInitialized()) {
784 throw new BadMethodCallException('Method called but file is not initialized.');
787 // Init counters and gaps array
788 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->initCounterGapsArrays() ...');
789 $this->initCountersGapsArray();
791 // Output message (as this may take some time)
792 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('Analyzing file structure ... (this may take some time)'));
794 // First Seek to right after file header
795 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->seek(%d) ...', $this->getHeaderSize() + 1));
796 $this->seek($this->getHeaderSize() + 1);
798 // Then try to load all entries
799 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, looping through file ...', $this->getSeekPosition()));
800 while ($this->isValid()) {
802 $current = $this->getCurrentBlock();
805 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: current=%s, calling this->readNextBlock() ...', $current));
806 $this->readNextBlock();
809 * If the block is empty, maybe the whole file is? This could mean
810 * that the file has been pre-allocated.
812 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: current(%d)[]=%s', strlen($current), gettype($current)));
813 if (empty(trim($current, chr(0)))) {
814 // Then skip this part
815 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: current[]=%s is empty - CONTINUE!', gettype($current)));
819 // Handle current record
820 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: current(%d)[%s]=%s', strlen($current), gettype($current), $current));
823 // If the last read block is empty, check gaps
824 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: current()=%d', strlen($current)));
825 if (empty(trim($current, chr(0)))) {
827 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Found a total of %d gaps.', count($this->gaps)));
829 // Check gaps, if the whole file is empty.
830 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->isFileGapsOnly() ...');
831 if ($this->isFileGapsOnly()) {
832 // Only gaps, so don't continue here.
833 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: File is gaps-only - EXIT!');
839 * The above call has calculated a total size of all gaps. If the
840 * percentage of gaps passes a "soft" limit and last
841 * defragmentation is to far in the past, or if a "hard" limit has
842 * reached, run defragmentation.
844 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->isDefragmentationNeeded() ...');
845 if ($this->isDefragmentationNeeded()) {
846 // Run "defragmentation"
847 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->doRunDefragmentation() ...');
848 $this->doRunDefragmentation();
852 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
856 * Reads next "block" of bytes into $currentBlock field. THis method loads
857 * the whole file into memory when the file is just freshly initialized
858 * (only zeros in it).
862 private function readNextBlock () {
863 // First calculate minimum block length
864 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d - CALLED!', $this->getSeekPosition()));
865 $length = $this->getIndexInstance()->calculateMinimumBlockLength();
867 // Read possibly back-buffered bytes from previous call of next().
868 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d,length=%d', $this->getSeekPosition(), $length));
869 $data = $this->getBackBuffer();
872 * Read until a entry/block separator has been found. The next read
873 * "block" may not fit, so this loop will continue until the EOB or EOF
874 * has been reached whatever comes first.
876 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d,data()=%d', $this->getSeekPosition(), strlen($data)));
877 while ((!$this->isEndOfFileReached()) && (empty($data) || !self::isBlockSeparatorFound($data))) {
878 // Then read the next possible block
879 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, calling this->read(%d) ...', $this->getSeekPosition(), $length));
880 $block = $this->read($length);
882 // Is the block empty?
883 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: block()=%d,length=%d', strlen($block), $length));
884 if (strlen($block) == 0) {
885 // Read empty block, maybe EOF
886 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, block is empty, maybe EOF reached - BREAK!', $this->getSeekPosition()));
888 } elseif (empty(trim($block, chr(0)))) {
890 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, calling this->markCurrentBlockAsEmpty(%d) ...', $this->getSeekPosition(), $length));
891 $this->markCurrentBlockAsEmpty($length);
894 // At this block then
898 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d,data()=%d', $this->getSeekPosition(), strlen($data)));
902 * Init back-buffer which is the data that has been found beyond the
905 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->initBackBuffer(), clearing this->currentBlock ...');
906 $this->initBackBuffer();
907 $this->setCurrentBlock('');
910 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: data(%d)=%s', strlen($data), $data));
911 if (empty(trim($data, chr(0)))) {
912 // Yes, maybe whole file was ...
913 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, maybe empty file found - EXIT!', $this->getSeekPosition()));
918 $dataArray = explode(chr(self::SEPARATOR_ENTRIES), $data);
920 // Left part is the actual block, right one the back-buffer data, if found
921 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: dataArray()=%d', count($dataArray)));
922 /* PRINTR-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: dataArray=%s', print_r($dataArray, true)));
923 $this->setCurrentBlock($dataArray[0]);
925 // Is back buffere data found?
926 if (isset($dataArray[1]) && !empty(trim($dataArray[1], chr(0)))) {
928 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting this->backBuffer=%s ...', $dataArray[1]));
929 $this->setBackBuffer($dataArray[1]);
933 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
937 * Pre-allocates file (if enabled) with some space for later faster write access.
939 * @param $type Type of the file
941 * @throws InvalidArgumentException If a parameter is empty
942 * @throws BadMethodCallException If this->stackInstance is not properly set
944 protected function preAllocateFileByTypeLength (string $type, int $minimumBlockLength) {
946 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: type=%s,minimumBlockLength=%d - CALLED!', $type, $minimumBlockLength));
949 throw new InvalidArgumentException('Parameter "type" is empty');
950 } elseif ($minimumBlockLength < 1) {
951 // Invalid block length
952 throw new InvalidArgumentException(sprintf('Parameter minimumBlockLength=%d is not valid', $minimumBlockLength));
953 } elseif (FrameworkBootstrap::getConfigurationInstance()->getConfigEntry($type . '_pre_allocate_enabled') != 'Y') {
954 // Don't continue here.
955 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Not pre-allocating file.'));
960 $fileSize = $this->getFileSize();
962 // Calulcate seek position
963 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: minimumBlockLength=%d,fileSize=%d', $minimumBlockLength, $fileSize));
964 $seekPosition = $this->getHeaderSize() + $minimumBlockLength * FrameworkBootstrap::getConfigurationInstance()->getConfigEntry($type . '_pre_allocate_count') + $fileSize ;
966 // Now simply write a NUL there. This will pre-allocate the file.
967 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->writeAtPosition(%d,NUL) ...', $seekPosition));
968 $this->writeAtPosition($seekPosition, chr(0));
970 // Is the seek position zero?
971 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: fileSize=%d', $fileSize));
972 if ($fileSize == 0) {
973 // Mark file as gaps-only
974 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->markGapsOnly(%s,%d) ...', $type, $minimumBlockLength));
975 $this->markFileGapsOnly($type, $minimumBlockLength);
977 // Analyze file structure
978 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->analyzeFileStructure() ...');
979 $this->analyzeFileStructure();
982 // Rewind seek position
983 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->rewind() ...');
987 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
991 * Checks wether the current entry is valid (not at the end of the file).
992 * This method will return true if an emptied (nulled) entry has been found.
994 * @return $isValid Whether the next entry is valid
995 * @throws InvalidArgumentException If a parameter is not valid
997 protected function isValidByLength (int $length) {
998 // Validate parameter
999 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: length=%d - CALLED!', $length));
1002 throw new InvalidArgumentException(sprintf('Parameter length=%d is not valid', $length));
1005 // Get current seek position
1006 $seekPosition = $this->determineSeekPosition();
1008 // Then try to read it
1009 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d', $seekPosition));
1010 $data = $this->read($length);
1012 // If some bytes could be read, all is fine
1013 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: data[%s]()=%d', gettype($data), strlen($data)));
1014 $isValid = ((is_string($data)) && (strlen($data) > 0));
1017 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isValid=%d', intval($isValid)));
1018 $headerSize = $this->getHeaderSize();
1020 // Is the seek position at or beyond the header?
1021 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d,headerSize=%d', $seekPosition, $headerSize));
1022 if ($seekPosition >= $headerSize) {
1023 // Seek back to old position
1024 $isValid = ($isValid && $this->seek($seekPosition) === 0);
1026 // Seek directly behind the header
1027 $isValid = ($isValid && $this->seek($headerSize) === 0);
1031 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isValid=%d - EXIT!', intval($isValid)));
1036 * Reads the file header
1039 * @throws LogicException If both instances are not set
1041 public function readFileHeader () {
1042 // Is index set or stack?
1043 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
1044 if ($this->getIndexInstance() instanceof Indexable) {
1045 // Call index instance
1046 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->indexInstance->readIndexHeader() ...');
1047 $this->getIndexInstance()->readIndexHeader();
1048 } elseif ($this->getStackInstance() instanceof StackableFile) {
1049 // Call stacke instance
1050 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->stackInstance->readStackHeader() ...');
1051 $this->getStackInstance()->readStackHeader();
1054 throw new LogicException('Wether indexInstance nor stackInstance are set');
1058 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
1062 * Searches for next suitable gap the given length of data can fit in
1063 * including padding bytes.
1065 * @param $length Length of raw data
1066 * @return $seekPosition Found next gap's seek position
1067 * @throws InvalidArgumentException If the parameter is not valid
1069 public function searchNextGap (int $length) {
1070 // If the file is only gaps, no need to seek
1071 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: length=%d - CALLED!', $length));
1074 throw new InvalidArgumentException(sprintf('length=%d is not valid', $length));
1075 } elseif ($this->isFileGapsOnly()) {
1077 * The first empty block is the 2nd one right after the header, so
1078 * one byte gap to the header.
1080 $seekPosition = ($this->getHeaderSize() + 2);
1083 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d - EXIT!', $seekPosition));
1084 return $seekPosition;
1088 $this->partialStub('length=' . $length);