3 namespace Org\Mxchange\CoreFramework\Filesystem\File;
5 // Import framework stuff
6 use Org\Mxchange\CoreFramework\Bootstrap\FrameworkBootstrap;
7 use Org\Mxchange\CoreFramework\Factory\Object\ObjectFactory;
8 use Org\Mxchange\CoreFramework\Filesystem\Block;
9 use Org\Mxchange\CoreFramework\Filesystem\Block\CalculatableBlock;
10 use Org\Mxchange\CoreFramework\Filesystem\File\BaseAbstractFile;
13 use \BadMethodCallException;
14 use \InvalidArgumentException;
16 use \UnexpectedValueException;
19 * A general binary file class
21 * @author Roland Haeder <webmaster@ship-simu.org>
23 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2020 Core Developer Team
24 * @license GNU GPL 3.0 or any newer version
25 * @link http://www.ship-simu.org
27 * This program is free software: you can redistribute it and/or modify
28 * it under the terms of the GNU General Public License as published by
29 * the Free Software Foundation, either version 3 of the License, or
30 * (at your option) any later version.
32 * This program is distributed in the hope that it will be useful,
33 * but WITHOUT ANY WARRANTY; without even the implied warranty of
34 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35 * GNU General Public License for more details.
37 * You should have received a copy of the GNU General Public License
38 * along with this program. If not, see <http://www.gnu.org/licenses/>.
40 abstract class BaseBinaryFile extends BaseAbstractFile {
42 * Separator for header data
44 const SEPARATOR_HEADER_DATA = 0x01;
47 * Separator header->entries
49 const SEPARATOR_HEADER_ENTRIES = 0x02;
52 * Separator group->hash
54 const SEPARATOR_GROUP_HASH = 0x03;
57 * Separator hash->value
59 const SEPARATOR_HASH_VALUE = 0x04;
62 * Separator entry->entry
64 const SEPARATOR_ENTRIES = 0x05;
67 * Separator type->position
69 const SEPARATOR_TYPE_POSITION = 0x06;
74 const LENGTH_COUNT = 20;
79 const LENGTH_POSITION = 20;
84 const LENGTH_GROUP = 10;
87 * Maximum length of entry type
89 const LENGTH_TYPE = 20;
91 //***** Array elements for 'gaps' array *****
96 const GAPS_INDEX_START = 'start';
101 const GAPS_INDEX_END = 'end';
104 * Current seek position
106 private $seekPosition = 0;
111 private $headerSize = 0;
116 private $header = [];
119 * Seek positions for gaps ("fragmentation")
124 * Seek positions for damaged entries (e.g. mismatching hash sum, ...)
126 private $damagedEntries = [];
131 private $backBuffer = '';
134 * Currently loaded block (will be returned by current())
136 private $currentBlock = '';
139 * An instance of a Block class
141 private $blockInstance = NULL;
144 * Protected constructor
146 * @param $className Name of the class
149 protected function __construct (string $className) {
150 // Call parent constructor
151 parent::__construct($className);
153 // Init counters and gaps array
154 $this->initCountersGapsArray();
158 * Setter for Block instance
160 * @param $blockInstance An instance of an Block class
163 protected final function setBlockInstance (Block $blockInstance) {
164 $this->blockInstance = $blockInstance;
168 * Getter for Block instance
170 * @return $blockInstance An instance of an Block class
172 public final function getBlockInstance () {
173 return $this->blockInstance;
177 * Setter for backBuffer field
179 * @param $backBuffer Characters to "store" in back-buffer
182 private function setBackBuffer (string $backBuffer) {
183 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting backBuffer(%d)=%s - CALLED!', strlen($backBuffer), $backBuffer));
184 $this->backBuffer = $backBuffer;
188 * Getter for backBuffer field
190 * @return $backBuffer Characters "stored" in back-buffer
192 private function getBackBuffer () {
193 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Getting this->backBuffer(%d)=%s - CALLED!', strlen($this->backBuffer), $this->backBuffer));
194 return $this->backBuffer;
198 * Setter for current field
200 * @param $current Characters to set a currently loaded block
203 private function setCurrentBlock (string $currentBlock) {
204 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting currentBlock(%d)=%s - CALLED!', strlen($currentBlock), $currentBlock));
205 $this->currentBlock = $currentBlock;
209 * Gets currently read data
211 * @return $current Currently read data
213 public function getCurrentBlock () {
214 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Getting this->currentBlock(%d)=%s - CALLED!', strlen($this->currentBlock), $this->currentBlock));
215 return $this->currentBlock;
219 * Getter for header size
221 * @return $totalEntries Size of file header
223 public final function getHeaderSize () {
224 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Getting this->headerSize=%d - CALLED!', $this->headerSize));
225 return $this->headerSize;
229 * Setter for header size
231 * @param $headerSize Size of file header
234 public final function setHeaderSize (int $headerSize) {
235 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting headerSize=%d - CALLED!', $headerSize));
236 $this->headerSize = $headerSize;
240 * Getter for header array
242 * @return $totalEntries Size of file header
244 public final function getHeader () {
246 return $this->header;
252 * @param $header Array for a file header
255 public final function setHeader (array $header) {
257 $this->header = $header;
261 * Getter for seek position
263 * @return $seekPosition Current seek position (stored here in object)
265 public final function getSeekPosition () {
266 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Getting this->seekPosition=%d - CALLED!', $this->seekPosition));
267 return $this->seekPosition;
271 * Setter for seek position
273 * @param $seekPosition Current seek position (stored here in object)
276 protected final function setSeekPosition (int $seekPosition) {
277 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting seekPosition=%d - CALLED!', $seekPosition));
278 $this->seekPosition = $seekPosition;
282 * Marks whole file as gaps-only (freshly created file
284 * @param $type Type of file
285 * @param $minimumBlockLength Minimum block length
288 private function markFileGapsOnly (string $type, int $minimumBlockLength) {
289 // Very simple to do ...
290 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: type=%s,minimumBlockLength=%d - CALLED!', $type, $minimumBlockLength));
291 for ($idx = 0; $idx < FrameworkBootstrap::getConfigurationInstance()->getConfigEntry($type . '_pre_allocate_count'); $idx++) {
292 // Mark start and end position as gap
293 $this->addGap($idx * $minimumBlockLength, $idx * $minimumBlockLength + $minimumBlockLength);
297 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
301 * Checks whether the abstracted file only contains gaps by counting all
302 * gaps' bytes together and compare it to total length.
304 * @return $isGapsOnly Whether the abstracted file only contains gaps
306 private function isFileGapsOnly () {
308 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
310 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->gaps()=%d', count($this->gaps)));
311 foreach ($this->gaps as $gap) {
312 // Calculate size of found gap: end-start including both
313 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: gap[%s]=%d,ga[%s]=%d', self::GAPS_INDEX_START, $gap[self::GAPS_INDEX_START], self::GAPS_INDEX_END, $gap[self::GAPS_INDEX_END]));
314 $gapsSize += ($gap[self::GAPS_INDEX_END] - $gap[self::GAPS_INDEX_START]);
317 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: gapsSize=%d', $gapsSize));
320 // Total gap size + header size must be same as file size
321 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: gapsSize=%d,this->fileSize=%d', $gapsSize, $this->getFileSize()));
322 $isGapsOnly = ($gapsSize + 1 == $this->getFileSize());
325 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isGapsOnly=%d - EXIT!', intval($isGapsOnly)));
330 * Adds a gap for given start and end position
332 * @param $startPosition Start seek position
333 * @param $endPosition End seek position
336 private function addGap(int $startPosition, int $endPosition) {
337 // Push to gaps array
338 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: startPosition=%d,endPosition=%d - CALLED!', $startPosition, $endPosition));
339 array_push($this->gaps, [
340 self::GAPS_INDEX_START => $startPosition,
341 self::GAPS_INDEX_END => $endPosition,
345 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
349 * Initializes the back-buffer by setting it to an empty string.
353 private function initBackBuffer () {
354 // Simply call the setter
355 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
356 $this->setBackBuffer('');
359 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
363 * Seeks to beginning of file, updates seek position in this object and
364 * flushes the header.
366 * @param $flushHeader Wether the file's header should be flushed (default: false)
369 protected function rewindUpdateSeekPosition (bool $flushHeader = false) {
370 // Seek to beginning of file
371 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: flushHeader=%d - CALLED!', intval($flushHeader)));
374 // And update seek position ...
375 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->updateSeekPosition() ...');
376 $this->updateSeekPosition();
380 // ... to write it back into the file
381 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->flushFileHeader() ...');
382 $this->flushFileHeader();
386 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
390 * Seeks to old position
394 protected function seekToOldPosition () {
395 // Seek to currently ("old") saved position
396 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
397 $this->seek($this->determineSeekPosition());
400 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
404 * Initializes this file class
406 * @param $fileInfoInstance An instance of a SplFileInfo class
409 protected function initFile (SplFileInfo $fileInfoInstance) {
410 // Get a file i/o pointer instance
411 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: fileInfoInstance[%s]=%s - CALLED!', get_class($fileInfoInstance), $fileInfoInstance));
412 $pointerInstance = ObjectFactory::createObjectByConfiguredName('file_raw_input_output_class', array($fileInfoInstance));
414 // ... and set it here
415 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Setting pointerInstance=%s ...', $pointerInstance->__toString()));
416 $this->setPointerInstance($pointerInstance);
419 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
423 * Marks the currently loaded block as empty (with length of the block)
425 * @param $length Length of the block
427 * @throws InvalidArgumentException If a parameter is invalid
429 protected function markCurrentBlockAsEmpty (int $length) {
430 // Validate parameter
431 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: length=%d - CALLED!', $length));
433 // Length cannot below one
434 throw new InvalidArgumentException(sprintf('length=%d is not valid', $length));
437 // Get current seek position
438 $currentPosition = $this->determineSeekPosition();
440 // Now add it as gap entry
441 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: currentPosition=%d', $currentPosition));
442 $this->addGap(($currentPosition - $length), $currentPosition);
445 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
449 * Initializes counter for valid entries, arrays for damaged entries and
450 * an array for gap seek positions. If you call this method on your own,
451 * please re-analyze the file structure. So you are better to call
452 * analyzeFileStructure() instead of this method.
456 public function initCountersGapsArray () {
457 // Init counter and seek position to header size
458 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
459 $this->setCounter(0);
460 $this->setSeekPosition($this->getHeaderSize());
464 $this->damagedEntries = [];
467 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
471 * Updates seekPosition attribute from file to avoid to much access on file.
475 public function updateSeekPosition () {
476 // Get key (= seek position)
477 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
478 $seekPosition = $this->determineSeekPosition();
481 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d', $seekPosition));
482 $this->setSeekPosition($seekPosition);
485 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
489 * Checks whether the block separator has been found
491 * @param $str String to look in
492 * @return $isFound Whether the block separator has been found
493 * @throws InvalidArgumentException If a parameter is not valid
495 public static function isBlockSeparatorFound (string $str) {
496 // Validate parameter
497 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: str=%s - CALLED!', $str));
500 throw new InvalidArgumentException('Parameter "str" is empty');
504 $isFound = (strpos($str, chr(self::SEPARATOR_ENTRIES)) !== false);
507 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isFound=%d - EXIT!', intval($isFound)));
512 * Writes data at given position
514 * @param $seekPosition Seek position
515 * @param $data Data to be written
516 * @param $flushHeader Whether to flush the header (default: flush)
518 * @throws InvalidArgumentException If a parameter is invalid
520 public function writeData (int $seekPosition, string $data, bool $flushHeader = true) {
521 // Validate parameter
522 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%s,data()=%d,flushHeader=%d - CALLED!', $seekPosition, strlen($data), intval($flushHeader)));
523 if ($seekPosition < 0) {
524 // Invalid seek position
525 throw new InvalidArgumentException(sprintf('seekPosition=%d is not valid', $seekPosition));
526 } elseif (empty($data)) {
527 // Empty data is invalid, too
528 throw new InvalidArgumentException('Parameter "data" is empty');
531 // Write data at given position
532 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->writeAtPosition(%d,%s) ...', $seekPosition, $data));
533 $this->writeAtPosition($seekPosition, $data);
536 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->incrementCounter() ...');
537 $this->incrementCounter();
539 // Update seek position
540 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->updateSeekPosition() ...');
541 $this->updateSeekPosition();
544 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: flushHeader=%d', intval($flushHeader)));
545 if ($flushHeader === true) {
547 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->flushFileHeader() ...');
548 $this->flushFileHeader();
550 // Seek to old position
551 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->seekToOldPosition() ...');
552 $this->seekToOldPosition();
556 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
560 * Writes at given position by seeking to it.
562 * @param $seekPosition Seek position in file
563 * @param $dataStream Data to be written
564 * @return mixed Number of writes bytes or false on error
565 * @throws InvalidArgumentException If a parameter is not valid
567 public function writeAtPosition (int $seekPosition, string $dataStream) {
568 // Validate parameter
569 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d,dataStream(%d)=%s - CALLED!', $seekPosition, strlen($dataStream), $dataStream));
570 if ($seekPosition < 0) {
571 // Invalid seek position
572 throw new InvalidArgumentException(sprintf('seekPosition=%d is not valid.', $seekPosition));
573 } elseif (empty($dataStream)) {
575 throw new InvalidArgumentException('Parameter "dataStream" is empty');
578 // Call pointer's method
579 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->pointerInstance->writeAtPosition(%d, %s) ...', $seekPosition, $dataStream));
580 $status = $this->getPointerInstance()->writeAtPosition($seekPosition, $dataStream);
583 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: status[%s]=%d - EXIT!', gettype($status), $status));
588 * Checks whether the file header is initialized
590 * @return $isInitialized Whether the file header is initialized
592 public function isFileHeaderInitialized () {
593 // Default is not initialized
594 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
595 $isInitialized = false;
597 // Is the file initialized?
598 if ($this->isFileInitialized()) {
599 // Some bytes has been written, so rewind to start of it.
603 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->readFileHeader() ...');
604 $this->readFileHeader();
607 $headerCount = count($this->getHeader());
609 // The above method does already check the header
610 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: headerCount=%d', $headerCount));
611 $isInitialized = ($headerCount > 0);
615 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isInitialized=%d - EXIT!', intval($isInitialized)));
616 return $isInitialized;
620 * Checks whether the assigned file has been initialized
622 * @return $isInitialized Whether the file's size is zero
623 * @throws UnexpectedValueException If an unexpected value was returned
625 public function isFileInitialized () {
626 // Get it from iterator which holds the pointer instance. If false is returned
627 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
628 $fileSize = $this->size();
631 * The returned file size should not be false or NULL as this means
632 * that the pointer class does not work correctly.
634 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: fileSize[%s]=%d', gettype($fileSize), $fileSize));
635 if (!is_int($fileSize)) {
637 throw new UnexpectedValueException(sprintf('fileSize[]=%s is unexpected', gettype($fileSize)));
640 // Is more than 0 returned?
641 $isInitialized = ($fileSize > 0);
644 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isInitialized=%d - EXIT!', intval($isInitialized)));
645 return $isInitialized;
649 * Creates the assigned file
652 * @throws BadMethodCallException If this file's header is already initialized
654 public function createFileHeader () {
655 // The file's header should not be initialized here
656 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
657 if ($this->isFileHeaderInitialized()) {
659 //* DEBUG-DIE: */ die(sprintf('[%s:%d]: this=%s', __METHOD__, __LINE__, print_r($this, TRUE)));
660 throw new BadMethodCallException('File header is already initialized but method called');
663 // Simple flush file header which will create it.
664 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->flushFileHeader() ...');
665 $this->flushFileHeader();
667 // Rewind seek position (to beginning of file) and update/flush file header
668 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->rewindUpdateSeekPosition() ...');
669 $this->rewindUpdateSeekPosition();
672 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
676 * Pre-allocates file (if enabled) with some space for later faster write access.
678 * @param $type Type of the file
680 * @throws InvalidArgumentException If a parameter is empty
682 public function preAllocateFile (string $type) {
684 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: type=%s - CALLED!', $type));
687 throw new InvalidArgumentException('Parameter "type" is empty');
688 } elseif (FrameworkBootstrap::getConfigurationInstance()->getConfigEntry($type . '_pre_allocate_enabled') != 'Y') {
689 // Don't continue here.
690 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Not pre-allocating file.'));
695 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Pre-allocating file ...');
697 // Calculate minimum length for one entry and get file size
698 $minimumBlockLength = $this->getBlockInstance()->calculateMinimumBlockLength();
699 $fileSize = $this->getFileSize();
701 // Calulcate seek position
702 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: minimumBlockLength=%d,fileSize=%d', $minimumBlockLength, $fileSize));
703 $seekPosition = $minimumBlockLength * FrameworkBootstrap::getConfigurationInstance()->getConfigEntry($type . '_pre_allocate_count') + $fileSize ;
705 // Now simply write a NUL there. This will pre-allocate the file.
706 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->writeAtPosition(%d,NUL) ...', $seekPosition));
707 $this->writeAtPosition($seekPosition, chr(0));
709 // Is the seek position zero?
710 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: fileSize=%d', $fileSize));
711 if ($fileSize == 0) {
712 // Mark file as gaps-only
713 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->markGapsOnly(%s,%d) ...', $type, $minimumBlockLength));
714 $this->markFileGapsOnly($type, $minimumBlockLength);
716 // Analyze file structure
717 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->analyzeFileStructure() ...');
718 $this->analyzeFileStructure();
721 // Rewind seek position
722 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->rewind() ...');
726 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
730 * Determines seek position
732 * @return $seekPosition Current seek position
734 public function determineSeekPosition () {
735 // Call pointer instance
736 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
737 $seekPosition = $this->getPointerInstance()->determineSeekPosition();
740 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d - EXIT!', $seekPosition));
741 return $seekPosition;
745 * Seek to given offset (default) or other possibilities as fseek() gives.
747 * @param $offset Offset to seek to (or used as "base" for other seeks)
748 * @param $whence Added to offset (default: only use offset to seek to)
749 * @return $status Status of file seek: 0 = success, -1 = failed
750 * @throws InvalidArgumentException If a parameter is not valid
752 public function seek (int $offset, int $whence = SEEK_SET) {
753 // Validate parameter
754 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: offset=%d,whence=%d - CALLED!', $offset, $whence));
756 // No offset is smaller than zero
757 throw new InvalidArgumentException(sprintf('offset=%d is not valid', $offset));
760 // Call pointer instance
761 $status = $this->getPointerInstance()->seek($offset, $whence);
764 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: status[%s]=%d - EXIT!', gettype($status), $status));
769 * Reads given amount of bytes from file.
771 * @param $bytes Amount of bytes to read
772 * @return $data Data read from file
773 * @throws InvalidArgumentException If a parameter is not valid
775 public function read (int $bytes = 0) {
776 // Validate parameter
777 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: bytes=%d - CALLED!', $bytes));
780 throw new InvalidArgumentException(sprintf('bytes=%d is not valid', $bytes));
783 // Call pointer instance
784 $data = $this->getPointerInstance()->read($bytes);
787 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: data[%s]=%s - EXIT!', gettype($data), $data));
792 * Rewinds to the beginning of the file
796 public function rewind () {
797 // Call pointer instance
798 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
799 $this->getPointerInstance()->rewind();
802 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
806 * Analyzes entries in index file. This will count all found (and valid)
807 * entries, mark invalid as damaged and count gaps ("fragmentation"). If
808 * only gaps are found, the file is considered as "virgin" (no entries).
811 * @throws BadMethodCallException If this method is called but file is not initialized
813 public function analyzeFileStructure () {
814 // Make sure the file is initialized
815 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
816 if (!$this->isFileInitialized()) {
818 throw new BadMethodCallException('Method called but file is not initialized.');
821 // Init counters and gaps array
822 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->initCounterGapsArrays() ...');
823 $this->initCountersGapsArray();
825 // Output message (as this may take some time)
826 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('Analyzing file structure ... (this may take some time)'));
828 // First rewind to the begining
829 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->rewind() ...');
832 // Then try to load all entries
833 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Looping through file');
834 while ($this->isValid()) {
836 $current = $this->getCurrentBlock();
839 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->readNextBlock() ...');
840 $this->readNextBlock();
843 * If the block is empty, maybe the whole file is? This could mean
844 * that the file has been pre-allocated.
846 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: current(%d)[]=%s', strlen($current), gettype($current)));
847 if (empty($current)) {
848 // Then skip this part
849 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: current[]=%s is empty - CONTINUE!', gettype($current)));
853 // Handle current record
854 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: current(%d)[]=%s', strlen($current), gettype($current)));
857 // If the last read block is empty, check gaps
858 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: current()=%d', strlen($current)));
859 if (empty($current)) {
861 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Found a total of %d gaps.', count($this->gaps)));
863 // Check gaps, if the whole file is empty.
864 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->isFileGapsOnly() ...');
865 if ($this->isFileGapsOnly()) {
866 // Only gaps, so don't continue here.
867 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: File is gaps-only - EXIT!');
872 * The above call has calculated a total size of all gaps. If the
873 * percentage of gaps passes a "soft" limit and last
874 * defragmentation is to far in the past, or if a "hard" limit has
875 * reached, run defragmentation.
877 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->isDefragmentationNeeded() ...');
878 if ($this->isDefragmentationNeeded()) {
879 // Run "defragmentation"
880 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Calling this->doRunDefragmentation() ...');
881 $this->doRunDefragmentation();
886 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
890 * Reads next "block" of bytes into $currentBlock field
893 * @throws BadMethodCallException If this method was called without prior valid() call
895 public function readNextBlock () {
896 // Is there nothing to read?
897 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
898 if (!$this->isValid()) {
900 throw new BadMethodCallException('next() invoked but no valid current block (EOF?)');
903 // First calculate minimum block length
904 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: this->seekPosition=%d', $this->determineSeekPosition()));
905 $length = $this->getBlockInstance()->calculateMinimumBlockLength();
906 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: length=%d', $length));
908 // Read possibly back-buffered bytes from previous call of next().
909 $data = $this->getBackBuffer();
912 * Read until a entry/block separator has been found. The next read
913 * "block" may not fit, so this loop will continue until the EOB or EOF
914 * has been reached whatever comes first.
916 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: data()=%d', strlen($data)));
917 while ((!$this->isEndOfFileReached()) && (empty($data) || !self::isBlockSeparatorFound($data))) {
918 // Then read the next possible block
919 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->read(%d) ...', $length));
920 $block = $this->read($length);
923 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: block()=%d,length=%d', strlen($block), $length));
924 if (strlen(trim($block, chr(0))) == 0) {
925 // Mark this block as empty
926 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: Calling this->markCurrentBlockAsEmpty(%d) ...', strlen($block)));
927 $this->markCurrentBlockAsEmpty(strlen($block));
930 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Empty block found ... - BREAK!');
934 // At this block then
938 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: data(%d)=%s', strlen($data), $data));
942 * Init back-buffer which is the data that has been found beyond the
945 $this->initBackBuffer();
948 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: data(%d)=%s', strlen($data), $data));
950 // Yes, maybe whole file was ...
951 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: Maybe empty file found - EXIT!');
956 $dataArray = explode(chr(self::SEPARATOR_ENTRIES), $data);
958 // Left part is the actual block, right one the back-buffer data, if found
959 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: dataArray()=%d', count($dataArray)));
960 //* PRINTR-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: dataArray=%s', print_r($dataArray, true)));
961 $this->setCurrentBlock($dataArray[0]);
963 // Is back buffere data found?
964 if (isset($dataArray[1])) {
966 $this->setBackBuffer($dataArray[1]);
970 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
974 * Checks wether the current entry is valid (not at the end of the file).
975 * This method will return true if an emptied (nulled) entry has been found.
977 * @return $isValid Whether the next entry is valid
978 * @throws UnexpectedValueException If some value is not expected
980 public function isValid () {
981 // First calculate minimum block length
982 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
983 $length = $this->getBlockInstance()->calculateMinimumBlockLength();
985 // Short be more than zero!
986 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: length=%d', $length));
989 throw new UnexpectedValueException(sprintf('length=%d is not expected', $length));
992 // Get current seek position
993 $seekPosition = $this->determineSeekPosition();
995 // Then try to read it
996 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d', $seekPosition));
997 $data = $this->read($length);
999 // If some bytes could be read, all is fine
1000 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: data[%s]()=%d', gettype($data), strlen($data)));
1001 $isValid = ((is_string($data)) && (strlen($data) > 0));
1004 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isValid=%d', intval($isValid)));
1005 $headerSize = $this->getHeaderSize();
1007 // Is the seek position at or beyond the header?
1008 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d,headerSize=%d', $seekPosition, $headerSize));
1009 if ($seekPosition >= $headerSize) {
1010 // Seek back to old position
1011 $isValid = ($isValid && $this->seek($seekPosition) === 0);
1013 // Seek directly behind the header
1014 $isValid = ($isValid && $this->seek($headerSize) === 0);
1018 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: isValid=%d - EXIT!', intval($isValid)));
1023 * Reads the file header
1027 public function readFileHeader () {
1028 // Call block instance
1029 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
1030 $this->getBlockInstance()->readFileHeader();
1033 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
1037 * Flushes the file header
1041 public function flushFileHeader () {
1042 // Call block instance
1043 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: CALLED!');
1044 $this->getBlockInstance()->flushFileHeader();
1047 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-BINARY-FILE: EXIT!');
1051 * Searches for next suitable gap the given length of data can fit in
1052 * including padding bytes.
1054 * @param $length Length of raw data
1055 * @return $seekPosition Found next gap's seek position
1056 * @throws InvalidArgumentException If the parameter is not valid
1058 public function searchNextGap (int $length) {
1059 // If the file is only gaps, no need to seek
1060 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: length=%d - CALLED!', $length));
1063 throw new InvalidArgumentException(sprintf('length=%d is not valid', $length));
1064 } elseif ($this->isFileGapsOnly()) {
1066 * The first empty block is the 2nd one right after the header, so
1067 * one byte gap to the header.
1069 $seekPosition = ($this->getHeaderSize() + 2);
1072 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-BINARY-FILE: seekPosition=%d - EXIT!', $seekPosition));
1073 return $seekPosition;
1077 $this->partialStub('length=' . $length);