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