e918c80b07fc391e71b52dfbecfde3a9cbd914b5
[core.git] / inc / classes / main / file_directories / class_BaseFile.php
1 <?php
2 /**
3  * A general file class
4  *
5  * @author              Roland Haeder <webmaster@ship-simu.org>
6  * @version             0.0.0
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
10  *
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.
15  *
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.
20  *
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/>.
23  */
24 class BaseFile extends BaseFrameworkSystem {
25         /**
26          * The current file we are working in
27          */
28         private $fileName = '';
29
30         /**
31          * Back-buffer
32          */
33         private $backBuffer = '';
34
35         /**
36          * Currently loaded block (will be returned by current())
37          */
38         private $currentBlock = '';
39
40         /**
41          * Protected constructor
42          *
43          * @param       $className      Name of the class
44 y        * @return      void
45          */
46         protected function __construct ($className) {
47                 // Call parent constructor
48                 parent::__construct($className);
49
50                 // Init counters and gaps array
51                 $this->initCountersGapsArray();
52         }
53
54         /**
55          * Destructor for cleaning purposes, etc
56          *
57          * @return      void
58          */
59         public final function __destruct() {
60                 // Try to close a file
61                 $this->closeFile();
62
63                 // Call the parent destructor
64                 parent::__destruct();
65         }
66
67         /**
68          * Getter for the file pointer
69          *
70          * @return      $filePointer    The file pointer which shall be a valid file resource
71          * @throws      UnsupportedOperationException   If this method is called
72          */
73         public final function getPointer () {
74                 throw new UnsupportedOperationException(array($this, __FUNCTION__), self::EXCEPTION_UNSPPORTED_OPERATION);
75         }
76
77         /**
78          * Setter for file name
79          *
80          * @param       $fileName       The new file name
81          * @return      void
82          */
83         protected final function setFileName ($fileName) {
84                 $fileName = (string) $fileName;
85                 $this->fileName = $fileName;
86         }
87
88         /**
89          * Getter for file name
90          *
91          * @return      $fileName       The current file name
92          */
93         public final function getFileName () {
94                 return $this->fileName;
95         }
96
97         /**
98          * Initializes the back-buffer by setting it to an empty string.
99          *
100          * @return      void
101          */
102         private function initBackBuffer () {
103                 // Simply call the setter
104                 $this->setBackBuffer('');
105         }
106
107         /**
108          * Setter for backBuffer field
109          *
110          * @param       $backBuffer             Characters to "store" in back-buffer
111          * @return      void
112          */
113         private function setBackBuffer ($backBuffer) {
114                 // Cast to string (so no arrays or objects)
115                 $backBuffer = (string) $backBuffer;
116
117                 // ... and set it
118                 $this->backBuffer = $backBuffer;
119         }
120
121         /**
122          * Getter for backBuffer field
123          *
124          * @return      $backBuffer             Characters "stored" in back-buffer
125          */
126         private function getBackBuffer () {
127                 return $this->backBuffer;
128         }
129
130         /**
131          * Setter for currentBlock field
132          *
133          * @param       $currentBlock           Characters to set a currently loaded block
134          * @return      void
135          */
136         private function setCurrentBlock ($currentBlock) {
137                 // Cast to string (so no arrays or objects)
138                 $currentBlock = (string) $currentBlock;
139
140                 // ... and set it
141                 $this->currentBlock = $currentBlock;
142         }
143
144         /**
145          * Gets currently read data
146          *
147          * @return      $current        Currently read data
148          */
149         public function getCurrentBlock () {
150                 // Return it
151                 return $this->currentBlock;
152         }
153
154         /**
155          * Initializes this file class
156          *
157          * @param       $fileName       Name of this abstract file
158          * @return      void
159          */
160         protected function initFile ($fileName) {
161                 // Get a file i/o pointer instance
162                 $pointerInstance = ObjectFactory::createObjectByConfiguredName('file_raw_input_output_class', array($fileName));
163
164                 // ... and set it here
165                 $this->setPointerInstance($pointerInstance);
166         }
167
168         /**
169          * Writes data at given position
170          *
171          * @param       $seekPosition   Seek position
172          * @param       $data                   Data to be written
173          * @param       $flushHeader    Whether to flush the header (default: flush)
174          * @return      void
175          */
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)));
178
179                 // Write data at given position
180                 $this->getPointerInstance()->writeAtPosition($seekPosition, $data);
181
182                 // Update seek position
183                 $this->updateSeekPosition();
184
185                 // Flush the header?
186                 if ($flushHeader === TRUE) {
187                         // Flush header
188                         $this->flushFileHeader();
189
190                         // Seek to old position
191                         $this->seekToOldPosition();
192                 } // END - if
193
194                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
195         }
196
197         /**
198          * Checks whether the file header is initialized
199          *
200          * @return      $isInitialized  Whether the file header is initialized
201          */
202         public function isFileHeaderInitialized () {
203                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
204
205                 // Default is not initialized
206                 $isInitialized = FALSE;
207
208                 // Is the file initialized?
209                 if ($this->isFileInitialized()) {
210                         // Some bytes has been written, so rewind to start of it.
211                         $rewindStatus = $this->getIteratorInstance()->rewind();
212                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] rewindStatus=%s', __METHOD__, __LINE__, $rewindStatus));
213
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__));
218                         } // END - if
219
220                         // Read file header
221                         $this->readFileHeader()
222
223                         // The above method does already check the header
224                         $isInitialized = TRUE;
225                 } // END - if
226
227                 // Return result
228                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] isInitialized=%d - EXIT!', __METHOD__, __LINE__, intval($isInitialized)));
229                 return $isInitialized;
230         }
231
232         /**
233          * Checks whether the assigned file has been initialized
234          *
235          * @return      $isInitialized          Whether the file's size is zero
236          */
237         public function isFileInitialized () {
238                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
239
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));
243
244                 /*
245                  * The returned file size should not be FALSE or NULL as this means
246                  * that the pointer class does not work correctly.
247                  */
248                 assert(is_int($fileSize));
249
250                 // Is more than 0 returned?
251                 $isInitialized = ($fileSize > 0);
252
253                 // Return result
254                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] isInitialized=%d - EXIT!', __METHOD__, __LINE__, intval($isInitialized)));
255                 return $isInitialized;
256         }
257
258         /**
259          * Creates the assigned file
260          *
261          * @return      void
262          */
263         public function createFileHeader () {
264                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
265
266                 // The file's header should not be initialized here
267                 assert(!$this->isFileHeaderInitialized());
268
269                 // Simple flush file header which will create it.
270                 $this->flushFileHeader();
271
272                 // Rewind seek position (to beginning of file) and update/flush file header
273                 $this->rewineUpdateSeekPosition();
274
275                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!!', __METHOD__, __LINE__));
276         }
277
278         /**
279          * Pre-allocates file (if enabled) with some space for later faster write access.
280          *
281          * @param       $type   Type of the file
282          * @return      void
283          */
284         public function preAllocateFile ($type) {
285                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
286
287                 // Is it enabled?
288                 if ($this->getConfigInstance()->getConfigEntry($type . '_pre_allocate_enabled') != 'Y') {
289                         // Not enabled
290                         self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Not pre-allocating file.', __METHOD__, __LINE__));
291
292                         // Don't continue here.
293                         return;
294                 } // END - if
295
296                 // Message to user
297                 self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Pre-allocating file ...', __METHOD__, __LINE__));
298
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));
302
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));
306
307                 // Now simply write a NUL there. This will pre-allocate the file.
308                 $this->writeData($seekPosition, chr(0));
309
310                 // Rewind seek position (to beginning of file) and update/flush file header
311                 $this->rewineUpdateSeekPosition();
312
313                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
314         }
315
316         /**
317          * Close a file source and set it's instance to null and the file name
318          * to empty
319          *
320          * @return      void
321          * @todo        ~10% done?
322          */
323         public function closeFile () {
324                 $this->partialStub('Unfinished method.');
325
326                 // Remove file name
327                 $this->setFileName('');
328         }
329
330         /**
331          * Determines seek position
332          *
333          * @return      $seekPosition   Current seek position
334          */
335         public function determineSeekPosition () {
336                 // Call pointer instance
337                 return $this->getPointerInstance()->determineSeekPosition();
338         }
339
340         /**
341          * Seek to given offset (default) or other possibilities as fseek() gives.
342          *
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
346          */
347         public function seek ($offset, $whence = SEEK_SET) {
348                 // Call pointer instance
349                 return $this->getPointerInstance()->seek($offset, $whence);
350         }
351
352         /**
353          * Size of this file
354          *
355          * @return      $size   Size (in bytes) of file
356          * @todo        Handle seekStatus
357          */
358         public function size () {
359                 // Call pointer instance
360                 return $this->getPointerInstance()->size();
361         }
362
363         /**
364          * Read data a file pointer
365          *
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
370          */
371         public function readFromFile () {
372                 // Call pointer instance
373                 return $this->getPointerInstance()->readFromFile();
374         }
375
376         /**
377          * Reads given amount of bytes from file.
378          *
379          * @param       $bytes  Amount of bytes to read
380          * @return      $data   Data read from file
381          */
382         public function read ($bytes) {
383                 // Call pointer instance
384                 return $this->getPointerInstance()->read($bytes);
385         }
386
387         /**
388          * Write data to a file pointer
389          *
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
396          */
397         public function writeToFile ($dataStream) {
398                 // Call pointer instance
399                 return $this->getPointerInstance()->writeToFile($dataStream);
400         }
401
402         /**
403          * Rewinds to the beginning of the file
404          *
405          * @return      $status         Status of this operation
406          */
407         public function rewind () {
408                 // Call pointer instance
409                 return $this->getPointerInstance()->rewind();
410         }
411
412         /**
413          * Determines whether the EOF has been reached
414          *
415          * @return      $isEndOfFileReached             Whether the EOF has been reached
416          */
417         public final function isEndOfFileReached () {
418                 // Call pointer instance
419                 return $this->getPointerInstance()->isEndOfFileReached();
420         }
421
422         /**
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).
426          *
427          * @return      void
428          */
429         public function analyzeFile () {
430                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
431
432                 // Make sure the file is initialized
433                 assert($this->isFileInitialized());
434
435                 // Init counters and gaps array
436                 $this->initCountersGapsArray();
437
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__));
440
441                 // First rewind to the begining
442                 $this->rewind();
443
444                 // Then try to load all entries
445                 while ($this->valid()) {
446                         // Go to next entry
447                         $this->next();
448
449                         // Get current entry
450                         $current = $this->getCurrentBlock();
451
452                         // Debug message
453                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] current()=%s', __METHOD__, __LINE__, strlen($current)));
454                 } // END - while
455
456                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
457         }
458
459         /**
460          * Advances to next "block" of bytes
461          *
462          * @return      void
463          * @todo        This method will load large but empty files in a whole
464          */
465         public function next () {
466                 // Is there nothing to read?
467                 if (!$this->valid()) {
468                         // Nothing to read
469                         return;
470                 } // END - if
471
472                 // Make sure the block instance is set
473                 assert($this->getBlockInstance() instanceof CalculatableBlock);
474
475                 // First calculate minimum block length
476                 $length = $this->getBlockInstance()->calculateMinimumBlockLength();
477
478                 // Short be more than zero!
479                 assert($length > 0);
480
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));
487                 } // END - if
488
489                 // EOF reached?
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);
494
495                         // Then abort here silently
496                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('EOF reached.');
497                         return;
498                 } // END - if
499
500                 /*
501                  * Init back-buffer which is the data that has been found beyond the
502                  * separator.
503                  */
504                 $this->initBackBuffer();
505
506                 // Separate data
507                 $dataArray = explode(self::getBlockSeparator(), $data);
508
509                 // This array must contain two elements
510                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('dataArray=' . print_r($dataArray, TRUE));
511                 assert(count($dataArray) == 2);
512
513                 // Left part is the actual block, right one the back-buffer data
514                 $this->setCurrentBlock($dataArray[0]);
515                 $this->setBackBuffer($dataArray[1]);
516         }
517
518         /**
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.
521          *
522          * @return      $isValid        Whether the next entry is valid
523          */
524         public function valid () {
525                 // Make sure the block instance is set
526                 assert($this->getBlockInstance() instanceof CalculatableBlock);
527
528                 // First calculate minimum block length
529                 $length = $this->getBlockInstance()->calculateMinimumBlockLength();
530
531                 // Short be more than zero!
532                 assert($length > 0);
533
534                 // Get current seek position
535                 $seekPosition = $this->key();
536
537                 // Then try to read it
538                 $data = $this->read($length);
539
540                 // If some bytes could be read, all is fine
541                 $isValid = ((is_string($data)) && (strlen($data) > 0));
542
543                 // Get header size
544                 $headerSize = $this->getBlockInstance()->getHeaderSize();
545
546                 // Is the seek position at or beyond the header?
547                 if ($seekPosition >= $headerSize) {
548                         // Seek back to old position
549                         $this->seek($seekPosition);
550                 } else {
551                         // Seek directly behind the header
552                         $this->seek($headerSize);
553                 }
554
555                 // Return result
556                 return $isValid;
557         }
558
559         /**
560          * Gets current seek position ("key").
561          *
562          * @return      $key    Current key in iteration
563          */
564         public function key () {
565                 // Call pointer instance
566                 return $this->getPointerInstance()->determineSeekPosition();
567         }
568
569         /**
570          * Reads the file header
571          *
572          * @return      void
573          */
574         public function readFileHeader () {
575                 // Call block instance
576                 $this->getBlockInstance()->readFileHeader()
577         }
578
579         /**
580          * Flushes the file header
581          *
582          * @return      void
583          */
584         public function flushFileHeader () {
585                 // Call block instance
586                 $this->getBlockInstance()->flushFileHeader()
587         }
588 }
589
590 // [EOF]
591 ?>