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