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 parent::__construct($className);
103 * Setter for backBuffer field
105 * @param $backBuffer Characters to "store" in back-buffer
108 private function setBackBuffer (string $backBuffer) {
109 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting backBuffer(%d)=%s - CALLED!', strlen($backBuffer), $backBuffer));
110 $this->backBuffer = $backBuffer;
114 * Getter for backBuffer field
116 * @return $backBuffer Characters "stored" in back-buffer
118 private function getBackBuffer () {
119 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Getting this->backBuffer(%d)=%s - CALLED!', strlen($this->backBuffer), $this->backBuffer));
120 return $this->backBuffer;
124 * Setter for current field
126 * @param $current Characters to set a currently loaded block
129 private function setCurrentBlock (string $currentBlock) {
130 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting currentBlock(%d)=%s - CALLED!', strlen($currentBlock), $currentBlock));
131 $this->currentBlock = $currentBlock;
135 * Gets currently read data
137 * @return $current Currently read data
139 public function getCurrentBlock () {
140 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Getting this->currentBlock(%d)=%s - CALLED!', strlen($this->currentBlock), $this->currentBlock));
141 return $this->currentBlock;
145 * Getter for header size
147 * @return $totalEntries Size of file header
149 public final function getHeaderSize () {
150 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Getting this->headerSize=%d - CALLED!', $this->headerSize));
151 return $this->headerSize;
155 * Setter for header size
157 * @param $headerSize Size of file header
160 public final function setHeaderSize (int $headerSize) {
161 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting headerSize=%d - CALLED!', $headerSize));
162 $this->headerSize = $headerSize;
166 * Getter for header array
168 * @return $totalEntries Size of file header
170 public final function getHeader () {
172 return $this->header;
178 * @param $header Array for a file header
181 public final function setHeader (array $header) {
183 $this->header = $header;
187 * Getter for seek position
189 * @return $seekPosition Current seek position (stored here in object)
191 public final function getSeekPosition () {
192 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Getting this->seekPosition=%d - CALLED!', $this->seekPosition));
193 return $this->seekPosition;
197 * Setter for seek position
199 * @param $seekPosition Current seek position (stored here in object)
202 protected final function setSeekPosition (int $seekPosition) {
203 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting seekPosition=%d - CALLED!', $seekPosition));
204 $this->seekPosition = $seekPosition;
208 * Checks whether the abstracted file only contains gaps by counting all
209 * gaps' bytes together and compare it to total length.
211 * @return $isGapsOnly Whether the abstracted file only contains gaps
212 * @throws OutOfBoundsException If calculated file size is larger than actual
214 public function isFileGapsOnly () {
216 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
218 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->gaps()=%d', count($this->gaps)));
219 foreach ($this->gaps as $gap) {
220 // Calculate size of found gap: end-start including both
221 //* 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]));
222 $gapsSize += ($gap[BinaryFile::GAPS_INDEX_END] - $gap[BinaryFile::GAPS_INDEX_START]);
225 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: gapsSize=%d', $gapsSize));
228 // Total gap size + header size + 1 must be same as file size
229 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: gapsSize=%d,this->headerSize=%d,this->fileSize=%d', $gapsSize, $this->getHeaderSize(), $this->getFileSize()));
230 $determinedFileSize = ($gapsSize + $this->getHeaderSize() + 1);
232 // Should not be more!
233 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: determinedFileSize=%d,this->fileSize=%d', $determinedFileSize, $this->getFileSize()));
234 if ($determinedFileSize > $this->getFileSize()) {
236 throw new OutOfBoundsException(sprintf('determinedFileSize=%d is larger than this->fileSize=%d', $determinedFileSize, $this->getFileSize()));
240 $isGapsOnly = ($determinedFileSize == $this->getFileSize());
243 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isGapsOnly=%d - EXIT!', intval($isGapsOnly)));
248 * Marks whole file as gaps-only (freshly created file
250 * @param $type Type of file
251 * @param $minimumBlockLength Minimum block length
254 private function markFileGapsOnly (string $type, int $minimumBlockLength) {
255 // Is config cache there?
256 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: type=%s,minimumBlockLength=%d - CALLED!', $type, $minimumBlockLength));
257 if (!isset(self::$configCache[$type . '_pre_allocate_count'])) {
259 self::$configCache[$type . '_pre_allocate_count'] = FrameworkBootstrap::getConfigurationInstance()->getConfigEntry($type . '_pre_allocate_count');
262 // Very simple to do ...
263 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: self:configCache[%s_pre_allocate_count]()=%d', count(self::$configCache[$type . '_pre_allocate_count'])));
264 for ($idx = 0; $idx < self::$configCache[$type . '_pre_allocate_count']; $idx++) {
265 // Calculate start/end positions
266 $startPosition = $idx * $minimumBlockLength;
267 $endPosition = $idx * $minimumBlockLength + $minimumBlockLength;
269 // Mark start and end position as gap
270 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->addGap(%d, %d) ...', $startPosition, $endPosition));
271 $this->addGap($startPosition, $endPosition);
275 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
279 * Adds a gap for given start and end position
281 * @param $startPosition Start seek position
282 * @param $endPosition End seek position
285 private function addGap(int $startPosition, int $endPosition) {
286 // Push to gaps array
287 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: startPosition=%d,endPosition=%d - CALLED!', $startPosition, $endPosition));
288 array_push($this->gaps, [
289 BinaryFile::GAPS_INDEX_START => $startPosition,
290 BinaryFile::GAPS_INDEX_END => $endPosition,
294 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
298 * Initializes the back-buffer by setting it to an empty string.
302 private function initBackBuffer () {
303 // Simply call the setter
304 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
305 $this->setBackBuffer('');
308 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
312 * Seeks to beginning of file, updates seek position in this object and
313 * flushes the header.
315 * @param $flushHeader Wether the file's header should be flushed (default: false)
318 protected function rewindUpdateSeekPosition (bool $flushHeader = false) {
319 // Seek to beginning of file
320 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: flushHeader=%d - CALLED!', intval($flushHeader)));
323 // And update seek position ...
324 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->updateSeekPosition() ...');
325 $this->updateSeekPosition();
329 // ... to write it back into the file
330 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->flushFileHeader() ...');
331 $this->flushFileHeader();
335 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
339 * Seeks to old position
343 protected function seekToOldPosition () {
344 // Seek to currently ("old") saved position
345 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
346 $this->seek($this->determineSeekPosition());
349 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
353 * Initializes this file class
355 * @param $fileInfoInstance An instance of a SplFileInfo class
358 protected function initFile (SplFileInfo $fileInfoInstance) {
359 // Get a file i/o pointer instance
360 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: fileInfoInstance[%s]=%s - CALLED!', get_class($fileInfoInstance), $fileInfoInstance));
361 $pointerInstance = ObjectFactory::createObjectByConfiguredName('file_raw_input_output_class', array($fileInfoInstance));
363 // ... and set it here
364 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting pointerInstance=%s ...', $pointerInstance->__toString()));
365 $this->setPointerInstance($pointerInstance);
368 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
372 * Marks the currently loaded block as empty (with length of the block)
374 * @param $length Length of the block
376 * @throws InvalidArgumentException If a parameter is invalid
378 protected function markCurrentBlockAsEmpty (int $length) {
379 // Validate parameter
380 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: length=%d - CALLED!', $length));
382 // Length cannot below one
383 throw new InvalidArgumentException(sprintf('length=%d is not valid', $length));
386 // Get current seek position
387 $currentPosition = $this->determineSeekPosition();
389 // Now add it as gap entry
390 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->addGap(%d, %d) ..', ($currentPosition - $length), $currentPosition));
391 $this->addGap(($currentPosition - $length), $currentPosition);
394 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
398 * Initializes counter for valid entries, arrays for damaged entries and
399 * an array for gap seek positions. If you call this method on your own,
400 * please re-analyze the file structure. So you are better to call
401 * analyzeFileStructure() instead of this method.
405 public function initCountersGapsArray () {
406 // Init counter and seek position to header size
407 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->determineSeekPosition() - CALLED!');
408 $seekPosition = $this->getSeekPosition();
411 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d', $seekPosition));
412 $this->setCounter(0);
415 $headerSize = $this->getHeaderSize();
418 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting this->seekPosition=%d ...', $headerSize));
419 $this->setSeekPosition($headerSize);
423 $this->damagedEntries = [];
425 // Seek back to old position
426 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->seek(%d) ...', $seekPosition));
427 $this->seek($seekPosition);
430 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
434 * Updates seekPosition attribute from file to avoid to much access on file.
438 public function updateSeekPosition () {
439 // Get key (= seek position)
440 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
441 $seekPosition = $this->determineSeekPosition();
444 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d', $seekPosition));
445 $this->setSeekPosition($seekPosition);
448 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
452 * Checks whether the block separator has been found
454 * @param $str String to look in
455 * @return $isFound Whether the block separator has been found
456 * @throws InvalidArgumentException If a parameter is not valid
458 public static function isBlockSeparatorFound (string $str) {
459 // Validate parameter
460 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: str=%s - CALLED!', $str));
463 throw new InvalidArgumentException('Parameter "str" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
467 $isFound = (strpos($str, chr(BinaryFile::SEPARATOR_ENTRIES)) !== false);
470 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isFound=%d - EXIT!', intval($isFound)));
475 * Writes data at given position
477 * @param $seekPosition Seek position
478 * @param $data Data to be written
479 * @param $flushHeader Whether to flush the header (default: flush)
481 * @throws OutOfBoundsException If the position is not seekable
482 * @throws InvalidArgumentException If a parameter is invalid
484 public function writeData (int $seekPosition, string $data, bool $flushHeader = true) {
485 // Validate parameter
486 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%s,data()=%d,flushHeader=%d - CALLED!', $seekPosition, strlen($data), intval($flushHeader)));
487 if ($seekPosition < 0) {
488 // Invalid seek position
489 throw new OutOfBoundsException(sprintf('seekPosition=%d is not valid', $seekPosition));
490 } elseif (empty($data)) {
491 // Empty data is invalid, too
492 throw new InvalidArgumentException('Parameter "data" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
495 // Write data at given position
496 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->writeAtPosition(%d,%s) ...', $seekPosition, $data));
497 $this->writeAtPosition($seekPosition, $data);
500 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->incrementCounter() ...');
501 $this->incrementCounter();
503 // Update seek position
504 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->updateSeekPosition() ...');
505 $this->updateSeekPosition();
508 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: flushHeader=%d', intval($flushHeader)));
509 if ($flushHeader === true) {
511 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->flushFileHeader() ...');
512 $this->flushFileHeader();
514 // Seek to old position
515 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->seekToOldPosition() ...');
516 $this->seekToOldPosition();
520 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
524 * Writes at given position by seeking to it.
526 * @param $seekPosition Seek position in file
527 * @param $dataStream Data to be written
528 * @return mixed Number of writes bytes or false on error
529 * @throws OutOfBoundsException If the position is not seekable
530 * @throws InvalidArgumentException If a parameter is not valid
532 public function writeAtPosition (int $seekPosition, string $dataStream) {
533 // Validate parameter
534 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d,dataStream(%d)=%s - CALLED!', $seekPosition, strlen($dataStream), $dataStream));
535 if ($seekPosition < 0) {
536 // Invalid seek position
537 throw new OutOfBoundsException(sprintf('seekPosition=%d is not valid.', $seekPosition));
538 } elseif (empty($dataStream)) {
540 throw new InvalidArgumentException('Parameter "dataStream" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
543 // Call pointer's method
544 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->pointerInstance->writeAtPosition(%d, %s) ...', $seekPosition, $dataStream));
545 $status = $this->getPointerInstance()->writeAtPosition($seekPosition, $dataStream);
548 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: status[%s]=%d - EXIT!', gettype($status), $status));
553 * Checks whether the file header is initialized
555 * @return $isInitialized Whether the file header is initialized
557 public function isFileHeaderInitialized () {
558 // Default is not initialized
559 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
560 $isInitialized = false;
562 // Is the file initialized?
563 if ($this->isFileInitialized()) {
564 // Some bytes has been written, so rewind to start of it.
568 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->readFileHeader() ...');
569 $this->readFileHeader();
572 $headerCount = count($this->getHeader());
574 // The above method does already check the header
575 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: headerCount=%d', $headerCount));
576 $isInitialized = ($headerCount > 0);
580 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isInitialized=%d - EXIT!', intval($isInitialized)));
581 return $isInitialized;
585 * Checks whether the assigned file has been initialized
587 * @return $isInitialized Whether the file's size is zero
588 * @throws UnexpectedValueException If an unexpected value was returned
590 public function isFileInitialized () {
591 // Get it from iterator which holds the pointer instance. If false is returned
592 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
593 $fileSize = $this->size();
596 * The returned file size should not be false or NULL as this means
597 * that the pointer class does not work correctly.
599 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: fileSize[%s]=%d', gettype($fileSize), $fileSize));
600 if (!is_int($fileSize)) {
602 throw new UnexpectedValueException(sprintf('fileSize[]=%s is unexpected', gettype($fileSize)), FrameworkInterface::EXCEPTION_UNEXPECTED_VALUE);
605 // Is more than 0 returned?
606 $isInitialized = ($fileSize > 0);
609 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isInitialized=%d - EXIT!', intval($isInitialized)));
610 return $isInitialized;
614 * Creates the assigned file
617 * @throws BadMethodCallException If this file's header is already initialized
619 public function createFileHeader () {
620 // The file's header should not be initialized here
621 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
622 if ($this->isFileHeaderInitialized()) {
624 //* DEBUG-DIE: */ ApplicationEntryPoint::exitApplication(sprintf('[%s:%d]: this=%s', __METHOD__, __LINE__, print_r($this, TRUE)));
625 throw new BadMethodCallException('File header is already initialized but method called');
628 // Simple flush file header which will create it.
629 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->flushFileHeader() ...');
630 $this->flushFileHeader();
632 // Rewind seek position (to beginning of file) and update/flush file header
633 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->rewindUpdateSeekPosition() ...');
634 $this->rewindUpdateSeekPosition();
637 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
641 * Determines seek position
643 * @return $seekPosition Current seek position
645 public function determineSeekPosition () {
646 // Call pointer instance
647 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
648 $seekPosition = $this->getPointerInstance()->determineSeekPosition();
651 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d - EXIT!', $seekPosition));
652 return $seekPosition;
656 * Seek to given offset (default) or other possibilities as fseek() gives.
658 * @param $offset Offset to seek to (or used as "base" for other seeks)
659 * @param $whence Added to offset (default: only use offset to seek to)
660 * @return $status Status of file seek: 0 = success, -1 = failed
661 * @throws OutOfBoundsException If the position is not seekable
663 public function seek (int $offset, int $whence = SEEK_SET) {
664 // Validate parameter
665 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: offset=%d,whence=%d - CALLED!', $offset, $whence));
667 // No offset is smaller than zero
668 throw new OutOfBoundsException(sprintf('offset=%d is not valid', $offset));
671 // Call pointer instance
672 $status = $this->getPointerInstance()->seek($offset, $whence);
675 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: status[%s]=%d - EXIT!', gettype($status), $status));
680 * Reads given amount of bytes from file.
682 * @param $bytes Amount of bytes to read
683 * @return $data Data read from file
684 * @throws OutOfBoundsException If the position is not seekable
686 public function read (int $bytes = 0) {
687 // Validate parameter
688 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: bytes=%d - CALLED!', $bytes));
691 throw new OutOfBoundsException(sprintf('bytes=%d is not valid', $bytes));
694 // Call pointer instance
695 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->pointerInstance->read(%d) ...', $bytes));
696 $data = $this->getPointerInstance()->read($bytes);
698 // Update seek position
699 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->updateSeekPosition() ...');
700 $this->updateSeekPosition();
703 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: data[%s]=%s - EXIT!', gettype($data), $data));
708 * Rewinds to the beginning of the file
712 public function rewind () {
713 // Call pointer instance
714 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
715 $this->getPointerInstance()->rewind();
718 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
722 * Analyzes entries in index file. This will count all found (and valid)
723 * entries, mark invalid as damaged and count gaps ("fragmentation"). If
724 * only gaps are found, the file is considered as "virgin" (no entries).
727 * @throws BadMethodCallException If this method is called but file is not initialized
729 public function analyzeFileStructure () {
730 // Make sure the file is initialized
731 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
732 if (!$this->isFileInitialized()) {
734 throw new BadMethodCallException('Method called but file is not initialized.');
737 // Init counters and gaps array
738 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->initCounterGapsArrays() ...');
739 $this->initCountersGapsArray();
741 // Output message (as this may take some time)
742 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('Analyzing file structure ... (this may take some time)'));
744 // First Seek to right after file header
745 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->seek(%d) ...', $this->getHeaderSize() + 1));
746 $this->seek($this->getHeaderSize() + 1);
748 // Then try to load all entries
749 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, looping through file ...', $this->getSeekPosition()));
750 while ($this->isValid()) {
752 $current = $this->getCurrentBlock();
755 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: current=%s, calling this->readNextBlock() ...', $current));
756 $this->readNextBlock();
759 * If the block is empty, maybe the whole file is? This could mean
760 * that the file has been pre-allocated.
762 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: current(%d)[]=%s', strlen($current), gettype($current)));
763 if (empty(trim($current, chr(0)))) {
764 // Then skip this part
765 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: current[]=%s is empty - CONTINUE!', gettype($current)));
769 // Handle current record
770 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: current(%d)[%s]=%s', strlen($current), gettype($current), $current));
773 // If the last read block is empty, check gaps
774 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: current()=%d', strlen($current)));
775 if (empty(trim($current, chr(0)))) {
777 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Found a total of %d gaps.', count($this->gaps)));
779 // Check gaps, if the whole file is empty.
780 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->isFileGapsOnly() ...');
781 if ($this->isFileGapsOnly()) {
782 // Only gaps, so don't continue here.
783 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: File is gaps-only - EXIT!');
789 * The above call has calculated a total size of all gaps. If the
790 * percentage of gaps passes a "soft" limit and last
791 * defragmentation is to far in the past, or if a "hard" limit has
792 * reached, run defragmentation.
794 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->isDefragmentationNeeded() ...');
795 if ($this->isDefragmentationNeeded()) {
796 // Run "defragmentation"
797 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->doRunDefragmentation() ...');
798 $this->doRunDefragmentation();
802 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
806 * Reads next "block" of given bytes into $currentBlock field. THis method
807 * loads the whole file into memory when the file is just freshly
808 * initialized (only zeros in it).
811 * @throws InvalidArgumentException If a parameter is not valid
813 protected function readNextBlockByLength (int $length) {
814 // Validate parameter
815 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d,length=%d - CALLED!', $this->getSeekPosition(), $length));
818 throw new InvalidArgumentException(sprintf('length=%d is not valid', $length));
821 // Read possibly back-buffered bytes from previous call of next().
822 $data = $this->getBackBuffer();
825 * Read until a entry/block separator has been found. The next read
826 * "block" may not fit, so this loop will continue until the EOB or EOF
827 * has been reached whatever comes first.
829 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d,data()=%d', $this->getSeekPosition(), strlen($data)));
830 while ((!$this->isEndOfFileReached()) && (empty($data) || !self::isBlockSeparatorFound($data))) {
831 // Then read the next possible block
832 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, calling this->read(%d) ...', $this->getSeekPosition(), $length));
833 $block = $this->read($length);
835 // Is the block empty?
836 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: block()=%d,length=%d', strlen($block), $length));
837 if (strlen($block) == 0) {
838 // Read empty block, maybe EOF
839 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, block is empty, maybe EOF reached - BREAK!', $this->getSeekPosition()));
841 } elseif (empty(trim($block, chr(0)))) {
843 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, calling this->markCurrentBlockAsEmpty(%d) ...', $this->getSeekPosition(), $length));
844 $this->markCurrentBlockAsEmpty($length);
847 // At this block then
851 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d,data()=%d', $this->getSeekPosition(), strlen($data)));
855 * Init back-buffer which is the data that has been found beyond the
858 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->initBackBuffer(), clearing this->currentBlock ...');
859 $this->initBackBuffer();
860 $this->setCurrentBlock('');
863 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: data(%d)=%s', strlen($data), $data));
864 if (empty(trim($data, chr(0)))) {
865 // Yes, maybe whole file was ...
866 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, maybe empty file found - EXIT!', $this->getSeekPosition()));
871 $dataArray = explode(chr(BinaryFile::SEPARATOR_ENTRIES), $data);
873 // Left part is the actual block, right one the back-buffer data, if found
874 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: dataArray()=%d', count($dataArray)));
875 //* PRINTR-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: dataArray=%s', print_r($dataArray, true)));
876 $this->setCurrentBlock($dataArray[0]);
878 // Is back buffere data found?
879 if (isset($dataArray[1]) && !empty(trim($dataArray[1], chr(0)))) {
881 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting this->backBuffer=%s ...', $dataArray[1]));
882 $this->setBackBuffer($dataArray[1]);
886 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
890 * Pre-allocates file (if enabled) with some space for later faster write access.
892 * @param $type Type of the file
894 * @throws InvalidArgumentException If a parameter is empty
895 * @throws BadMethodCallException If this->stackInstance is not properly set
897 protected function preAllocateFileByTypeLength (string $type, int $minimumBlockLength) {
899 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: type=%s,minimumBlockLength=%d - CALLED!', $type, $minimumBlockLength));
902 throw new InvalidArgumentException('Parameter "type" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
903 } elseif ($minimumBlockLength < 1) {
904 // Invalid block length
905 throw new InvalidArgumentException(sprintf('Parameter minimumBlockLength=%d is not valid', $minimumBlockLength));
906 } elseif (FrameworkBootstrap::getConfigurationInstance()->getConfigEntry($type . '_pre_allocate_enabled') != 'Y') {
907 // Don't continue here.
908 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Not pre-allocating file.'));
913 $fileSize = $this->getFileSize();
915 // Calulcate seek position
916 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: minimumBlockLength=%d,fileSize=%d', $minimumBlockLength, $fileSize));
917 $seekPosition = $this->getHeaderSize() + $minimumBlockLength * FrameworkBootstrap::getConfigurationInstance()->getConfigEntry($type . '_pre_allocate_count') + $fileSize ;
919 // Now simply write a NUL there. This will pre-allocate the file.
920 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->writeAtPosition(%d,NUL) ...', $seekPosition));
921 $this->writeAtPosition($seekPosition, chr(0));
923 // Is the seek position zero?
924 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: fileSize=%d', $fileSize));
925 if ($fileSize == 0) {
926 // Mark file as gaps-only
927 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->markGapsOnly(%s,%d) ...', $type, $minimumBlockLength));
928 $this->markFileGapsOnly($type, $minimumBlockLength);
930 // Analyze file structure
931 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->analyzeFileStructure() ...');
932 $this->analyzeFileStructure();
935 // Rewind seek position
936 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->rewind() ...');
940 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
944 * Checks wether the current entry is valid (not at the end of the file).
945 * This method will return true if an emptied (nulled) entry has been found.
947 * @return $isValid Whether the next entry is valid
948 * @throws InvalidArgumentException If a parameter is not valid
950 protected function isValidByLength (int $length) {
951 // Validate parameter
952 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: length=%d - CALLED!', $length));
955 throw new InvalidArgumentException(sprintf('Parameter length=%d is not valid', $length));
958 // Get current seek position
959 $seekPosition = $this->determineSeekPosition();
961 // Then try to read it
962 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d', $seekPosition));
963 $data = $this->read($length);
965 // If some bytes could be read, all is fine
966 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: data[%s]()=%d', gettype($data), strlen($data)));
967 $isValid = ((is_string($data)) && (strlen($data) > 0));
970 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isValid=%d', intval($isValid)));
971 $headerSize = $this->getHeaderSize();
973 // Is the seek position at or beyond the header?
974 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d,headerSize=%d', $seekPosition, $headerSize));
975 if ($seekPosition >= $headerSize) {
976 // Seek back to old position
977 $isValid = ($isValid && $this->seek($seekPosition) === 0);
979 // Seek directly behind the header
980 $isValid = ($isValid && $this->seek($headerSize) === 0);
984 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isValid=%d - EXIT!', intval($isValid)));
989 * Reads next "block" of bytes into $currentBlock field. THis method loads
990 * the whole file into memory when the file is just freshly initialized
991 * (only zeros in it).
995 protected abstract function readNextBlock ();
998 * Reads the file header
1001 * @throws LogicException If both instances are not set
1003 public abstract function readFileHeader ();
1006 * Searches for next suitable gap the given length of data can fit in
1007 * including padding bytes.
1009 * @param $length Length of raw data
1010 * @return $seekPosition Found next gap's seek position
1011 * @throws InvalidArgumentException If the parameter is not valid
1013 public function searchNextGap (int $length) {
1014 // If the file is only gaps, no need to seek
1015 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: length=%d - CALLED!', $length));
1018 throw new InvalidArgumentException(sprintf('length=%d is not valid', $length));
1019 } elseif ($this->isFileGapsOnly()) {
1021 * The first empty block is the 2nd one right after the header, so
1022 * one byte gap to the header.
1024 $seekPosition = ($this->getHeaderSize() + 2);
1027 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d - EXIT!', $seekPosition));
1028 return $seekPosition;
1032 DebugMiddleware::getSelfInstance()->partialStub('length=' . $length);