Add stuff for reading header of a file-based stack.
[core.git] / inc / classes / main / stacker / file / class_BaseFileStack.php
1 <?php
2 /**
3  * A general file-based stack class
4  *
5  * @author              Roland Haeder <webmaster@ship-simu.org>
6  * @version             0.0.0
7  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2013 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 BaseFileStack extends BaseStacker {
25         /**
26          * Magic for this stack
27          */
28         const STACK_MAGIC = 'STACKv0.1';
29
30         /**
31          * Separator for header data
32          */
33         const SEPARATOR_HEADER_DATA = 0x01;
34
35         /**
36          * Separator header->entries
37          */
38         const SEPARATOR_HEADER_ENTRIES = 0x02;
39
40         /**
41          * Separator hash->name
42          */
43         const SEPARATOR_HASH_NAME = 0x03;
44
45         /**
46          * Length of name
47          */
48         const LENGTH_NAME = 10;
49
50         /**
51          * Length of count
52          */
53         const LENGTH_COUNT = 20;
54
55         /**
56          * Length of position
57          */
58         const LENGTH_POSITION = 20;
59
60         /**
61          * Counter for total entries
62          */
63         private $totalEntries = 0;
64
65         /**
66          * Current seek position
67          */
68         private $seekPosition = 0;
69
70         /**
71          * Size of header
72          */
73         private $headerSize = 0;
74
75         /**
76          * File header
77          */
78         private $header = array();
79
80         /**
81          * Protected constructor
82          *
83          * @param       $className      Name of the class
84          * @return      void
85          */
86         protected function __construct ($className) {
87                 // Call parent constructor
88                 parent::__construct($className);
89
90                 // Calculate header size
91                 $this->headerSize =
92                         strlen(self::MAGIC) +
93                         strlen(self::SEPARATOR_HEADER_DATA) +
94                         self::LENGTH_COUNT +
95                         strlen(self::SEPARATOR_HEADER_DATA) +
96                         self::LENGTH_POSITION +
97                         strlen(self::SEPARATOR_HEADER_ENTRIES)
98                 );
99         }
100
101         /**
102          * Getter for total entries
103          *
104          * @return      $totalEntries   Total entries in this stack
105          */
106         private function getCounter () {
107                 // Get it
108                 return $this->totalEntries;
109         }
110
111         /**
112          * Increment counter
113          *
114          * @return      void
115          */
116         private function incrementCounter () {
117                 // Get it
118                 $this->totalEntries++;
119         }
120
121         /**
122          * Getter for seek position
123          *
124          * @return      $seekPosition   Current seek position (stored here in object)
125          */
126         private function getSeekPosition () {
127                 // Get it
128                 return $this->seekPosition;
129         }
130
131         /**
132          * Setter for seek position
133          *
134          * @param       $seekPosition   Current seek position (stored here in object)
135          * @return      void
136          */
137         private function setSeekPosition ($seekPosition) {
138                 // And set it
139                 $this->seekPosition = $seekPosition;
140         }
141
142         /**
143          * Updates seekPosition attribute from file to avoid to much access on file.
144          *
145          * @return      void
146          */
147         private function updateSeekPosition () {
148                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
149
150                 // Get key (= seek position)
151                 $seekPosition = $this->getIteratorInstance()->key();
152                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Setting seekPosition=%s', __METHOD__, __LINE__, $seekPosition));
153
154                 // And set it here
155                 $this->setSeekPosition($seekPosition);
156
157                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
158         }
159
160         /**
161          * Reads the file header
162          *
163          * @return      void
164          */
165         private function readFileHeader () {
166                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
167
168                 // First rewind to beginning as the header sits at the beginning ...
169                 $this->getIteratorInstance()->rewind();
170
171                 // Then read it (see constructor for calculation)
172                 $data = $this->getIteratorInstance()->read($this->headerLength);
173
174                 // Last character must be the separator
175                 assert(substr($data, -1, 1) == self::SEPARATOR_HEADER_ENTRIES);
176
177                 // Okay, then remove it
178                 $data = substr($data, 0, -1);
179
180                 // And update seek position
181                 $this->updateSeekPosition();
182
183                 /*
184                  * Now split it:
185                  *
186                  * 0 => Magic
187                  * 1 => Total entries
188                  * 2 => Current seek position
189                  */
190                 $this->header = explode(self::SEPARATOR_HEADER_DATA, $data);
191
192                 // Check if the array has only 3 elements
193                 assert(count($this->header) == 3);
194
195                 // Check magic
196                 assert($this->header[0] == self::MAGIC);
197
198                 // Check length of count and seek position
199                 assert(strlen($this->header[1]) == self::LENGTH_COUNT);
200                 assert(strlen($this->header[2]) == self::LENGTH_POSITION);
201
202                 // Decode count and seek position
203                 $this->header[1] = hex2bin($this->header[1]);
204                 $this->header[2] = hex2bin($this->header[2]);
205
206                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
207         }
208
209         /**
210          * Checks whether the file header is initialized
211          *
212          * @return      $isInitialized  Whether the file header is initialized
213          */
214         private function isFileHeaderInitialized () {
215                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
216                 // Default is not initialized
217                 $isInitialized = FALSE;
218
219                 // Is the file initialized?
220                 if ($this->isFileInitialized()) {
221                         // Some bytes has been written, so rewind to start of it.
222                         $rewindStatus = $this->getIteratorInstance()->rewind();
223                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] rewindStatus=%s', __METHOD__, __LINE__, $rewindStatus));
224
225                         // Is the rewind() call successfull?
226                         if ($rewindStatus != 1) {
227                                 // Something bad happened
228                                 self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Could not rewind().', __METHOD__, __LINE__));
229                         } // END - if
230
231                         // Read file header
232                         $this->readFileHeader();
233
234                         // Check if its format is valid.
235                 } // END - if
236
237                 // Return result
238                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] isInitialized=%d - EXIT!', __METHOD__, __LINE__, intval($isInitialized)));
239                 return $isInitialized;
240         }
241
242         /**
243          * Checks whether the file-based stack has been initialized
244          *
245          * @return      $isInitialized          Whether the file's size is zero
246          */
247         private function isFileInitialized () {
248                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
249
250                 // Get it from iterator which holds the pointer instance. If FALSE is returned
251                 $fileSize = $this->getIteratorInstance()->size();
252                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] fileSize=%s', __METHOD__, __LINE__, $fileSize));
253
254                 /*
255                  * The returned file size should not be FALSE or NULL as this means
256                  * that the pointer class does not work correctly.
257                  */
258                 assert(is_int($fileSize));
259
260                 // Is more than 0 returned?
261                 $isInitialized = ($fileSize > 0);
262
263                 // Return result
264                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] isInitialized=%d - EXIT!', __METHOD__, __LINE__, intval($isInitialized)));
265                 return $isInitialized;
266         }
267
268         /**
269          * Creates the file-stack's header
270          *
271          * @return      void
272          */
273         private function createFileHeader () {
274                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
275                 // The file's header should not be initialized here
276                 assert(!$this->isFileHeaderInitialized());
277
278                 // Simple flush file header which will create it.
279                 $this->flushFileHeader();
280
281                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!!', __METHOD__, __LINE__));
282         }
283
284         /**
285          * Flushes the file header
286          *
287          * @return      void
288          */
289         private function flushFileHeader () {
290                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
291
292                 // Put all informations together
293                 $header = sprintf('%s%s%s%s%s%s',
294                         // Magic
295                         self::STACK_MAGIC,
296
297                         // Separator magic<->count
298                         chr(self::SEPARATOR_HEADER_DATA),
299
300                         // Total entries (will be zero) and pad it to 20 chars
301                         str_pad($this->dec2hex($this->getCounter()), self::LENGTH_COUNT, '0', STR_PAD_LEFT),
302
303                         // Separator count<->seek position
304                         chr(self::SEPARATOR_HEADER_DATA),
305
306                         // Position (will be zero)
307                         str_pad($this->dec2hex($this->getSeekPosition(), 2), self::LENGTH_POSITION, '0', STR_PAD_LEFT),
308
309                         // Separator position<->entries
310                         chr(self::SEPARATOR_HEADER_ENTRIES)
311                 );
312
313                 // Write it to disk (header is always at seek position 0)
314                 $this->writeData(0, $header);
315
316                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
317         }
318
319         /**
320          * Writes data at given position
321          *
322          * @param       $seekPosition   Seek position
323          * @param       $data                   Data to be written
324          * @return      void
325          */
326         private function writeData ($seekPosition, $data) {
327                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] seekPosition=%s,data()=%s - CALLED!', __METHOD__, __LINE__, $seekPosition, strlen($data)));
328
329                 // Write data at given position
330                 $this->getIteratorInstance()->writeAtPosition($seekPosition, $data);
331
332                 // Update seek position
333                 $this->updateSeekPosition();
334
335                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
336         }
337
338         /**
339          * Pre-allocates file (if enabled) with some space for later faster write access.
340          *
341          * @return      void
342          */
343         private function preAllocateFile () {
344                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
345
346                 // Is it enabled?
347                 if ($this->getConfigInstance()->getConfigEntry('file_stack_pre_allocate_enabled') != 'Y') {
348                         // Not enabled
349                         self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Not pre-allocating stack file.', __METHOD__, __LINE__));
350
351                         // Don't continue here.
352                         return;
353                 } // END - if
354
355                 // Message to user
356                 self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Pre-allocating stack file ...', __METHOD__, __LINE__));
357
358                 /*
359                  * Calculate minimum length for one entry:
360                  * minimum length = hash length + separator + name + minimum entry size = ?? + 1 + 10 + 1 = ??
361                  */
362                 $minLengthEntry = self::getHashLength() + strlen(self::SEPARATOR_HASH_NAME) + self::LENGTH_NAME + 1;
363                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] minLengthEntry=%s', __METHOD__, __LINE__, $minLengthEntry));
364
365                 // Calulcate seek position
366                 $seekPosition = $minLengthEntry * $this->getConfigInstance()->getConfigEntry('file_stack_pre_allocate_count');
367                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] seekPosition=%s', __METHOD__, __LINE__, $seekPosition));
368
369                 // Now simply write a NUL there. This will pre-allocate the file.
370                 $this->writeData($seekPosition, chr(0));
371
372                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
373         }
374
375         /**
376          * Initializes this file-based stack.
377          *
378          * @param       $fileName       File name of this stack
379          * @return      void
380          */
381         protected function initFileStack ($fileName) {
382                 // Get a file i/o pointer instance
383                 $pointerInstance = ObjectFactory::createObjectByConfiguredName('file_raw_input_output_class', array($fileName));
384
385                 // Get iterator instance
386                 $iteratorInstance = ObjectFactory::createObjectByConfiguredName('file_io_iterator_class', array($pointerInstance));
387
388                 // Is the instance implementing the right interface?
389                 assert($iteratorInstance instanceof SeekableWritableFileIterator);
390
391                 // Set iterator here
392                 $this->setIteratorInstance($iteratorInstance);
393
394                 // Is the file's header initialized?
395                 if ($this->isFileHeaderInitialized()) {
396                         // Then load it
397                         $this->loadFileHeader();
398                 } else {
399                         // No, then create it (which may pre-allocate the stack)
400                         $this->createFileHeader();
401
402                         // And pre-allocate a bit
403                         $this->preAllocateFile();
404                 }
405         }
406
407         /**
408          * Initializes given stacker
409          *
410          * @param       $stackerName    Name of the stack
411          * @param       $forceReInit    Force re-initialization
412          * @return      void
413          * @throws      AlreadyInitializedStackerException      If the stack is already initialized
414          */
415         public function initStack ($stackerName, $forceReInit = FALSE) {
416                 // Is the stack already initialized?
417                 if (($forceReInit === FALSE) && ($this->isStackInitialized($stackerName))) {
418                         // Then throw the exception
419                         throw new AlreadyInitializedStackerException(array($this, $stackerName, $forceReInit), self::EXCEPTION_STACKER_ALREADY_INITIALIZED);
420                 } // END - if
421
422                 // Initialize the given stack
423                 $this->partialStub('stackerName=' . $stackerName . ',forceReInit=' . intval($forceReInit));
424         }
425
426         /**
427          * Checks whether the given stack is initialized (set in array $stackers)
428          *
429          * @param       $stackerName    Name of the stack
430          * @return      $isInitialized  Whether the stack is initialized
431          */
432         public function isStackInitialized ($stackerName) {
433                 // Is is there?
434                 $this->partialStub('stackerName=' . $stackerName);
435                 $isInitialized = TRUE;
436
437                 // Return result
438                 return $isInitialized;
439         }
440
441         /**
442          * Getter for size of given stack (array count)
443          *
444          * @param       $stackerName    Name of the stack
445          * @return      $count                  Size of stack (array count)
446          * @throws      NoStackerException      If given stack is missing
447          */
448         public function getStackCount ($stackerName) {
449                 // Is the stack not yet initialized?
450                 if (!$this->isStackInitialized($stackerName)) {
451                         // Throw an exception
452                         throw new NoStackerException(array($this, $stackerName), self::EXCEPTION_NO_STACKER_FOUND);
453                 } // END - if
454
455                 // Now, count the array of entries
456                 $this->partialStub('stackerName=' . $stackerName);
457                 $count = 0;
458
459                 // Return result
460                 return $count;
461         }
462
463         /**
464          * Adds a value to given stack
465          *
466          * @param       $stackerName    Name of the stack
467          * @param       $value                  Value to add to this stacker
468          * @return      void
469          * @throws      FullStackerException    Thrown if the stack is full
470          */
471         protected function addValue ($stackerName, $value) {
472                 // Is the stack not yet initialized or full?
473                 if (!$this->isStackInitialized($stackerName)) {
474                         // Then do it here
475                         $this->initStack($stackerName);
476                 } elseif ($this->isStackFull($stackerName)) {
477                         // Stacker is full
478                         throw new FullStackerException(array($this, $stackerName, $value), self::EXCEPTION_STACKER_IS_FULL);
479                 }
480
481                 // Now add the value to the stack
482                 $this->partialStub('stackerName=' . $stackerName . ',value[]=' . gettype($value));
483         }
484
485         /**
486          * Get last value from named stacker
487          *
488          * @param       $stackerName    Name of the stack
489          * @return      $value                  Value of last added value
490          * @throws      NoStackerException      If the named stacker was not found
491          * @throws      EmptyStackerException   If the named stacker is empty
492          */
493         protected function getLastValue ($stackerName) {
494                 // Is the stack not yet initialized or full?
495                 if (!$this->isStackInitialized($stackerName)) {
496                         // Throw an exception
497                         throw new NoStackerException(array($this, $stackerName), self::EXCEPTION_NO_STACKER_FOUND);
498                 } elseif ($this->isStackEmpty($stackerName)) {
499                         // Throw an exception
500                         throw new EmptyStackerException(array($this, $stackerName), self::EXCEPTION_STACKER_IS_EMPTY);
501                 }
502
503                 // Now get the last value
504                 $this->partialStub('stackerName=' . $stackerName);
505                 $value = NULL;
506
507                 // Return it
508                 return $value;
509         }
510
511         /**
512          * Get first value from named stacker
513          *
514          * @param       $stackerName    Name of the stack
515          * @return      $value                  Value of last added value
516          * @throws      NoStackerException      If the named stacker was not found
517          * @throws      EmptyStackerException   If the named stacker is empty
518          */
519         protected function getFirstValue ($stackerName) {
520                 // Is the stack not yet initialized or full?
521                 if (!$this->isStackInitialized($stackerName)) {
522                         // Throw an exception
523                         throw new NoStackerException(array($this, $stackerName), self::EXCEPTION_NO_STACKER_FOUND);
524                 } elseif ($this->isStackEmpty($stackerName)) {
525                         // Throw an exception
526                         throw new EmptyStackerException(array($this, $stackerName), self::EXCEPTION_STACKER_IS_EMPTY);
527                 }
528
529                 // Now get the first value
530                 $this->partialStub('stackerName=' . $stackerName);
531                 $value = NULL;
532
533                 // Return it
534                 return $value;
535         }
536
537         /**
538          * "Pops" last entry from stack
539          *
540          * @param       $stackerName    Name of the stack
541          * @return      $value                  Value "poped" from array
542          * @throws      NoStackerException      If the named stacker was not found
543          * @throws      EmptyStackerException   If the named stacker is empty
544          */
545         protected function popLast ($stackerName) {
546                 // Is the stack not yet initialized or full?
547                 if (!$this->isStackInitialized($stackerName)) {
548                         // Throw an exception
549                         throw new NoStackerException(array($this, $stackerName), self::EXCEPTION_NO_STACKER_FOUND);
550                 } elseif ($this->isStackEmpty($stackerName)) {
551                         // Throw an exception
552                         throw new EmptyStackerException(array($this, $stackerName), self::EXCEPTION_STACKER_IS_EMPTY);
553                 }
554
555                 // Now, remove the last entry, we don't care about the return value here, see elseif() block above
556                 $this->partialStub('stackerName=' . $stackerName);
557                 return NULL;
558         }
559
560         /**
561          * "Pops" first entry from stack
562          *
563          * @param       $stackerName    Name of the stack
564          * @return      $value                  Value "shifted" from array
565          * @throws      NoStackerException      If the named stacker was not found
566          * @throws      EmptyStackerException   If the named stacker is empty
567          */
568         protected function popFirst ($stackerName) {
569                 // Is the stack not yet initialized or full?
570                 if (!$this->isStackInitialized($stackerName)) {
571                         // Throw an exception
572                         throw new NoStackerException(array($this, $stackerName), self::EXCEPTION_NO_STACKER_FOUND);
573                 } elseif ($this->isStackEmpty($stackerName)) {
574                         // Throw an exception
575                         throw new EmptyStackerException(array($this, $stackerName), self::EXCEPTION_STACKER_IS_EMPTY);
576                 }
577
578                 // Now, remove the last entry, we don't care about the return value here, see elseif() block above
579                 $this->partialStub('stackerName=' . $stackerName);
580                 return NULL;
581         }
582 }
583
584 // [EOF]
585 ?>