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\Traits\Index\IndexableTrait;
12 use Org\Mxchange\CoreFramework\Traits\Stack\StackableTrait;
15 use \BadMethodCallException;
16 use \InvalidArgumentException;
18 use \OutOfBoundsException;
20 use \UnexpectedValueException;
23 * A general binary file class
25 * @author Roland Haeder <webmaster@ship-simu.org>
27 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2021 Core Developer Team
28 * @license GNU GPL 3.0 or any newer version
29 * @link http://www.ship-simu.org
31 * This program is free software: you can redistribute it and/or modify
32 * it under the terms of the GNU General Public License as published by
33 * the Free Software Foundation, either version 3 of the License, or
34 * (at your option) any later version.
36 * This program is distributed in the hope that it will be useful,
37 * but WITHOUT ANY WARRANTY; without even the implied warranty of
38 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
39 * GNU General Public License for more details.
41 * You should have received a copy of the GNU General Public License
42 * along with this program. If not, see <http://www.gnu.org/licenses/>.
44 abstract class BaseBinaryFile extends BaseAbstractFile implements BinaryFile {
50 * Current seek position
52 private $seekPosition = 0;
57 private $headerSize = 0;
65 * Seek positions for gaps ("fragmentation")
70 * Seek positions for damaged entries (e.g. mismatching hash sum, ...)
72 private $damagedEntries = [];
77 private $backBuffer = '';
80 * Currently loaded block (will be returned by current())
82 private $currentBlock = '';
85 * Protected constructor
87 * @param $className Name of the class
90 protected function __construct (string $className) {
91 // Call parent constructor
92 parent::__construct($className);
96 * Setter for backBuffer field
98 * @param $backBuffer Characters to "store" in back-buffer
101 private function setBackBuffer (string $backBuffer) {
102 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting backBuffer(%d)=%s - CALLED!', strlen($backBuffer), $backBuffer));
103 $this->backBuffer = $backBuffer;
107 * Getter for backBuffer field
109 * @return $backBuffer Characters "stored" in back-buffer
111 private function getBackBuffer () {
112 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Getting this->backBuffer(%d)=%s - CALLED!', strlen($this->backBuffer), $this->backBuffer));
113 return $this->backBuffer;
117 * Setter for current field
119 * @param $current Characters to set a currently loaded block
122 private function setCurrentBlock (string $currentBlock) {
123 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting currentBlock(%d)=%s - CALLED!', strlen($currentBlock), $currentBlock));
124 $this->currentBlock = $currentBlock;
128 * Gets currently read data
130 * @return $current Currently read data
132 public function getCurrentBlock () {
133 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Getting this->currentBlock(%d)=%s - CALLED!', strlen($this->currentBlock), $this->currentBlock));
134 return $this->currentBlock;
138 * Getter for header size
140 * @return $totalEntries Size of file header
142 public final function getHeaderSize () {
143 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Getting this->headerSize=%d - CALLED!', $this->headerSize));
144 return $this->headerSize;
148 * Setter for header size
150 * @param $headerSize Size of file header
153 public final function setHeaderSize (int $headerSize) {
154 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting headerSize=%d - CALLED!', $headerSize));
155 $this->headerSize = $headerSize;
159 * Getter for header array
161 * @return $totalEntries Size of file header
163 public final function getHeader () {
165 return $this->header;
171 * @param $header Array for a file header
174 public final function setHeader (array $header) {
176 $this->header = $header;
180 * Getter for seek position
182 * @return $seekPosition Current seek position (stored here in object)
184 public final function getSeekPosition () {
185 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Getting this->seekPosition=%d - CALLED!', $this->seekPosition));
186 return $this->seekPosition;
190 * Setter for seek position
192 * @param $seekPosition Current seek position (stored here in object)
195 protected final function setSeekPosition (int $seekPosition) {
196 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting seekPosition=%d - CALLED!', $seekPosition));
197 $this->seekPosition = $seekPosition;
201 * Checks whether the abstracted file only contains gaps by counting all
202 * gaps' bytes together and compare it to total length.
204 * @return $isGapsOnly Whether the abstracted file only contains gaps
205 * @throws OutOfBoundsException If calculated file size is larger than actual
207 public function isFileGapsOnly () {
209 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
211 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->gaps()=%d', count($this->gaps)));
212 foreach ($this->gaps as $gap) {
213 // Calculate size of found gap: end-start including both
214 //* 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]));
215 $gapsSize += ($gap[BinaryFile::GAPS_INDEX_END] - $gap[BinaryFile::GAPS_INDEX_START]);
218 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: gapsSize=%d', $gapsSize));
221 // Total gap size + header size + 1 must be same as file size
222 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: gapsSize=%d,this->headerSize=%d,this->fileSize=%d', $gapsSize, $this->getHeaderSize(), $this->getFileSize()));
223 $determinedFileSize = ($gapsSize + $this->getHeaderSize() + 1);
225 // Should not be more!
226 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: determinedFileSize=%d,this->fileSize=%d', $determinedFileSize, $this->getFileSize()));
227 if ($determinedFileSize > $this->getFileSize()) {
229 throw new OutOfBoundsException(sprintf('determinedFileSize=%d is larger than this->fileSize=%d', $determinedFileSize, $this->getFileSize()));
233 $isGapsOnly = ($determinedFileSize == $this->getFileSize());
236 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isGapsOnly=%d - EXIT!', intval($isGapsOnly)));
241 * Marks whole file as gaps-only (freshly created file
243 * @param $type Type of file
244 * @param $minimumBlockLength Minimum block length
247 private function markFileGapsOnly (string $type, int $minimumBlockLength) {
248 // Very simple to do ...
249 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: type=%s,minimumBlockLength=%d - CALLED!', $type, $minimumBlockLength));
250 for ($idx = 0; $idx < FrameworkBootstrap::getConfigurationInstance()->getConfigEntry($type . '_pre_allocate_count'); $idx++) {
251 // Calculate start/end positions
252 $startPosition = $idx * $minimumBlockLength;
253 $endPosition = $idx * $minimumBlockLength + $minimumBlockLength;
255 // Mark start and end position as gap
256 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->addGap(%d, %d) ...', $startPosition, $endPosition));
257 $this->addGap($startPosition, $endPosition);
261 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
265 * Adds a gap for given start and end position
267 * @param $startPosition Start seek position
268 * @param $endPosition End seek position
271 private function addGap(int $startPosition, int $endPosition) {
272 // Push to gaps array
273 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: startPosition=%d,endPosition=%d - CALLED!', $startPosition, $endPosition));
274 array_push($this->gaps, [
275 BinaryFile::GAPS_INDEX_START => $startPosition,
276 BinaryFile::GAPS_INDEX_END => $endPosition,
280 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
284 * Initializes the back-buffer by setting it to an empty string.
288 private function initBackBuffer () {
289 // Simply call the setter
290 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
291 $this->setBackBuffer('');
294 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
298 * Seeks to beginning of file, updates seek position in this object and
299 * flushes the header.
301 * @param $flushHeader Wether the file's header should be flushed (default: false)
304 protected function rewindUpdateSeekPosition (bool $flushHeader = false) {
305 // Seek to beginning of file
306 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: flushHeader=%d - CALLED!', intval($flushHeader)));
309 // And update seek position ...
310 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->updateSeekPosition() ...');
311 $this->updateSeekPosition();
315 // ... to write it back into the file
316 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->flushFileHeader() ...');
317 $this->flushFileHeader();
321 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
325 * Seeks to old position
329 protected function seekToOldPosition () {
330 // Seek to currently ("old") saved position
331 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
332 $this->seek($this->determineSeekPosition());
335 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
339 * Initializes this file class
341 * @param $fileInfoInstance An instance of a SplFileInfo class
344 protected function initFile (SplFileInfo $fileInfoInstance) {
345 // Get a file i/o pointer instance
346 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: fileInfoInstance[%s]=%s - CALLED!', get_class($fileInfoInstance), $fileInfoInstance));
347 $pointerInstance = ObjectFactory::createObjectByConfiguredName('file_raw_input_output_class', array($fileInfoInstance));
349 // ... and set it here
350 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting pointerInstance=%s ...', $pointerInstance->__toString()));
351 $this->setPointerInstance($pointerInstance);
354 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
358 * Marks the currently loaded block as empty (with length of the block)
360 * @param $length Length of the block
362 * @throws InvalidArgumentException If a parameter is invalid
364 protected function markCurrentBlockAsEmpty (int $length) {
365 // Validate parameter
366 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: length=%d - CALLED!', $length));
368 // Length cannot below one
369 throw new InvalidArgumentException(sprintf('length=%d is not valid', $length));
372 // Get current seek position
373 $currentPosition = $this->determineSeekPosition();
375 // Now add it as gap entry
376 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->addGap(%d, %d) ..', ($currentPosition - $length), $currentPosition));
377 $this->addGap(($currentPosition - $length), $currentPosition);
380 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
384 * Initializes counter for valid entries, arrays for damaged entries and
385 * an array for gap seek positions. If you call this method on your own,
386 * please re-analyze the file structure. So you are better to call
387 * analyzeFileStructure() instead of this method.
391 public function initCountersGapsArray () {
392 // Init counter and seek position to header size
393 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->determineSeekPosition() - CALLED!');
394 $seekPosition = $this->getSeekPosition();
397 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d', $seekPosition));
398 $this->setCounter(0);
401 $headerSize = $this->getHeaderSize();
404 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting this->seekPosition=%d ...', $headerSize));
405 $this->setSeekPosition($headerSize);
409 $this->damagedEntries = [];
411 // Seek back to old position
412 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->seek(%d) ...', $seekPosition));
413 $this->seek($seekPosition);
416 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
420 * Updates seekPosition attribute from file to avoid to much access on file.
424 public function updateSeekPosition () {
425 // Get key (= seek position)
426 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
427 $seekPosition = $this->determineSeekPosition();
430 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d', $seekPosition));
431 $this->setSeekPosition($seekPosition);
434 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
438 * Checks whether the block separator has been found
440 * @param $str String to look in
441 * @return $isFound Whether the block separator has been found
442 * @throws InvalidArgumentException If a parameter is not valid
444 public static function isBlockSeparatorFound (string $str) {
445 // Validate parameter
446 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: str=%s - CALLED!', $str));
449 throw new InvalidArgumentException('Parameter "str" is empty');
453 $isFound = (strpos($str, chr(BinaryFile::SEPARATOR_ENTRIES)) !== false);
456 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isFound=%d - EXIT!', intval($isFound)));
461 * Writes data at given position
463 * @param $seekPosition Seek position
464 * @param $data Data to be written
465 * @param $flushHeader Whether to flush the header (default: flush)
467 * @throws OutOfBoundsException If the position is not seekable
468 * @throws InvalidArgumentException If a parameter is invalid
470 public function writeData (int $seekPosition, string $data, bool $flushHeader = true) {
471 // Validate parameter
472 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%s,data()=%d,flushHeader=%d - CALLED!', $seekPosition, strlen($data), intval($flushHeader)));
473 if ($seekPosition < 0) {
474 // Invalid seek position
475 throw new OutOfBoundsException(sprintf('seekPosition=%d is not valid', $seekPosition));
476 } elseif (empty($data)) {
477 // Empty data is invalid, too
478 throw new InvalidArgumentException('Parameter "data" is empty');
481 // Write data at given position
482 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->writeAtPosition(%d,%s) ...', $seekPosition, $data));
483 $this->writeAtPosition($seekPosition, $data);
486 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->incrementCounter() ...');
487 $this->incrementCounter();
489 // Update seek position
490 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->updateSeekPosition() ...');
491 $this->updateSeekPosition();
494 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: flushHeader=%d', intval($flushHeader)));
495 if ($flushHeader === true) {
497 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->flushFileHeader() ...');
498 $this->flushFileHeader();
500 // Seek to old position
501 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->seekToOldPosition() ...');
502 $this->seekToOldPosition();
506 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
510 * Writes at given position by seeking to it.
512 * @param $seekPosition Seek position in file
513 * @param $dataStream Data to be written
514 * @return mixed Number of writes bytes or false on error
515 * @throws OutOfBoundsException If the position is not seekable
516 * @throws InvalidArgumentException If a parameter is not valid
518 public function writeAtPosition (int $seekPosition, string $dataStream) {
519 // Validate parameter
520 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d,dataStream(%d)=%s - CALLED!', $seekPosition, strlen($dataStream), $dataStream));
521 if ($seekPosition < 0) {
522 // Invalid seek position
523 throw new OutOfBoundsException(sprintf('seekPosition=%d is not valid.', $seekPosition));
524 } elseif (empty($dataStream)) {
526 throw new InvalidArgumentException('Parameter "dataStream" is empty');
529 // Call pointer's method
530 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->pointerInstance->writeAtPosition(%d, %s) ...', $seekPosition, $dataStream));
531 $status = $this->getPointerInstance()->writeAtPosition($seekPosition, $dataStream);
534 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: status[%s]=%d - EXIT!', gettype($status), $status));
539 * Checks whether the file header is initialized
541 * @return $isInitialized Whether the file header is initialized
543 public function isFileHeaderInitialized () {
544 // Default is not initialized
545 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
546 $isInitialized = false;
548 // Is the file initialized?
549 if ($this->isFileInitialized()) {
550 // Some bytes has been written, so rewind to start of it.
554 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->readFileHeader() ...');
555 $this->readFileHeader();
558 $headerCount = count($this->getHeader());
560 // The above method does already check the header
561 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: headerCount=%d', $headerCount));
562 $isInitialized = ($headerCount > 0);
566 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isInitialized=%d - EXIT!', intval($isInitialized)));
567 return $isInitialized;
571 * Checks whether the assigned file has been initialized
573 * @return $isInitialized Whether the file's size is zero
574 * @throws UnexpectedValueException If an unexpected value was returned
576 public function isFileInitialized () {
577 // Get it from iterator which holds the pointer instance. If false is returned
578 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
579 $fileSize = $this->size();
582 * The returned file size should not be false or NULL as this means
583 * that the pointer class does not work correctly.
585 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: fileSize[%s]=%d', gettype($fileSize), $fileSize));
586 if (!is_int($fileSize)) {
588 throw new UnexpectedValueException(sprintf('fileSize[]=%s is unexpected', gettype($fileSize)));
591 // Is more than 0 returned?
592 $isInitialized = ($fileSize > 0);
595 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isInitialized=%d - EXIT!', intval($isInitialized)));
596 return $isInitialized;
600 * Creates the assigned file
603 * @throws BadMethodCallException If this file's header is already initialized
605 public function createFileHeader () {
606 // The file's header should not be initialized here
607 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
608 if ($this->isFileHeaderInitialized()) {
610 //* DEBUG-DIE: */ ApplicationEntryPoint::exitApplication(sprintf('[%s:%d]: this=%s', __METHOD__, __LINE__, print_r($this, TRUE)));
611 throw new BadMethodCallException('File header is already initialized but method called');
614 // Simple flush file header which will create it.
615 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->flushFileHeader() ...');
616 $this->flushFileHeader();
618 // Rewind seek position (to beginning of file) and update/flush file header
619 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->rewindUpdateSeekPosition() ...');
620 $this->rewindUpdateSeekPosition();
623 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
627 * Determines seek position
629 * @return $seekPosition Current seek position
631 public function determineSeekPosition () {
632 // Call pointer instance
633 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
634 $seekPosition = $this->getPointerInstance()->determineSeekPosition();
637 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d - EXIT!', $seekPosition));
638 return $seekPosition;
642 * Seek to given offset (default) or other possibilities as fseek() gives.
644 * @param $offset Offset to seek to (or used as "base" for other seeks)
645 * @param $whence Added to offset (default: only use offset to seek to)
646 * @return $status Status of file seek: 0 = success, -1 = failed
647 * @throws OutOfBoundsException If the position is not seekable
649 public function seek (int $offset, int $whence = SEEK_SET) {
650 // Validate parameter
651 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: offset=%d,whence=%d - CALLED!', $offset, $whence));
653 // No offset is smaller than zero
654 throw new OutOfBoundsException(sprintf('offset=%d is not valid', $offset));
657 // Call pointer instance
658 $status = $this->getPointerInstance()->seek($offset, $whence);
661 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: status[%s]=%d - EXIT!', gettype($status), $status));
666 * Reads given amount of bytes from file.
668 * @param $bytes Amount of bytes to read
669 * @return $data Data read from file
670 * @throws OutOfBoundsException If the position is not seekable
672 public function read (int $bytes = 0) {
673 // Validate parameter
674 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: bytes=%d - CALLED!', $bytes));
677 throw new OutOfBoundsException(sprintf('bytes=%d is not valid', $bytes));
680 // Call pointer instance
681 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->pointerInstance->read(%d) ...', $bytes));
682 $data = $this->getPointerInstance()->read($bytes);
684 // Update seek position
685 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->updateSeekPosition() ...');
686 $this->updateSeekPosition();
689 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: data[%s]=%s - EXIT!', gettype($data), $data));
694 * Rewinds to the beginning of the file
698 public function rewind () {
699 // Call pointer instance
700 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
701 $this->getPointerInstance()->rewind();
704 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
708 * Analyzes entries in index file. This will count all found (and valid)
709 * entries, mark invalid as damaged and count gaps ("fragmentation"). If
710 * only gaps are found, the file is considered as "virgin" (no entries).
713 * @throws BadMethodCallException If this method is called but file is not initialized
715 public function analyzeFileStructure () {
716 // Make sure the file is initialized
717 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
718 if (!$this->isFileInitialized()) {
720 throw new BadMethodCallException('Method called but file is not initialized.');
723 // Init counters and gaps array
724 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->initCounterGapsArrays() ...');
725 $this->initCountersGapsArray();
727 // Output message (as this may take some time)
728 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('Analyzing file structure ... (this may take some time)'));
730 // First Seek to right after file header
731 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->seek(%d) ...', $this->getHeaderSize() + 1));
732 $this->seek($this->getHeaderSize() + 1);
734 // Then try to load all entries
735 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, looping through file ...', $this->getSeekPosition()));
736 while ($this->isValid()) {
738 $current = $this->getCurrentBlock();
741 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: current=%s, calling this->readNextBlock() ...', $current));
742 $this->readNextBlock();
745 * If the block is empty, maybe the whole file is? This could mean
746 * that the file has been pre-allocated.
748 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: current(%d)[]=%s', strlen($current), gettype($current)));
749 if (empty(trim($current, chr(0)))) {
750 // Then skip this part
751 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: current[]=%s is empty - CONTINUE!', gettype($current)));
755 // Handle current record
756 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: current(%d)[%s]=%s', strlen($current), gettype($current), $current));
759 // If the last read block is empty, check gaps
760 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: current()=%d', strlen($current)));
761 if (empty(trim($current, chr(0)))) {
763 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Found a total of %d gaps.', count($this->gaps)));
765 // Check gaps, if the whole file is empty.
766 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->isFileGapsOnly() ...');
767 if ($this->isFileGapsOnly()) {
768 // Only gaps, so don't continue here.
769 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: File is gaps-only - EXIT!');
775 * The above call has calculated a total size of all gaps. If the
776 * percentage of gaps passes a "soft" limit and last
777 * defragmentation is to far in the past, or if a "hard" limit has
778 * reached, run defragmentation.
780 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->isDefragmentationNeeded() ...');
781 if ($this->isDefragmentationNeeded()) {
782 // Run "defragmentation"
783 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->doRunDefragmentation() ...');
784 $this->doRunDefragmentation();
788 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
792 * Reads next "block" of given bytes into $currentBlock field. THis method
793 * loads the whole file into memory when the file is just freshly
794 * initialized (only zeros in it).
797 * @throws InvalidArgumentException If a parameter is not valid
799 protected function readNextBlockByLength (int $length) {
800 // Validate parameter
801 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d,length=%d - CALLED!', $this->getSeekPosition(), $length));
804 throw new InvalidArgumentException(sprintf('length=%d is not valid', $length));
807 // Read possibly back-buffered bytes from previous call of next().
808 $data = $this->getBackBuffer();
811 * Read until a entry/block separator has been found. The next read
812 * "block" may not fit, so this loop will continue until the EOB or EOF
813 * has been reached whatever comes first.
815 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d,data()=%d', $this->getSeekPosition(), strlen($data)));
816 while ((!$this->isEndOfFileReached()) && (empty($data) || !self::isBlockSeparatorFound($data))) {
817 // Then read the next possible block
818 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, calling this->read(%d) ...', $this->getSeekPosition(), $length));
819 $block = $this->read($length);
821 // Is the block empty?
822 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: block()=%d,length=%d', strlen($block), $length));
823 if (strlen($block) == 0) {
824 // Read empty block, maybe EOF
825 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, block is empty, maybe EOF reached - BREAK!', $this->getSeekPosition()));
827 } elseif (empty(trim($block, chr(0)))) {
829 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, calling this->markCurrentBlockAsEmpty(%d) ...', $this->getSeekPosition(), $length));
830 $this->markCurrentBlockAsEmpty($length);
833 // At this block then
837 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d,data()=%d', $this->getSeekPosition(), strlen($data)));
841 * Init back-buffer which is the data that has been found beyond the
844 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->initBackBuffer(), clearing this->currentBlock ...');
845 $this->initBackBuffer();
846 $this->setCurrentBlock('');
849 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: data(%d)=%s', strlen($data), $data));
850 if (empty(trim($data, chr(0)))) {
851 // Yes, maybe whole file was ...
852 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, maybe empty file found - EXIT!', $this->getSeekPosition()));
857 $dataArray = explode(chr(BinaryFile::SEPARATOR_ENTRIES), $data);
859 // Left part is the actual block, right one the back-buffer data, if found
860 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: dataArray()=%d', count($dataArray)));
861 //* PRINTR-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: dataArray=%s', print_r($dataArray, true)));
862 $this->setCurrentBlock($dataArray[0]);
864 // Is back buffere data found?
865 if (isset($dataArray[1]) && !empty(trim($dataArray[1], chr(0)))) {
867 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting this->backBuffer=%s ...', $dataArray[1]));
868 $this->setBackBuffer($dataArray[1]);
872 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
876 * Pre-allocates file (if enabled) with some space for later faster write access.
878 * @param $type Type of the file
880 * @throws InvalidArgumentException If a parameter is empty
881 * @throws BadMethodCallException If this->stackInstance is not properly set
883 protected function preAllocateFileByTypeLength (string $type, int $minimumBlockLength) {
885 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: type=%s,minimumBlockLength=%d - CALLED!', $type, $minimumBlockLength));
888 throw new InvalidArgumentException('Parameter "type" is empty');
889 } elseif ($minimumBlockLength < 1) {
890 // Invalid block length
891 throw new InvalidArgumentException(sprintf('Parameter minimumBlockLength=%d is not valid', $minimumBlockLength));
892 } elseif (FrameworkBootstrap::getConfigurationInstance()->getConfigEntry($type . '_pre_allocate_enabled') != 'Y') {
893 // Don't continue here.
894 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Not pre-allocating file.'));
899 $fileSize = $this->getFileSize();
901 // Calulcate seek position
902 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: minimumBlockLength=%d,fileSize=%d', $minimumBlockLength, $fileSize));
903 $seekPosition = $this->getHeaderSize() + $minimumBlockLength * FrameworkBootstrap::getConfigurationInstance()->getConfigEntry($type . '_pre_allocate_count') + $fileSize ;
905 // Now simply write a NUL there. This will pre-allocate the file.
906 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->writeAtPosition(%d,NUL) ...', $seekPosition));
907 $this->writeAtPosition($seekPosition, chr(0));
909 // Is the seek position zero?
910 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: fileSize=%d', $fileSize));
911 if ($fileSize == 0) {
912 // Mark file as gaps-only
913 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->markGapsOnly(%s,%d) ...', $type, $minimumBlockLength));
914 $this->markFileGapsOnly($type, $minimumBlockLength);
916 // Analyze file structure
917 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->analyzeFileStructure() ...');
918 $this->analyzeFileStructure();
921 // Rewind seek position
922 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->rewind() ...');
926 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
930 * Checks wether the current entry is valid (not at the end of the file).
931 * This method will return true if an emptied (nulled) entry has been found.
933 * @return $isValid Whether the next entry is valid
934 * @throws InvalidArgumentException If a parameter is not valid
936 protected function isValidByLength (int $length) {
937 // Validate parameter
938 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: length=%d - CALLED!', $length));
941 throw new InvalidArgumentException(sprintf('Parameter length=%d is not valid', $length));
944 // Get current seek position
945 $seekPosition = $this->determineSeekPosition();
947 // Then try to read it
948 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d', $seekPosition));
949 $data = $this->read($length);
951 // If some bytes could be read, all is fine
952 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: data[%s]()=%d', gettype($data), strlen($data)));
953 $isValid = ((is_string($data)) && (strlen($data) > 0));
956 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isValid=%d', intval($isValid)));
957 $headerSize = $this->getHeaderSize();
959 // Is the seek position at or beyond the header?
960 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d,headerSize=%d', $seekPosition, $headerSize));
961 if ($seekPosition >= $headerSize) {
962 // Seek back to old position
963 $isValid = ($isValid && $this->seek($seekPosition) === 0);
965 // Seek directly behind the header
966 $isValid = ($isValid && $this->seek($headerSize) === 0);
970 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isValid=%d - EXIT!', intval($isValid)));
975 * Reads next "block" of bytes into $currentBlock field. THis method loads
976 * the whole file into memory when the file is just freshly initialized
977 * (only zeros in it).
981 protected abstract function readNextBlock ();
984 * Reads the file header
987 * @throws LogicException If both instances are not set
989 public abstract function readFileHeader ();
992 * Searches for next suitable gap the given length of data can fit in
993 * including padding bytes.
995 * @param $length Length of raw data
996 * @return $seekPosition Found next gap's seek position
997 * @throws InvalidArgumentException If the parameter is not valid
999 public function searchNextGap (int $length) {
1000 // If the file is only gaps, no need to seek
1001 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: length=%d - CALLED!', $length));
1004 throw new InvalidArgumentException(sprintf('length=%d is not valid', $length));
1005 } elseif ($this->isFileGapsOnly()) {
1007 * The first empty block is the 2nd one right after the header, so
1008 * one byte gap to the header.
1010 $seekPosition = ($this->getHeaderSize() + 2);
1013 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d - EXIT!', $seekPosition));
1014 return $seekPosition;
1018 $this->partialStub('length=' . $length);