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 * The current file we are working in
28 private $fileName = '';
33 private $backBuffer = '';
36 * Currently loaded block (will be returned by current())
38 private $currentBlock = '';
41 * Protected constructor
43 * @param $className Name of the class
46 protected function __construct ($className) {
47 // Call parent constructor
48 parent::__construct($className);
50 // Init counters and gaps array
51 $this->initCountersGapsArray();
55 * Destructor for cleaning purposes, etc
59 public final function __destruct() {
60 // Try to close a file
63 // Call the parent destructor
68 * Getter for the file pointer
70 * @return $filePointer The file pointer which shall be a valid file resource
71 * @throws UnsupportedOperationException If this method is called
73 public final function getPointer () {
74 throw new UnsupportedOperationException(array($this, __FUNCTION__), self::EXCEPTION_UNSPPORTED_OPERATION);
78 * Setter for file name
80 * @param $fileName The new file name
83 protected final function setFileName ($fileName) {
84 $fileName = (string) $fileName;
85 $this->fileName = $fileName;
89 * Getter for file name
91 * @return $fileName The current file name
93 public final function getFileName () {
94 return $this->fileName;
98 * Initializes the back-buffer by setting it to an empty string.
102 private function initBackBuffer () {
103 // Simply call the setter
104 $this->setBackBuffer('');
108 * Setter for backBuffer field
110 * @param $backBuffer Characters to "store" in back-buffer
113 private function setBackBuffer ($backBuffer) {
114 // Cast to string (so no arrays or objects)
115 $backBuffer = (string) $backBuffer;
118 $this->backBuffer = $backBuffer;
122 * Getter for backBuffer field
124 * @return $backBuffer Characters "stored" in back-buffer
126 private function getBackBuffer () {
127 return $this->backBuffer;
131 * Setter for currentBlock field
133 * @param $currentBlock Characters to set a currently loaded block
136 private function setCurrentBlock ($currentBlock) {
137 // Cast to string (so no arrays or objects)
138 $currentBlock = (string) $currentBlock;
141 $this->currentBlock = $currentBlock;
145 * Gets currently read data
147 * @return $current Currently read data
149 public function getCurrentBlock () {
151 return $this->currentBlock;
155 * Initializes this file class
157 * @param $fileName Name of this abstract file
160 protected function initFile ($fileName) {
161 // Get a file i/o pointer instance
162 $pointerInstance = ObjectFactory::createObjectByConfiguredName('file_raw_input_output_class', array($fileName));
164 // ... and set it here
165 $this->setPointerInstance($pointerInstance);
169 * Writes data at given position
171 * @param $seekPosition Seek position
172 * @param $data Data to be written
173 * @param $flushHeader Whether to flush the header (default: flush)
176 protected function writeData ($seekPosition, $data, $flushHeader = TRUE) {
177 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] seekPosition=%s,data()=%s - CALLED!', __METHOD__, __LINE__, $seekPosition, strlen($data)));
179 // Write data at given position
180 $this->getPointerInstance()->writeAtPosition($seekPosition, $data);
182 // Update seek position
183 $this->updateSeekPosition();
186 if ($flushHeader === TRUE) {
188 $this->flushFileHeader();
190 // Seek to old position
191 $this->seekToOldPosition();
194 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
198 * Checks whether the file header is initialized
200 * @return $isInitialized Whether the file header is initialized
202 public function isFileHeaderInitialized () {
203 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
205 // Default is not initialized
206 $isInitialized = FALSE;
208 // Is the file initialized?
209 if ($this->isFileInitialized()) {
210 // Some bytes has been written, so rewind to start of it.
211 $rewindStatus = $this->rewind();
212 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] rewindStatus=%s', __METHOD__, __LINE__, $rewindStatus));
214 // Is the rewind() call successfull?
215 if ($rewindStatus != 1) {
216 // Something bad happened
217 self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Could not rewind().', __METHOD__, __LINE__));
221 $this->readFileHeader();
223 // The above method does already check the header
224 $isInitialized = TRUE;
228 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] isInitialized=%d - EXIT!', __METHOD__, __LINE__, intval($isInitialized)));
229 return $isInitialized;
233 * Checks whether the assigned file has been initialized
235 * @return $isInitialized Whether the file's size is zero
237 public function isFileInitialized () {
238 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
240 // Get it from iterator which holds the pointer instance. If FALSE is returned
241 $fileSize = $this->size();
242 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] fileSize=%s', __METHOD__, __LINE__, $fileSize));
245 * The returned file size should not be FALSE or NULL as this means
246 * that the pointer class does not work correctly.
248 assert(is_int($fileSize));
250 // Is more than 0 returned?
251 $isInitialized = ($fileSize > 0);
254 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] isInitialized=%d - EXIT!', __METHOD__, __LINE__, intval($isInitialized)));
255 return $isInitialized;
259 * Creates the assigned file
263 public function createFileHeader () {
264 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
266 // The file's header should not be initialized here
267 assert(!$this->isFileHeaderInitialized());
269 // Simple flush file header which will create it.
270 $this->flushFileHeader();
272 // Rewind seek position (to beginning of file) and update/flush file header
273 $this->rewineUpdateSeekPosition();
275 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!!', __METHOD__, __LINE__));
279 * Pre-allocates file (if enabled) with some space for later faster write access.
281 * @param $type Type of the file
284 public function preAllocateFile ($type) {
285 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
288 if ($this->getConfigInstance()->getConfigEntry($type . '_pre_allocate_enabled') != 'Y') {
290 self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Not pre-allocating file.', __METHOD__, __LINE__));
292 // Don't continue here.
297 self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Pre-allocating file ...', __METHOD__, __LINE__));
299 // Calculate minimum length for one entry
300 $minLengthEntry = $this->getBlockInstance()->calculateMinimumBlockLength();
301 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] minLengthEntry=%s', __METHOD__, __LINE__, $minLengthEntry));
303 // Calulcate seek position
304 $seekPosition = $minLengthEntry * $this->getConfigInstance()->getConfigEntry($type . '_pre_allocate_count');
305 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] seekPosition=%s', __METHOD__, __LINE__, $seekPosition));
307 // Now simply write a NUL there. This will pre-allocate the file.
308 $this->writeData($seekPosition, chr(0));
310 // Rewind seek position (to beginning of file) and update/flush file header
311 $this->rewineUpdateSeekPosition();
313 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
317 * Close a file source and set it's instance to null and the file name
323 public function closeFile () {
324 $this->partialStub('Unfinished method.');
327 $this->setFileName('');
331 * Determines seek position
333 * @return $seekPosition Current seek position
335 public function determineSeekPosition () {
336 // Call pointer instance
337 return $this->getPointerInstance()->determineSeekPosition();
341 * Seek to given offset (default) or other possibilities as fseek() gives.
343 * @param $offset Offset to seek to (or used as "base" for other seeks)
344 * @param $whence Added to offset (default: only use offset to seek to)
345 * @return $status Status of file seek: 0 = success, -1 = failed
347 public function seek ($offset, $whence = SEEK_SET) {
348 // Call pointer instance
349 return $this->getPointerInstance()->seek($offset, $whence);
355 * @return $size Size (in bytes) of file
356 * @todo Handle seekStatus
358 public function size () {
359 // Call pointer instance
360 return $this->getPointerInstance()->size();
364 * Read data a file pointer
366 * @return mixed The result of fread()
367 * @throws NullPointerException If the file pointer instance
368 * is not set by setPointer()
369 * @throws InvalidResourceException If there is being set
371 public function readFromFile () {
372 // Call pointer instance
373 return $this->getPointerInstance()->readFromFile();
377 * Reads given amount of bytes from file.
379 * @param $bytes Amount of bytes to read
380 * @return $data Data read from file
382 public function read ($bytes) {
383 // Call pointer instance
384 return $this->getPointerInstance()->read($bytes);
388 * Write data to a file pointer
390 * @param $dataStream The data stream we shall write to the file
391 * @return mixed Number of writes bytes or FALSE on error
392 * @throws NullPointerException If the file pointer instance
393 * is not set by setPointer()
394 * @throws InvalidResourceException If there is being set
395 * an invalid file resource
397 public function writeToFile ($dataStream) {
398 // Call pointer instance
399 return $this->getPointerInstance()->writeToFile($dataStream);
403 * Rewinds to the beginning of the file
405 * @return $status Status of this operation
407 public function rewind () {
408 // Call pointer instance
409 return $this->getPointerInstance()->rewind();
413 * Determines whether the EOF has been reached
415 * @return $isEndOfFileReached Whether the EOF has been reached
417 public final function isEndOfFileReached () {
418 // Call pointer instance
419 return $this->getPointerInstance()->isEndOfFileReached();
423 * Analyzes entries in index file. This will count all found (and valid)
424 * entries, mark invalid as damaged and count gaps ("fragmentation"). If
425 * only gaps are found, the file is considered as "virgin" (no entries).
429 public function analyzeFile () {
430 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
432 // Make sure the file is initialized
433 assert($this->isFileInitialized());
435 // Init counters and gaps array
436 $this->initCountersGapsArray();
438 // Output message (as this may take some time)
439 self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Analyzing file structure ... (this may take some time)', __METHOD__, __LINE__));
441 // First rewind to the begining
444 // Then try to load all entries
445 while ($this->valid()) {
450 $current = $this->getCurrentBlock();
453 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] current()=%s', __METHOD__, __LINE__, strlen($current)));
456 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
460 * Advances to next "block" of bytes
463 * @todo This method will load large but empty files in a whole
465 public function next () {
466 // Is there nothing to read?
467 if (!$this->valid()) {
472 // Make sure the block instance is set
473 assert($this->getBlockInstance() instanceof Block);
475 // First calculate minimum block length
476 $length = $this->getBlockInstance()->calculateMinimumBlockLength();
478 // Short be more than zero!
481 // Wait until a entry/block separator has been found
482 $data = $this->getBackBuffer();
483 while ((!$this->isEndOfFileReached()) && (!self::isBlockSeparatorFound($data))) {
484 // Then read the block
485 $data .= $this->read($length);
486 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('data()=' . strlen($data));
490 if ($this->isEndOfFileReached()) {
491 // Set whole data as current block
492 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('Calling setCurrentBlock(' . strlen($data) . ') ...');
493 $this->setCurrentBlock($data);
495 // Then abort here silently
496 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('EOF reached.');
501 * Init back-buffer which is the data that has been found beyond the
504 $this->initBackBuffer();
507 $dataArray = explode(chr(self::SEPARATOR_ENTRIES), $data);
509 // This array must contain two elements
510 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('dataArray=' . print_r($dataArray, TRUE));
511 assert(count($dataArray) == 2);
513 // Left part is the actual block, right one the back-buffer data
514 $this->setCurrentBlock($dataArray[0]);
515 $this->setBackBuffer($dataArray[1]);
519 * Checks wether the current entry is valid (not at the end of the file).
520 * This method will return TRUE if an emptied (nulled) entry has been found.
522 * @return $isValid Whether the next entry is valid
524 public function valid () {
525 // Make sure the block instance is set
526 assert($this->getBlockInstance() instanceof Block);
528 // First calculate minimum block length
529 $length = $this->getBlockInstance()->calculateMinimumBlockLength();
531 // Short be more than zero!
534 // Get current seek position
535 $seekPosition = $this->key();
537 // Then try to read it
538 $data = $this->read($length);
540 // If some bytes could be read, all is fine
541 $isValid = ((is_string($data)) && (strlen($data) > 0));
544 $headerSize = $this->getBlockInstance()->getHeaderSize();
546 // Is the seek position at or beyond the header?
547 if ($seekPosition >= $headerSize) {
548 // Seek back to old position
549 $this->seek($seekPosition);
551 // Seek directly behind the header
552 $this->seek($headerSize);
560 * Gets current seek position ("key").
562 * @return $key Current key in iteration
564 public function key () {
565 // Call pointer instance
566 return $this->getPointerInstance()->determineSeekPosition();
570 * Reads the file header
574 public function readFileHeader () {
575 // Make sure the block instance is set
576 assert($this->getBlockInstance() instanceof Block);
578 // Call block instance
579 $this->getBlockInstance()->readFileHeader();
583 * Flushes the file header
587 public function flushFileHeader () {
588 // Make sure the block instance is set
589 assert($this->getBlockInstance() instanceof Block);
591 // Call block instance
592 $this->getBlockInstance()->flushFileHeader();