5 * @author Roland Haeder <webmaster@ship-simu.org>
7 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2012 Core Developer Team
8 * @license GNU GPL 3.0 or any newer version
9 * @link http://www.ship-simu.org
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see <http://www.gnu.org/licenses/>.
24 class BaseFile extends BaseFrameworkSystem {
26 * Separator for header data
28 const SEPARATOR_HEADER_DATA = 0x01;
31 * Separator header->entries
33 const SEPARATOR_HEADER_ENTRIES = 0x02;
36 * Separator hash->name
38 const SEPARATOR_HASH_NAME = 0x03;
41 * Separator entry->entry
43 const SEPARATOR_ENTRIES = 0x04;
46 * Separator type->position
48 const SEPARATOR_TYPE_POSITION = 0x05;
53 const LENGTH_COUNT = 20;
58 const LENGTH_POSITION = 20;
63 const LENGTH_NAME = 10;
66 * Maximum length of entry type
68 const LENGTH_TYPE = 20;
70 //***** Array elements for 'gaps' array *****
75 const GAPS_INDEX_START = 'start';
80 const GAPS_INDEX_END = 'end';
83 * Length of output from hash()
85 private static $hashLength = NULL;
88 * Counter for total entries
90 private $totalEntries = 0;
93 * Current seek position
95 private $seekPosition = 0;
100 private $headerSize = 0;
105 private $header = array();
108 * Seek positions for gaps ("fragmentation")
110 private $gaps = array();
113 * Seek positions for damaged entries (e.g. mismatching hash sum, ...)
115 private $damagedEntries = array();
118 * The current file we are working in
120 private $fileName = '';
125 private $backBuffer = '';
128 * Currently loaded block (will be returned by current())
130 private $currentBlock = '';
133 * Protected constructor
135 * @param $className Name of the class
138 protected function __construct ($className) {
139 // Call parent constructor
140 parent::__construct($className);
142 // Init counters and gaps array
143 $this->initCountersGapsArray();
147 * Destructor for cleaning purposes, etc
151 public final function __destruct() {
152 // Try to close a file
155 // Call the parent destructor
156 parent::__destruct();
160 * Initializes counter for valid entries, arrays for damaged entries and
161 * an array for gap seek positions. If you call this method on your own,
162 * please re-analyze the file structure. So you are better to call
163 * analyzeFile() instead of this method.
167 public function initCountersGapsArray () {
168 // Init counter and seek position
169 $this->setCounter(0);
170 $this->setSeekPosition(0);
173 $this->gaps = array();
174 $this->damagedEntries = array();
178 * Getter for total entries
180 * @return $totalEntries Total entries in this file
182 protected final function getCounter () {
184 return $this->totalEntries;
188 * Setter for total entries
190 * @param $totalEntries Total entries in this file
193 protected final function setCounter ($counter) {
195 $this->totalEntries = $counter;
203 protected final function incrementCounter () {
205 $this->totalEntries++;
209 * Getter for header size
211 * @return $totalEntries Size of file header
213 public final function getHeaderSize () {
215 return $this->headerSize;
219 * Setter for header size
221 * @param $headerSize Size of file header
224 public final function setHeaderSize ($headerSize) {
226 $this->headerSize = $headerSize;
230 * Getter for header array
232 * @return $totalEntries Size of file header
234 protected final function getHeade () {
236 return $this->header;
242 * @param $header Array for a file header
245 protected final function setHeader (array $header) {
247 $this->header = $header;
251 * Getter for seek position
253 * @return $seekPosition Current seek position (stored here in object)
255 protected final function getSeekPosition () {
257 return $this->seekPosition;
261 * Setter for seek position
263 * @param $seekPosition Current seek position (stored here in object)
266 protected final function setSeekPosition ($seekPosition) {
268 $this->seekPosition = $seekPosition;
272 * Updates seekPosition attribute from file to avoid to much access on file.
276 protected function updateSeekPosition () {
277 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
279 // Get key (= seek position)
280 $seekPosition = $this->getIteratorInstance()->key();
281 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Setting seekPosition=%s', __METHOD__, __LINE__, $seekPosition));
284 $this->setSeekPosition($seekPosition);
286 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
290 * Seeks to beginning of file, updates seek position in this object and
291 * flushes the header.
295 protected function rewindUpdateSeekPosition () {
296 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
298 // flushFileHeader must be callable
299 assert(is_callable(array($this, 'flushFileHeader')));
301 // Seek to beginning of file
302 $this->getIteratorInstance()->rewind();
304 // And update seek position ...
305 $this->updateSeekPosition();
307 // ... to write it back into the file
308 $this->flushFileHeader();
310 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!!', __METHOD__, __LINE__));
314 * Seeks to old position
318 protected function seekToOldPosition () {
319 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
321 // Seek to currently ("old") saved position
322 $this->getIteratorInstance()->seek($this->getSeekPosition());
324 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!!', __METHOD__, __LINE__));
328 * Checks whether the block separator has been found
330 * @param $str String to look in
331 * @return $isFound Whether the block separator has been found
333 public static function isBlockSeparatorFound ($str) {
335 $isFound = (strpos($str, chr(self::SEPARATOR_ENTRIES)) !== FALSE);
342 * Getter for the file pointer
344 * @return $filePointer The file pointer which shall be a valid file resource
345 * @throws UnsupportedOperationException If this method is called
347 public final function getPointer () {
348 throw new UnsupportedOperationException(array($this, __FUNCTION__), self::EXCEPTION_UNSPPORTED_OPERATION);
352 * Setter for file name
354 * @param $fileName The new file name
357 protected final function setFileName ($fileName) {
358 $fileName = (string) $fileName;
359 $this->fileName = $fileName;
363 * Getter for file name
365 * @return $fileName The current file name
367 public final function getFileName () {
368 return $this->fileName;
372 * Initializes the back-buffer by setting it to an empty string.
376 private function initBackBuffer () {
377 // Simply call the setter
378 $this->setBackBuffer('');
382 * Setter for backBuffer field
384 * @param $backBuffer Characters to "store" in back-buffer
387 private function setBackBuffer ($backBuffer) {
388 // Cast to string (so no arrays or objects)
389 $backBuffer = (string) $backBuffer;
392 $this->backBuffer = $backBuffer;
396 * Getter for backBuffer field
398 * @return $backBuffer Characters "stored" in back-buffer
400 private function getBackBuffer () {
401 return $this->backBuffer;
405 * Setter for currentBlock field
407 * @param $currentBlock Characters to set a currently loaded block
410 private function setCurrentBlock ($currentBlock) {
411 // Cast to string (so no arrays or objects)
412 $currentBlock = (string) $currentBlock;
415 $this->currentBlock = $currentBlock;
419 * Gets currently read data
421 * @return $current Currently read data
423 public function getCurrentBlock () {
425 return $this->currentBlock;
429 * Initializes this file class
431 * @param $fileName Name of this abstract file
434 protected function initFile ($fileName) {
435 // Get a file i/o pointer instance
436 $pointerInstance = ObjectFactory::createObjectByConfiguredName('file_raw_input_output_class', array($fileName));
438 // ... and set it here
439 $this->setPointerInstance($pointerInstance);
443 * Writes data at given position
445 * @param $seekPosition Seek position
446 * @param $data Data to be written
447 * @param $flushHeader Whether to flush the header (default: flush)
450 protected function writeData ($seekPosition, $data, $flushHeader = TRUE) {
451 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] seekPosition=%s,data()=%s - CALLED!', __METHOD__, __LINE__, $seekPosition, strlen($data)));
453 // Write data at given position
454 $this->getPointerInstance()->writeAtPosition($seekPosition, $data);
456 // Update seek position
457 $this->updateSeekPosition();
460 if ($flushHeader === TRUE) {
462 $this->flushFileHeader();
464 // Seek to old position
465 $this->seekToOldPosition();
468 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
472 * Marks the currently loaded block as empty (with length of the block)
474 * @param $length Length of the block
477 protected function markCurrentBlockAsEmpty ($length) {
478 // Get current seek position
479 $currentPosition = $this->key();
481 // Now add it as gap entry
482 array_push($this->gaps, array(
483 self::GAPS_INDEX_START => ($currentPosition - $length),
484 self::GAPS_INDEX_END => $currentPosition,
489 * Checks whether the file header is initialized
491 * @return $isInitialized Whether the file header is initialized
493 public function isFileHeaderInitialized () {
494 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
496 // Default is not initialized
497 $isInitialized = FALSE;
499 // Is the file initialized?
500 if ($this->isFileInitialized()) {
501 // Some bytes has been written, so rewind to start of it.
502 $rewindStatus = $this->rewind();
503 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] rewindStatus=%s', __METHOD__, __LINE__, $rewindStatus));
505 // Is the rewind() call successfull?
506 if ($rewindStatus != 1) {
507 // Something bad happened
508 self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Could not rewind().', __METHOD__, __LINE__));
512 $this->readFileHeader();
514 // The above method does already check the header
515 $isInitialized = TRUE;
519 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] isInitialized=%d - EXIT!', __METHOD__, __LINE__, intval($isInitialized)));
520 return $isInitialized;
524 * Checks whether the assigned file has been initialized
526 * @return $isInitialized Whether the file's size is zero
528 public function isFileInitialized () {
529 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
531 // Get it from iterator which holds the pointer instance. If FALSE is returned
532 $fileSize = $this->size();
533 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] fileSize=%s', __METHOD__, __LINE__, $fileSize));
536 * The returned file size should not be FALSE or NULL as this means
537 * that the pointer class does not work correctly.
539 assert(is_int($fileSize));
541 // Is more than 0 returned?
542 $isInitialized = ($fileSize > 0);
545 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] isInitialized=%d - EXIT!', __METHOD__, __LINE__, intval($isInitialized)));
546 return $isInitialized;
550 * Creates the assigned file
554 public function createFileHeader () {
555 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
557 // The file's header should not be initialized here
558 assert(!$this->isFileHeaderInitialized());
560 // Simple flush file header which will create it.
561 $this->flushFileHeader();
563 // Rewind seek position (to beginning of file) and update/flush file header
564 $this->rewindUpdateSeekPosition();
566 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!!', __METHOD__, __LINE__));
570 * Pre-allocates file (if enabled) with some space for later faster write access.
572 * @param $type Type of the file
575 public function preAllocateFile ($type) {
576 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
579 if ($this->getConfigInstance()->getConfigEntry($type . '_pre_allocate_enabled') != 'Y') {
581 self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Not pre-allocating file.', __METHOD__, __LINE__));
583 // Don't continue here.
588 self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Pre-allocating file ...', __METHOD__, __LINE__));
590 // Calculate minimum length for one entry
591 $minLengthEntry = $this->getBlockInstance()->calculateMinimumBlockLength();
592 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] minLengthEntry=%s', __METHOD__, __LINE__, $minLengthEntry));
594 // Calulcate seek position
595 $seekPosition = $minLengthEntry * $this->getConfigInstance()->getConfigEntry($type . '_pre_allocate_count');
596 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] seekPosition=%s', __METHOD__, __LINE__, $seekPosition));
598 // Now simply write a NUL there. This will pre-allocate the file.
599 $this->writeData($seekPosition, chr(0));
601 // Rewind seek position (to beginning of file) and update/flush file header
602 $this->rewindUpdateSeekPosition();
604 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
608 * Close a file source and set it's instance to null and the file name
614 public function closeFile () {
615 $this->partialStub('Unfinished method.');
618 $this->setFileName('');
622 * Determines seek position
624 * @return $seekPosition Current seek position
626 public function determineSeekPosition () {
627 // Call pointer instance
628 return $this->getPointerInstance()->determineSeekPosition();
632 * Seek to given offset (default) or other possibilities as fseek() gives.
634 * @param $offset Offset to seek to (or used as "base" for other seeks)
635 * @param $whence Added to offset (default: only use offset to seek to)
636 * @return $status Status of file seek: 0 = success, -1 = failed
638 public function seek ($offset, $whence = SEEK_SET) {
639 // Call pointer instance
640 return $this->getPointerInstance()->seek($offset, $whence);
646 * @return $size Size (in bytes) of file
647 * @todo Handle seekStatus
649 public function size () {
650 // Call pointer instance
651 return $this->getPointerInstance()->size();
655 * Read data a file pointer
657 * @return mixed The result of fread()
658 * @throws NullPointerException If the file pointer instance
659 * is not set by setPointer()
660 * @throws InvalidResourceException If there is being set
662 public function readFromFile () {
663 // Call pointer instance
664 return $this->getPointerInstance()->readFromFile();
668 * Reads given amount of bytes from file.
670 * @param $bytes Amount of bytes to read
671 * @return $data Data read from file
673 public function read ($bytes) {
674 // Call pointer instance
675 return $this->getPointerInstance()->read($bytes);
679 * Write data to a file pointer
681 * @param $dataStream The data stream we shall write to the file
682 * @return mixed Number of writes bytes or FALSE on error
683 * @throws NullPointerException If the file pointer instance
684 * is not set by setPointer()
685 * @throws InvalidResourceException If there is being set
686 * an invalid file resource
688 public function writeToFile ($dataStream) {
689 // Call pointer instance
690 return $this->getPointerInstance()->writeToFile($dataStream);
694 * Rewinds to the beginning of the file
696 * @return $status Status of this operation
698 public function rewind () {
699 // Call pointer instance
700 return $this->getPointerInstance()->rewind();
704 * Determines whether the EOF has been reached
706 * @return $isEndOfFileReached Whether the EOF has been reached
708 public final function isEndOfFileReached () {
709 // Call pointer instance
710 return $this->getPointerInstance()->isEndOfFileReached();
714 * Analyzes entries in index file. This will count all found (and valid)
715 * entries, mark invalid as damaged and count gaps ("fragmentation"). If
716 * only gaps are found, the file is considered as "virgin" (no entries).
720 public function analyzeFile () {
721 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
723 // Make sure the file is initialized
724 assert($this->isFileInitialized());
726 // Init counters and gaps array
727 $this->initCountersGapsArray();
729 // Output message (as this may take some time)
730 self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Analyzing file structure ... (this may take some time)', __METHOD__, __LINE__));
732 // First rewind to the begining
735 // Then try to load all entries
736 while ($this->valid()) {
741 $current = $this->getCurrentBlock();
744 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] current()=%s', __METHOD__, __LINE__, strlen($current)));
747 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
751 * Advances to next "block" of bytes
755 public function next () {
756 // Is there nothing to read?
757 if (!$this->valid()) {
762 // Make sure the block instance is set
763 assert($this->getBlockInstance() instanceof CalculatableBlock);
765 // First calculate minimum block length
766 $length = $this->getBlockInstance()->calculateMinimumBlockLength();
768 // Short be more than zero!
771 // Read possibly back-buffered bytes from previous call of next().
772 $data = $this->getBackBuffer();
775 * Read until a entry/block separator has been found. The next read
776 * "block" may not fit, so this loop will continue until the EOB or EOF
777 * has been reached whatever comes first.
779 while ((!$this->isEndOfFileReached()) && (!self::isBlockSeparatorFound($data))) {
780 // Then read the next possible block
781 $block = $this->read($length);
784 if (strlen(trim($block)) == 0) {
785 // Mark this block as empty
786 $this->markCurrentBlockAsEmpty($length);
788 // Skip to next block
792 // At this block then
796 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d] data()=%s', __FUNCTION__, __LINE__, strlen($data)));
800 if ($this->isEndOfFileReached()) {
801 // Set whole data as current read block
802 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('Calling setCurrentBlock(' . strlen($data) . ') ...');
803 $this->setCurrentBlock($data);
805 // Then abort here silently
806 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('EOF reached.');
811 * Init back-buffer which is the data that has been found beyond the
814 $this->initBackBuffer();
817 $dataArray = explode(chr(self::SEPARATOR_ENTRIES), $data);
819 // This array must contain two elements
820 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('dataArray=' . print_r($dataArray, TRUE));
821 assert(count($dataArray) == 2);
823 // Left part is the actual block, right one the back-buffer data
824 $this->setCurrentBlock($dataArray[0]);
825 $this->setBackBuffer($dataArray[1]);
829 * Checks wether the current entry is valid (not at the end of the file).
830 * This method will return TRUE if an emptied (nulled) entry has been found.
832 * @return $isValid Whether the next entry is valid
834 public function valid () {
835 // Make sure the block instance is set
836 assert($this->getBlockInstance() instanceof Block);
838 // First calculate minimum block length
839 $length = $this->getBlockInstance()->calculateMinimumBlockLength();
841 // Short be more than zero!
844 // Get current seek position
845 $seekPosition = $this->key();
847 // Then try to read it
848 $data = $this->read($length);
850 // If some bytes could be read, all is fine
851 $isValid = ((is_string($data)) && (strlen($data) > 0));
854 $headerSize = $this->getBlockInstance()->getHeaderSize();
856 // Is the seek position at or beyond the header?
857 if ($seekPosition >= $headerSize) {
858 // Seek back to old position
859 $this->seek($seekPosition);
861 // Seek directly behind the header
862 $this->seek($headerSize);
870 * Gets current seek position ("key").
872 * @return $key Current key in iteration
874 public function key () {
875 // Call pointer instance
876 return $this->getPointerInstance()->determineSeekPosition();
880 * Reads the file header
884 public function readFileHeader () {
885 // Make sure the block instance is set
886 assert($this->getBlockInstance() instanceof Block);
888 // Call block instance
889 $this->getBlockInstance()->readFileHeader();
893 * Flushes the file header
897 public function flushFileHeader () {
898 // Make sure the block instance is set
899 assert($this->getBlockInstance() instanceof Block);
901 // Call block instance
902 $this->getBlockInstance()->flushFileHeader();