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\Middleware\Debug\DebugMiddleware;
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 - 2023 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 {
54 private static $configCache = [];
57 * Current seek position
59 private $seekPosition = 0;
64 private $headerSize = 0;
72 * Seek positions for gaps ("fragmentation")
77 * Seek positions for damaged entries (e.g. mismatching hash sum, ...)
79 private $damagedEntries = [];
84 private $backBuffer = '';
87 * Currently loaded block (will be returned by current())
89 private $currentBlock = '';
92 * Protected constructor
94 * @param $className Name of the class
97 protected function __construct (string $className) {
98 // Call parent constructor
99 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: className=%s - CALLED!', $className));
100 parent::__construct($className);
103 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('FRAMEWORK-DIRECTORY-POINTER: EXIT!');
107 * Setter for backBuffer field
109 * @param $backBuffer Characters to "store" in back-buffer
112 private function setBackBuffer (string $backBuffer) {
113 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Setting backBuffer(%d)=%s - CALLED!', strlen($backBuffer), $backBuffer));
114 $this->backBuffer = $backBuffer;
118 * Getter for backBuffer field
120 * @return $backBuffer Characters "stored" in back-buffer
122 private function getBackBuffer () {
123 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Getting this->backBuffer(%d)=%s - CALLED!', strlen($this->backBuffer), $this->backBuffer));
124 return $this->backBuffer;
128 * Setter for current field
130 * @param $current Characters to set a currently loaded block
133 private function setCurrentBlock (string $currentBlock) {
134 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Setting currentBlock(%d)=%s - CALLED!', strlen($currentBlock), $currentBlock));
135 $this->currentBlock = $currentBlock;
139 * Gets currently read data
141 * @return $current Currently read data
143 public function getCurrentBlock () {
144 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Getting this->currentBlock(%d)=%s - CALLED!', strlen($this->currentBlock), $this->currentBlock));
145 return $this->currentBlock;
149 * Getter for header size
151 * @return $totalEntries Size of file header
153 public final function getHeaderSize () {
154 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Getting this->headerSize=%d - CALLED!', $this->headerSize));
155 return $this->headerSize;
159 * Setter for header size
161 * @param $headerSize Size of file header
164 public final function setHeaderSize (int $headerSize) {
165 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Setting headerSize=%d - CALLED!', $headerSize));
166 $this->headerSize = $headerSize;
170 * Getter for header array
172 * @return $totalEntries Size of file header
174 public final function getHeader () {
176 return $this->header;
182 * @param $header Array for a file header
185 public final function setHeader (array $header) {
187 $this->header = $header;
191 * Getter for seek position
193 * @return $seekPosition Current seek position (stored here in object)
195 public final function getSeekPosition () {
196 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Getting this->seekPosition=%d - CALLED!', $this->seekPosition));
197 return $this->seekPosition;
201 * Setter for seek position
203 * @param $seekPosition Current seek position (stored here in object)
206 protected final function setSeekPosition (int $seekPosition) {
207 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Setting seekPosition=%d - CALLED!', $seekPosition));
208 $this->seekPosition = $seekPosition;
212 * Checks whether the abstracted file only contains gaps by counting all
213 * gaps' bytes together and compare it to total length.
215 * @return $isGapsOnly Whether the abstracted file only contains gaps
216 * @throws OutOfBoundsException If calculated file size is larger than actual
218 public function isFileGapsOnly () {
220 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: CALLED!');
222 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: this->gaps()=%d', count($this->gaps)));
223 foreach ($this->gaps as $gap) {
224 // Calculate size of found gap: end-start including both
225 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(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]));
226 $gapsSize += ($gap[BinaryFile::GAPS_INDEX_END] - $gap[BinaryFile::GAPS_INDEX_START]);
229 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: gapsSize=%d', $gapsSize));
232 // Total gap size + header size + 1 must be same as file size
233 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: gapsSize=%d,this->headerSize=%d,this->fileSize=%d', $gapsSize, $this->getHeaderSize(), $this->getFileSize()));
234 $determinedFileSize = ($gapsSize + $this->getHeaderSize() + 1);
236 // Should not be more!
237 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: determinedFileSize=%d,this->fileSize=%d', $determinedFileSize, $this->getFileSize()));
238 if ($determinedFileSize > $this->getFileSize()) {
240 throw new OutOfBoundsException(sprintf('determinedFileSize=%d is larger than this->fileSize=%d', $determinedFileSize, $this->getFileSize()));
244 $isGapsOnly = ($determinedFileSize == $this->getFileSize());
247 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: isGapsOnly=%d - EXIT!', intval($isGapsOnly)));
252 * Marks whole file as gaps-only (freshly created file
254 * @param $type Type of file
255 * @param $minimumBlockLength Minimum block length
258 private function markFileGapsOnly (string $type, int $minimumBlockLength) {
259 // Is config cache there?
260 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: type=%s,minimumBlockLength=%d - CALLED!', $type, $minimumBlockLength));
261 if (!isset(self::$configCache[$type . '_pre_allocate_count'])) {
263 self::$configCache[$type . '_pre_allocate_count'] = FrameworkBootstrap::getConfigurationInstance()->getConfigEntry($type . '_pre_allocate_count');
266 // Very simple to do ...
267 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: self:configCache[%s_pre_allocate_count]=%d', $type, self::$configCache[$type . '_pre_allocate_count']));
268 for ($idx = 0; $idx < self::$configCache[$type . '_pre_allocate_count']; $idx++) {
269 // Calculate start/end positions
270 $startPosition = $idx * $minimumBlockLength;
271 $endPosition = $idx * $minimumBlockLength + $minimumBlockLength;
273 // Mark start and end position as gap
274 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Invoking this->addGap(%d, %d) ...', $startPosition, $endPosition));
275 $this->addGap($startPosition, $endPosition);
279 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
283 * Adds a gap for given start and end position
285 * @param $startPosition Start seek position
286 * @param $endPosition End seek position
289 private function addGap(int $startPosition, int $endPosition) {
290 // Push to gaps array
291 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: startPosition=%d,endPosition=%d - CALLED!', $startPosition, $endPosition));
292 array_push($this->gaps, [
293 BinaryFile::GAPS_INDEX_START => $startPosition,
294 BinaryFile::GAPS_INDEX_END => $endPosition,
298 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
302 * Initializes the back-buffer by setting it to an empty string.
306 private function initBackBuffer () {
307 // Simply call the setter
308 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: CALLED!');
309 $this->setBackBuffer('');
312 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
316 * Seeks to beginning of file, updates seek position in this object and
317 * flushes the header.
319 * @param $flushHeader Wether the file's header should be flushed (default: false)
322 protected function rewindUpdateSeekPosition (bool $flushHeader = false) {
323 // Seek to beginning of file
324 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: flushHeader=%d - CALLED!', intval($flushHeader)));
327 // And update seek position ...
328 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->updateSeekPosition() ...');
329 $this->updateSeekPosition();
333 // ... to write it back into the file
334 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->flushFileHeader() ...');
335 $this->flushFileHeader();
339 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
343 * Seeks to old position
347 protected function seekToOldPosition () {
348 // Seek to currently ("old") saved position
349 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: CALLED!');
350 $this->seek($this->determineSeekPosition());
353 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
357 * Initializes this file class
359 * @param $fileInfoInstance An instance of a SplFileInfo class
362 protected function initFile (SplFileInfo $fileInfoInstance) {
363 // Get a file i/o pointer instance
364 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: fileInfoInstance[%s]=%s - CALLED!', get_class($fileInfoInstance), $fileInfoInstance));
365 $pointerInstance = ObjectFactory::createObjectByConfiguredName('file_raw_input_output_class', array($fileInfoInstance));
367 // ... and set it here
368 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: Setting pointerInstance=%s ...', $pointerInstance->__toString()));
369 $this->setPointerInstance($pointerInstance);
372 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
376 * Marks the currently loaded block as empty (with length of the block)
378 * @param $length Length of the block
380 * @throws InvalidArgumentException If a parameter is invalid
382 protected function markCurrentBlockAsEmpty (int $length) {
383 // Validate parameter
384 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: length=%d - CALLED!', $length));
386 // Length cannot below one
387 throw new InvalidArgumentException(sprintf('length=%d is not valid', $length), FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
390 // Get current seek position
391 $currentPosition = $this->determineSeekPosition();
393 // Now add it as gap entry
394 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Invoking this->addGap(%d, %d) ..', ($currentPosition - $length), $currentPosition));
395 $this->addGap(($currentPosition - $length), $currentPosition);
398 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
402 * Initializes counter for valid entries, arrays for damaged entries and
403 * an array for gap seek positions. If you call this method on your own,
404 * please re-analyze the file structure. So you are better to call
405 * analyzeFileStructure() instead of this method.
409 public function initCountersGapsArray () {
410 // Init counter and seek position to header size
411 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->determineSeekPosition() - CALLED!');
412 $seekPosition = $this->getSeekPosition();
415 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: seekPosition=%d', $seekPosition));
416 $this->setCounter(0);
419 $headerSize = $this->getHeaderSize();
422 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: Setting this->seekPosition=%d ...', $headerSize));
423 $this->setSeekPosition($headerSize);
427 $this->damagedEntries = [];
429 // Seek back to old position
430 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Invoking this->seek(%d) ...', $seekPosition));
431 $this->seek($seekPosition);
434 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
438 * Updates seekPosition attribute from file to avoid to much access on file.
442 public function updateSeekPosition () {
443 // Get key (= seek position)
444 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: CALLED!');
445 $seekPosition = $this->determineSeekPosition();
448 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: seekPosition=%d', $seekPosition));
449 $this->setSeekPosition($seekPosition);
452 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
456 * Checks whether the block separator has been found
458 * @param $str String to look in
459 * @return $isFound Whether the block separator has been found
460 * @throws InvalidArgumentException If a parameter is not valid
462 public static function isBlockSeparatorFound (string $str) {
463 // Validate parameter
464 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: str=%s - CALLED!', $str));
467 throw new InvalidArgumentException('Parameter "str" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
471 $isFound = (strpos($str, chr(BinaryFile::SEPARATOR_ENTRIES)) !== false);
474 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: isFound=%d - EXIT!', intval($isFound)));
479 * Writes data at given position
481 * @param $seekPosition Seek position
482 * @param $data Data to be written
483 * @param $flushHeader Whether to flush the header (default: flush)
485 * @throws OutOfBoundsException If the position is not seekable
486 * @throws InvalidArgumentException If a parameter is invalid
488 public function writeData (int $seekPosition, string $data, bool $flushHeader = true) {
489 // Validate parameter
490 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: seekPosition=%s,data()=%d,flushHeader=%d - CALLED!', $seekPosition, strlen($data), intval($flushHeader)));
491 if ($seekPosition < 0) {
492 // Invalid seek position
493 throw new OutOfBoundsException(sprintf('seekPosition=%d is not valid', $seekPosition));
494 } elseif (empty($data)) {
495 // Empty data is invalid, too
496 throw new InvalidArgumentException('Parameter "data" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
499 // Write data at given position
500 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Invoking this->writeAtPosition(%d,%s) ...', $seekPosition, $data));
501 $this->writeAtPosition($seekPosition, $data);
504 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->incrementCounter() ...');
505 $this->incrementCounter();
507 // Update seek position
508 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->updateSeekPosition() ...');
509 $this->updateSeekPosition();
512 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: flushHeader=%d', intval($flushHeader)));
513 if ($flushHeader === true) {
515 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->flushFileHeader() ...');
516 $this->flushFileHeader();
518 // Seek to old position
519 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->seekToOldPosition() ...');
520 $this->seekToOldPosition();
524 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
528 * Writes at given position by seeking to it.
530 * @param $seekPosition Seek position in file
531 * @param $dataStream Data to be written
532 * @return mixed Number of writes bytes or false on error
533 * @throws OutOfBoundsException If the position is not seekable
534 * @throws InvalidArgumentException If a parameter is not valid
536 public function writeAtPosition (int $seekPosition, string $dataStream) {
537 // Validate parameter
538 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: seekPosition=%d,dataStream(%d)=%s - CALLED!', $seekPosition, strlen($dataStream), $dataStream));
539 if ($seekPosition < 0) {
540 // Invalid seek position
541 throw new OutOfBoundsException(sprintf('seekPosition=%d is not valid.', $seekPosition));
542 } elseif (empty($dataStream)) {
544 throw new InvalidArgumentException('Parameter "dataStream" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
547 // Call pointer's method
548 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Invoking this->pointerInstance->writeAtPosition(%d, %s) ...', $seekPosition, $dataStream));
549 $status = $this->getPointerInstance()->writeAtPosition($seekPosition, $dataStream);
552 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: status[%s]=%d - EXIT!', gettype($status), $status));
557 * Checks whether the file header is initialized
559 * @return $isInitialized Whether the file header is initialized
561 public function isFileHeaderInitialized () {
562 // Default is not initialized
563 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: CALLED!');
564 $isInitialized = false;
566 // Is the file initialized?
567 if ($this->isFileInitialized()) {
568 // Some bytes has been written, so rewind to start of it.
572 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->readFileHeader() ...');
573 $this->readFileHeader();
576 $headerCount = count($this->getHeader());
578 // The above method does already check the header
579 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: headerCount=%d', $headerCount));
580 $isInitialized = ($headerCount > 0);
584 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: isInitialized=%d - EXIT!', intval($isInitialized)));
585 return $isInitialized;
589 * Checks whether the assigned file has been initialized
591 * @return $isInitialized Whether the file's size is zero
592 * @throws UnexpectedValueException If an unexpected value was returned
594 public function isFileInitialized () {
595 // Get it from iterator which holds the pointer instance. If false is returned
596 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: CALLED!');
597 $fileSize = $this->size();
600 * The returned file size should not be false or NULL as this means
601 * that the pointer class does not work correctly.
603 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: fileSize[%s]=%d', gettype($fileSize), $fileSize));
604 if (!is_int($fileSize)) {
606 throw new UnexpectedValueException(sprintf('fileSize[]=%s is unexpected', gettype($fileSize)), FrameworkInterface::EXCEPTION_UNEXPECTED_VALUE);
609 // Is more than 0 returned?
610 $isInitialized = ($fileSize > 0);
613 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: isInitialized=%d - EXIT!', intval($isInitialized)));
614 return $isInitialized;
618 * Creates the assigned file
621 * @throws BadMethodCallException If this file's header is already initialized
623 public function createFileHeader () {
624 // The file's header should not be initialized here
625 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: CALLED!');
626 if ($this->isFileHeaderInitialized()) {
628 //* DEBUG-DIE: */ ApplicationEntryPoint::exitApplication(sprintf('[%s:%d]: this=%s', __METHOD__, __LINE__, print_r($this, TRUE)));
629 throw new BadMethodCallException('File header is already initialized but method called', FrameworkInterface::EXCEPTION_BAD_METHOD_CALL);
632 // Simple flush file header which will create it.
633 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->flushFileHeader() ...');
634 $this->flushFileHeader();
636 // Rewind seek position (to beginning of file) and update/flush file header
637 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->rewindUpdateSeekPosition() ...');
638 $this->rewindUpdateSeekPosition();
641 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
645 * Determines seek position
647 * @return $seekPosition Current seek position
649 public function determineSeekPosition () {
650 // Call pointer instance
651 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: CALLED!');
652 $seekPosition = $this->getPointerInstance()->determineSeekPosition();
655 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: seekPosition=%d - EXIT!', $seekPosition));
656 return $seekPosition;
660 * Seek to given offset (default) or other possibilities as fseek() gives.
662 * @param $offset Offset to seek to (or used as "base" for other seeks)
663 * @param $whence Added to offset (default: only use offset to seek to)
664 * @return $status Status of file seek: 0 = success, -1 = failed
665 * @throws OutOfBoundsException If the position is not seekable
667 public function seek (int $offset, int $whence = SEEK_SET) {
668 // Validate parameter
669 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: offset=%d,whence=%d - CALLED!', $offset, $whence));
671 // No offset is smaller than zero
672 throw new OutOfBoundsException(sprintf('offset=%d is not valid', $offset));
675 // Call pointer instance
676 $status = $this->getPointerInstance()->seek($offset, $whence);
679 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: status[%s]=%d - EXIT!', gettype($status), $status));
684 * Reads given amount of bytes from file.
686 * @param $bytes Amount of bytes to read
687 * @return $data Data read from file
688 * @throws OutOfBoundsException If the position is not seekable
690 public function read (int $bytes = 0) {
691 // Validate parameter
692 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: bytes=%d - CALLED!', $bytes));
695 throw new OutOfBoundsException(sprintf('bytes=%d is not valid', $bytes));
698 // Call pointer instance
699 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Invoking this->pointerInstance->read(%d) ...', $bytes));
700 $data = $this->getPointerInstance()->read($bytes);
702 // Update seek position
703 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->updateSeekPosition() ...');
704 $this->updateSeekPosition();
707 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: data[%s]=%s - EXIT!', gettype($data), $data));
712 * Rewinds to the beginning of the file
716 public function rewind () {
717 // Call pointer instance
718 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: CALLED!');
719 $this->getPointerInstance()->rewind();
722 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
726 * Analyzes entries in index file. This will count all found (and valid)
727 * entries, mark invalid as damaged and count gaps ("fragmentation"). If
728 * only gaps are found, the file is considered as "virgin" (no entries).
731 * @throws BadMethodCallException If this method is called but file is not initialized
733 public function analyzeFileStructure () {
734 // Make sure the file is initialized
735 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: CALLED!');
736 if (!$this->isFileInitialized()) {
738 throw new BadMethodCallException('Method called but file is not initialized.', FrameworkInterface::EXCEPTION_BAD_METHOD_CALL);
741 // Init counters and gaps array
742 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->initCounterGapsArrays() ...');
743 $this->initCountersGapsArray();
745 // Output message (as this may take some time)
746 self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('Analyzing file structure ... (this may take some time)'));
748 // First Seek to right after file header
749 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Invoking this->seek(%d) ...', $this->getHeaderSize() + 1));
750 $this->seek($this->getHeaderSize() + 1);
752 // Then try to load all entries
753 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, looping through file ...', $this->getSeekPosition()));
754 while ($this->isValid()) {
756 $current = $this->getCurrentBlock();
759 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: current=%s, Invoking this->readNextBlock() ...', $current));
760 $this->readNextBlock();
763 * If the block is empty, maybe the whole file is? This could mean
764 * that the file has been pre-allocated.
766 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: current(%d)[]=%s', strlen($current), gettype($current)));
767 if (empty(trim($current, chr(0)))) {
768 // Then skip this part
769 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: current[]=%s is empty - CONTINUE!', gettype($current)));
773 // Handle current record
774 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: current(%d)[%s]=%s', strlen($current), gettype($current), $current));
777 // If the last read block is empty, check gaps
778 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: current()=%d', strlen($current)));
779 if (empty(trim($current, chr(0)))) {
781 self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: Found a total of %d gaps.', count($this->gaps)));
783 // Check gaps, if the whole file is empty.
784 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->isFileGapsOnly() ...');
785 if ($this->isFileGapsOnly()) {
786 // Only gaps, so don't continue here.
787 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage('BASE-BINARY-FILE: File is gaps-only - EXIT!');
793 * The above call has calculated a total size of all gaps. If the
794 * percentage of gaps passes a "soft" limit and last
795 * defragmentation is to far in the past, or if a "hard" limit has
796 * reached, run defragmentation.
798 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->isDefragmentationNeeded() ...');
799 if ($this->isDefragmentationNeeded()) {
800 // Run "defragmentation"
801 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->doRunDefragmentation() ...');
802 $this->doRunDefragmentation();
806 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
810 * Reads next "block" of given bytes into $currentBlock field. THis method
811 * loads the whole file into memory when the file is just freshly
812 * initialized (only zeros in it).
815 * @throws InvalidArgumentException If a parameter is not valid
817 protected function readNextBlockByLength (int $length) {
818 // Validate parameter
819 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: this->seekPosition=%d,length=%d - CALLED!', $this->getSeekPosition(), $length));
822 throw new InvalidArgumentException(sprintf('length=%d is not valid', $length), FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
825 // Read possibly back-buffered bytes from previous call of next().
826 $data = $this->getBackBuffer();
829 * Read until a entry/block separator has been found. The next read
830 * "block" may not fit, so this loop will continue until the EOB or EOF
831 * has been reached whatever comes first.
833 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: this->seekPosition=%d,data()=%d', $this->getSeekPosition(), strlen($data)));
834 while ((!$this->isEndOfFileReached()) && (empty($data) || !self::isBlockSeparatorFound($data))) {
835 // Then read the next possible block
836 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, Invoking this->read(%d) ...', $this->getSeekPosition(), $length));
837 $block = $this->read($length);
839 // Is the block empty?
840 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: block()=%d,length=%d', strlen($block), $length));
841 if (strlen($block) == 0) {
842 // Read empty block, maybe EOF
843 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, block is empty, maybe EOF reached - BREAK!', $this->getSeekPosition()));
845 } elseif (empty(trim($block, chr(0)))) {
847 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, Invoking this->markCurrentBlockAsEmpty(%d) ...', $this->getSeekPosition(), $length));
848 $this->markCurrentBlockAsEmpty($length);
851 // At this block then
855 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: this->seekPosition=%d,data()=%d', $this->getSeekPosition(), strlen($data)));
859 * Init back-buffer which is the data that has been found beyond the
862 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->initBackBuffer(), clearing this->currentBlock ...');
863 $this->initBackBuffer();
864 $this->setCurrentBlock('');
867 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: data(%d)=%s', strlen($data), $data));
868 if (empty(trim($data, chr(0)))) {
869 // Yes, maybe whole file was ...
870 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, maybe empty file found - EXIT!', $this->getSeekPosition()));
875 $dataArray = explode(chr(BinaryFile::SEPARATOR_ENTRIES), $data);
877 // Left part is the actual block, right one the back-buffer data, if found
878 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: dataArray()=%d', count($dataArray)));
879 //* PRINTR-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: dataArray=%s', print_r($dataArray, true)));
880 $this->setCurrentBlock($dataArray[0]);
882 // Is back buffere data found?
883 if (isset($dataArray[1]) && !empty(trim($dataArray[1], chr(0)))) {
885 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: Setting this->backBuffer=%s ...', $dataArray[1]));
886 $this->setBackBuffer($dataArray[1]);
890 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
894 * Pre-allocates file (if enabled) with some space for later faster write access.
896 * @param $type Type of the file
898 * @throws InvalidArgumentException If a parameter is empty
900 protected function preAllocateFileByTypeLength (string $type, int $minimumBlockLength) {
902 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: type=%s,minimumBlockLength=%d - CALLED!', $type, $minimumBlockLength));
905 throw new InvalidArgumentException('Parameter "type" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
906 } elseif ($minimumBlockLength < 1) {
907 // Invalid block length
908 throw new InvalidArgumentException(sprintf('Parameter minimumBlockLength=%d is not valid', $minimumBlockLength), FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
909 } elseif (!FrameworkBootstrap::getConfigurationInstance()->isEnabled($type . '_pre_allocate')) {
910 // Don't continue here.
911 self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Not pre-allocating file. - EXIT!');
916 $fileSize = $this->getFileSize();
918 // Calulcate seek position
919 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: minimumBlockLength=%d,fileSize=%d', $minimumBlockLength, $fileSize));
920 $seekPosition = $this->getHeaderSize() + $minimumBlockLength * FrameworkBootstrap::getConfigurationInstance()->getConfigEntry($type . '_pre_allocate_count') + $fileSize ;
922 // Now simply write a NUL there. This will pre-allocate the file.
923 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Invoking this->writeAtPosition(%d,NUL) ...', $seekPosition));
924 $this->writeAtPosition($seekPosition, chr(0));
926 // Is the seek position zero?
927 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: fileSize=%d', $fileSize));
928 if ($fileSize == 0) {
929 // Mark file as gaps-only
930 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Invoking this->markGapsOnly(%s,%d) ...', $type, $minimumBlockLength));
931 $this->markFileGapsOnly($type, $minimumBlockLength);
933 // Analyze file structure
934 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->analyzeFileStructure() ...');
935 $this->analyzeFileStructure();
938 // Rewind seek position
939 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->rewind() ...');
943 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
947 * Checks wether the current entry is valid (not at the end of the file).
948 * This method will return true if an emptied (nulled) entry has been found.
950 * @return $isValid Whether the next entry is valid
951 * @throws InvalidArgumentException If a parameter is not valid
953 protected function isValidByLength (int $length) {
954 // Validate parameter
955 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: length=%d - CALLED!', $length));
958 throw new InvalidArgumentException(sprintf('Parameter length=%d is not valid', $length), FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
961 // Get current seek position
962 $seekPosition = $this->determineSeekPosition();
964 // Then try to read it
965 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: seekPosition=%d', $seekPosition));
966 $data = $this->read($length);
968 // If some bytes could be read, all is fine
969 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: data[%s]()=%d', gettype($data), strlen($data)));
970 $isValid = ((is_string($data)) && (strlen($data) > 0));
973 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: isValid=%d', intval($isValid)));
974 $headerSize = $this->getHeaderSize();
976 // Is the seek position at or beyond the header?
977 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: seekPosition=%d,headerSize=%d', $seekPosition, $headerSize));
978 if ($seekPosition >= $headerSize) {
979 // Seek back to old position
980 $isValid = ($isValid && $this->seek($seekPosition) === 0);
982 // Seek directly behind the header
983 $isValid = ($isValid && $this->seek($headerSize) === 0);
987 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: isValid=%d - EXIT!', intval($isValid)));
992 * Reads next "block" of bytes into $currentBlock field. THis method loads
993 * the whole file into memory when the file is just freshly initialized
994 * (only zeros in it).
998 protected abstract function readNextBlock ();
1001 * Reads the file header
1004 * @throws LogicException If both instances are not set
1006 public abstract function readFileHeader ();
1009 * Searches for next suitable gap the given length of data can fit in
1010 * including padding bytes.
1012 * @param $length Length of raw data
1013 * @return $seekPosition Found next gap's seek position
1014 * @throws InvalidArgumentException If the parameter is not valid
1016 public function searchNextGap (int $length) {
1017 // If the file is only gaps, no need to seek
1018 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: length=%d - CALLED!', $length));
1021 throw new InvalidArgumentException(sprintf('length=%d is not valid', $length), FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
1022 } elseif ($this->isFileGapsOnly()) {
1024 * The first empty block is the 2nd one right after the header, so
1025 * one byte gap to the header.
1027 $seekPosition = ($this->getHeaderSize() + 2);
1030 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: seekPosition=%d - EXIT!', $seekPosition));
1031 return $seekPosition;
1035 DebugMiddleware::getSelfInstance()->partialStub('length=' . $length);