Implemented rudely reading file header + commented out a lot noisy debug lines.
[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::STACK_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->headerSize);
173                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Read %d bytes (%d wanted).', __METHOD__, __LINE__, strlen($data), $this->headerSize));
174
175                 // Have all requested bytes been read?
176                 assert(strlen($data) == $this->headerSize);
177                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Passed assert().', __METHOD__, __LINE__));
178
179                 // Last character must be the separator
180                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] data(-1)=%s', __METHOD__, __LINE__, dechex(ord(substr($data, -1, 1)))));
181                 assert(substr($data, -1, 1) == chr(self::SEPARATOR_HEADER_ENTRIES));
182                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Passed assert().', __METHOD__, __LINE__));
183
184                 // Okay, then remove it
185                 $data = substr($data, 0, -1);
186
187                 // And update seek position
188                 $this->updateSeekPosition();
189
190                 /*
191                  * Now split it:
192                  *
193                  * 0 => Magic
194                  * 1 => Total entries
195                  * 2 => Current seek position
196                  */
197                 $this->header = explode(chr(self::SEPARATOR_HEADER_DATA), $data);
198
199                 // Check if the array has only 3 elements
200                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] header(%d)=%s', __METHOD__, __LINE__, count($this->header), print_r($this->header, TRUE)));
201                 assert(count($this->header) == 3);
202                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Passed assert().', __METHOD__, __LINE__));
203
204                 // Check magic
205                 assert($this->header[0] == self::STACK_MAGIC);
206                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Passed assert().', __METHOD__, __LINE__));
207
208                 // Check length of count and seek position
209                 assert(strlen($this->header[1]) == self::LENGTH_COUNT);
210                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Passed assert().', __METHOD__, __LINE__));
211                 assert(strlen($this->header[2]) == self::LENGTH_POSITION);
212                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Passed assert().', __METHOD__, __LINE__));
213
214                 // Decode count and seek position
215                 $this->header[1] = hex2bin($this->header[1]);
216                 $this->header[2] = hex2bin($this->header[2]);
217
218                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
219         }
220
221         /**
222          * Checks whether the file header is initialized
223          *
224          * @return      $isInitialized  Whether the file header is initialized
225          */
226         private function isFileHeaderInitialized () {
227                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
228                 // Default is not initialized
229                 $isInitialized = FALSE;
230
231                 // Is the file initialized?
232                 if ($this->isFileInitialized()) {
233                         // Some bytes has been written, so rewind to start of it.
234                         $rewindStatus = $this->getIteratorInstance()->rewind();
235                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] rewindStatus=%s', __METHOD__, __LINE__, $rewindStatus));
236
237                         // Is the rewind() call successfull?
238                         if ($rewindStatus != 1) {
239                                 // Something bad happened
240                                 self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Could not rewind().', __METHOD__, __LINE__));
241                         } // END - if
242
243                         // Read file header
244                         $this->readFileHeader();
245
246                         // The above method does already check the header
247                         $isInitialized = TRUE;
248                 } // END - if
249
250                 // Return result
251                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] isInitialized=%d - EXIT!', __METHOD__, __LINE__, intval($isInitialized)));
252                 return $isInitialized;
253         }
254
255         /**
256          * Checks whether the file-based stack has been initialized
257          *
258          * @return      $isInitialized          Whether the file's size is zero
259          */
260         private function isFileInitialized () {
261                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
262
263                 // Get it from iterator which holds the pointer instance. If FALSE is returned
264                 $fileSize = $this->getIteratorInstance()->size();
265                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] fileSize=%s', __METHOD__, __LINE__, $fileSize));
266
267                 /*
268                  * The returned file size should not be FALSE or NULL as this means
269                  * that the pointer class does not work correctly.
270                  */
271                 assert(is_int($fileSize));
272
273                 // Is more than 0 returned?
274                 $isInitialized = ($fileSize > 0);
275
276                 // Return result
277                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] isInitialized=%d - EXIT!', __METHOD__, __LINE__, intval($isInitialized)));
278                 return $isInitialized;
279         }
280
281         /**
282          * Creates the file-stack's header
283          *
284          * @return      void
285          */
286         private function createFileHeader () {
287                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
288                 // The file's header should not be initialized here
289                 assert(!$this->isFileHeaderInitialized());
290
291                 // Simple flush file header which will create it.
292                 $this->flushFileHeader();
293
294                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!!', __METHOD__, __LINE__));
295         }
296
297         /**
298          * Flushes the file header
299          *
300          * @return      void
301          */
302         private function flushFileHeader () {
303                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
304
305                 // Put all informations together
306                 $header = sprintf('%s%s%s%s%s%s',
307                         // Magic
308                         self::STACK_MAGIC,
309
310                         // Separator magic<->count
311                         chr(self::SEPARATOR_HEADER_DATA),
312
313                         // Total entries (will be zero) and pad it to 20 chars
314                         str_pad($this->dec2hex($this->getCounter()), self::LENGTH_COUNT, '0', STR_PAD_LEFT),
315
316                         // Separator count<->seek position
317                         chr(self::SEPARATOR_HEADER_DATA),
318
319                         // Position (will be zero)
320                         str_pad($this->dec2hex($this->getSeekPosition(), 2), self::LENGTH_POSITION, '0', STR_PAD_LEFT),
321
322                         // Separator position<->entries
323                         chr(self::SEPARATOR_HEADER_ENTRIES)
324                 );
325
326                 // Write it to disk (header is always at seek position 0)
327                 $this->writeData(0, $header);
328
329                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
330         }
331
332         /**
333          * Writes data at given position
334          *
335          * @param       $seekPosition   Seek position
336          * @param       $data                   Data to be written
337          * @return      void
338          */
339         private function writeData ($seekPosition, $data) {
340                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] seekPosition=%s,data()=%s - CALLED!', __METHOD__, __LINE__, $seekPosition, strlen($data)));
341
342                 // Write data at given position
343                 $this->getIteratorInstance()->writeAtPosition($seekPosition, $data);
344
345                 // Update seek position
346                 $this->updateSeekPosition();
347
348                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
349         }
350
351         /**
352          * Pre-allocates file (if enabled) with some space for later faster write access.
353          *
354          * @return      void
355          */
356         private function preAllocateFile () {
357                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
358
359                 // Is it enabled?
360                 if ($this->getConfigInstance()->getConfigEntry('file_stack_pre_allocate_enabled') != 'Y') {
361                         // Not enabled
362                         self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Not pre-allocating stack file.', __METHOD__, __LINE__));
363
364                         // Don't continue here.
365                         return;
366                 } // END - if
367
368                 // Message to user
369                 self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Pre-allocating stack file ...', __METHOD__, __LINE__));
370
371                 /*
372                  * Calculate minimum length for one entry:
373                  * minimum length = hash length + separator + name + minimum entry size = ?? + 1 + 10 + 1 = ??
374                  */
375                 $minLengthEntry = self::getHashLength() + strlen(self::SEPARATOR_HASH_NAME) + self::LENGTH_NAME + 1;
376                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] minLengthEntry=%s', __METHOD__, __LINE__, $minLengthEntry));
377
378                 // Calulcate seek position
379                 $seekPosition = $minLengthEntry * $this->getConfigInstance()->getConfigEntry('file_stack_pre_allocate_count');
380                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] seekPosition=%s', __METHOD__, __LINE__, $seekPosition));
381
382                 // Now simply write a NUL there. This will pre-allocate the file.
383                 $this->writeData($seekPosition, chr(0));
384
385                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
386         }
387
388         /**
389          * Initializes this file-based stack.
390          *
391          * @param       $fileName       File name of this stack
392          * @return      void
393          */
394         protected function initFileStack ($fileName) {
395                 // Get a file i/o pointer instance
396                 $pointerInstance = ObjectFactory::createObjectByConfiguredName('file_raw_input_output_class', array($fileName));
397
398                 // Get iterator instance
399                 $iteratorInstance = ObjectFactory::createObjectByConfiguredName('file_io_iterator_class', array($pointerInstance));
400
401                 // Is the instance implementing the right interface?
402                 assert($iteratorInstance instanceof SeekableWritableFileIterator);
403
404                 // Set iterator here
405                 $this->setIteratorInstance($iteratorInstance);
406
407                 // Is the file's header initialized?
408                 if ($this->isFileHeaderInitialized()) {
409                         // Then load it
410                         $this->loadFileHeader();
411                 } else {
412                         // No, then create it (which may pre-allocate the stack)
413                         $this->createFileHeader();
414
415                         // And pre-allocate a bit
416                         $this->preAllocateFile();
417                 }
418         }
419
420         /**
421          * Initializes given stacker
422          *
423          * @param       $stackerName    Name of the stack
424          * @param       $forceReInit    Force re-initialization
425          * @return      void
426          * @throws      AlreadyInitializedStackerException      If the stack is already initialized
427          */
428         public function initStack ($stackerName, $forceReInit = FALSE) {
429                 // Is the stack already initialized?
430                 if (($forceReInit === FALSE) && ($this->isStackInitialized($stackerName))) {
431                         // Then throw the exception
432                         throw new AlreadyInitializedStackerException(array($this, $stackerName, $forceReInit), self::EXCEPTION_STACKER_ALREADY_INITIALIZED);
433                 } // END - if
434
435                 // Initialize the given stack
436                 $this->partialStub('stackerName=' . $stackerName . ',forceReInit=' . intval($forceReInit));
437         }
438
439         /**
440          * Checks whether the given stack is initialized (set in array $stackers)
441          *
442          * @param       $stackerName    Name of the stack
443          * @return      $isInitialized  Whether the stack is initialized
444          */
445         public function isStackInitialized ($stackerName) {
446                 // Is is there?
447                 $this->partialStub('stackerName=' . $stackerName);
448                 $isInitialized = TRUE;
449
450                 // Return result
451                 return $isInitialized;
452         }
453
454         /**
455          * Getter for size of given stack (array count)
456          *
457          * @param       $stackerName    Name of the stack
458          * @return      $count                  Size of stack (array count)
459          * @throws      NoStackerException      If given stack is missing
460          */
461         public function getStackCount ($stackerName) {
462                 // Is the stack not yet initialized?
463                 if (!$this->isStackInitialized($stackerName)) {
464                         // Throw an exception
465                         throw new NoStackerException(array($this, $stackerName), self::EXCEPTION_NO_STACKER_FOUND);
466                 } // END - if
467
468                 // Now, count the array of entries
469                 $this->partialStub('stackerName=' . $stackerName);
470                 $count = 0;
471
472                 // Return result
473                 return $count;
474         }
475
476         /**
477          * Adds a value to given stack
478          *
479          * @param       $stackerName    Name of the stack
480          * @param       $value                  Value to add to this stacker
481          * @return      void
482          * @throws      FullStackerException    Thrown if the stack is full
483          */
484         protected function addValue ($stackerName, $value) {
485                 // Is the stack not yet initialized or full?
486                 if (!$this->isStackInitialized($stackerName)) {
487                         // Then do it here
488                         $this->initStack($stackerName);
489                 } elseif ($this->isStackFull($stackerName)) {
490                         // Stacker is full
491                         throw new FullStackerException(array($this, $stackerName, $value), self::EXCEPTION_STACKER_IS_FULL);
492                 }
493
494                 // Now add the value to the stack
495                 $this->partialStub('stackerName=' . $stackerName . ',value[]=' . gettype($value));
496         }
497
498         /**
499          * Get last value from named stacker
500          *
501          * @param       $stackerName    Name of the stack
502          * @return      $value                  Value of last added value
503          * @throws      NoStackerException      If the named stacker was not found
504          * @throws      EmptyStackerException   If the named stacker is empty
505          */
506         protected function getLastValue ($stackerName) {
507                 // Is the stack not yet initialized or full?
508                 if (!$this->isStackInitialized($stackerName)) {
509                         // Throw an exception
510                         throw new NoStackerException(array($this, $stackerName), self::EXCEPTION_NO_STACKER_FOUND);
511                 } elseif ($this->isStackEmpty($stackerName)) {
512                         // Throw an exception
513                         throw new EmptyStackerException(array($this, $stackerName), self::EXCEPTION_STACKER_IS_EMPTY);
514                 }
515
516                 // Now get the last value
517                 $this->partialStub('stackerName=' . $stackerName);
518                 $value = NULL;
519
520                 // Return it
521                 return $value;
522         }
523
524         /**
525          * Get first value from named stacker
526          *
527          * @param       $stackerName    Name of the stack
528          * @return      $value                  Value of last added value
529          * @throws      NoStackerException      If the named stacker was not found
530          * @throws      EmptyStackerException   If the named stacker is empty
531          */
532         protected function getFirstValue ($stackerName) {
533                 // Is the stack not yet initialized or full?
534                 if (!$this->isStackInitialized($stackerName)) {
535                         // Throw an exception
536                         throw new NoStackerException(array($this, $stackerName), self::EXCEPTION_NO_STACKER_FOUND);
537                 } elseif ($this->isStackEmpty($stackerName)) {
538                         // Throw an exception
539                         throw new EmptyStackerException(array($this, $stackerName), self::EXCEPTION_STACKER_IS_EMPTY);
540                 }
541
542                 // Now get the first value
543                 $this->partialStub('stackerName=' . $stackerName);
544                 $value = NULL;
545
546                 // Return it
547                 return $value;
548         }
549
550         /**
551          * "Pops" last entry from stack
552          *
553          * @param       $stackerName    Name of the stack
554          * @return      $value                  Value "poped" from array
555          * @throws      NoStackerException      If the named stacker was not found
556          * @throws      EmptyStackerException   If the named stacker is empty
557          */
558         protected function popLast ($stackerName) {
559                 // Is the stack not yet initialized or full?
560                 if (!$this->isStackInitialized($stackerName)) {
561                         // Throw an exception
562                         throw new NoStackerException(array($this, $stackerName), self::EXCEPTION_NO_STACKER_FOUND);
563                 } elseif ($this->isStackEmpty($stackerName)) {
564                         // Throw an exception
565                         throw new EmptyStackerException(array($this, $stackerName), self::EXCEPTION_STACKER_IS_EMPTY);
566                 }
567
568                 // Now, remove the last entry, we don't care about the return value here, see elseif() block above
569                 $this->partialStub('stackerName=' . $stackerName);
570                 return NULL;
571         }
572
573         /**
574          * "Pops" first entry from stack
575          *
576          * @param       $stackerName    Name of the stack
577          * @return      $value                  Value "shifted" from array
578          * @throws      NoStackerException      If the named stacker was not found
579          * @throws      EmptyStackerException   If the named stacker is empty
580          */
581         protected function popFirst ($stackerName) {
582                 // Is the stack not yet initialized or full?
583                 if (!$this->isStackInitialized($stackerName)) {
584                         // Throw an exception
585                         throw new NoStackerException(array($this, $stackerName), self::EXCEPTION_NO_STACKER_FOUND);
586                 } elseif ($this->isStackEmpty($stackerName)) {
587                         // Throw an exception
588                         throw new EmptyStackerException(array($this, $stackerName), self::EXCEPTION_STACKER_IS_EMPTY);
589                 }
590
591                 // Now, remove the last entry, we don't care about the return value here, see elseif() block above
592                 $this->partialStub('stackerName=' . $stackerName);
593                 return NULL;
594         }
595 }
596
597 // [EOF]
598 ?>