b7597f0c9c4393c2f522be42078517c3dda90797
[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->getConfigInstanstance()->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                 $this->getIteratorInstance()->seek($seekPosition);
281
282                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
283         }
284
285         /**
286          * Initializes this file-based stack.
287          *
288          * @param       $fileName       File name of this stack
289          * @return      void
290          */
291         protected function initFileStack ($fileName) {
292                 // Get a file i/o pointer instance
293                 $pointerInstance = ObjectFactory::createObjectByConfiguredName('file_raw_input_output_class', array($fileName));
294
295                 // Get iterator instance
296                 $iteratorInstance = ObjectFactory::createObjectByConfiguredName('file_io_iterator_class', array($pointerInstance));
297
298                 // Is the instance implementing the right interface?
299                 assert($iteratorInstance instanceof SeekableWritableFileIterator);
300
301                 // Set iterator here
302                 $this->setIteratorInstance($iteratorInstance);
303
304                 // Is the file's header initialized?
305                 if ($this->isFileHeaderInitialized()) {
306                         // Then load it
307                         $this->loadFileHeader();
308                 } else {
309                         // No, then create it (which may pre-allocate the stack)
310                         $this->createFileHeader();
311
312                         // And pre-allocate a bit
313                         $this->preAllocateFile();
314                 }
315         }
316
317         /**
318          * Initializes given stacker
319          *
320          * @param       $stackerName    Name of the stack
321          * @param       $forceReInit    Force re-initialization
322          * @return      void
323          * @throws      AlreadyInitializedStackerException      If the stack is already initialized
324          */
325         public function initStack ($stackerName, $forceReInit = FALSE) {
326                 // Is the stack already initialized?
327                 if (($forceReInit === FALSE) && ($this->isStackInitialized($stackerName))) {
328                         // Then throw the exception
329                         throw new AlreadyInitializedStackerException(array($this, $stackerName, $forceReInit), self::EXCEPTION_STACKER_ALREADY_INITIALIZED);
330                 } // END - if
331
332                 // Initialize the given stack
333                 $this->partialStub('stackerName=' . $stackerName . ',forceReInit=' . intval($forceReInit));
334         }
335
336         /**
337          * Checks whether the given stack is initialized (set in array $stackers)
338          *
339          * @param       $stackerName    Name of the stack
340          * @return      $isInitialized  Whether the stack is initialized
341          */
342         public function isStackInitialized ($stackerName) {
343                 // Is is there?
344                 $this->partialStub('stackerName=' . $stackerName);
345                 $isInitialized = TRUE;
346
347                 // Return result
348                 return $isInitialized;
349         }
350
351         /**
352          * Getter for size of given stack (array count)
353          *
354          * @param       $stackerName    Name of the stack
355          * @return      $count                  Size of stack (array count)
356          * @throws      NoStackerException      If given stack is missing
357          */
358         public function getStackCount ($stackerName) {
359                 // Is the stack not yet initialized?
360                 if (!$this->isStackInitialized($stackerName)) {
361                         // Throw an exception
362                         throw new NoStackerException(array($this, $stackerName), self::EXCEPTION_NO_STACKER_FOUND);
363                 } // END - if
364
365                 // Now, count the array of entries
366                 $this->partialStub('stackerName=' . $stackerName);
367                 $count = 0;
368
369                 // Return result
370                 return $count;
371         }
372
373         /**
374          * Adds a value to given stack
375          *
376          * @param       $stackerName    Name of the stack
377          * @param       $value                  Value to add to this stacker
378          * @return      void
379          * @throws      FullStackerException    Thrown if the stack is full
380          */
381         protected function addValue ($stackerName, $value) {
382                 // Is the stack not yet initialized or full?
383                 if (!$this->isStackInitialized($stackerName)) {
384                         // Then do it here
385                         $this->initStack($stackerName);
386                 } elseif ($this->isStackFull($stackerName)) {
387                         // Stacker is full
388                         throw new FullStackerException(array($this, $stackerName, $value), self::EXCEPTION_STACKER_IS_FULL);
389                 }
390
391                 // Now add the value to the stack
392                 $this->partialStub('stackerName=' . $stackerName . ',value[]=' . gettype($value));
393         }
394
395         /**
396          * Get last value from named stacker
397          *
398          * @param       $stackerName    Name of the stack
399          * @return      $value                  Value of last added value
400          * @throws      NoStackerException      If the named stacker was not found
401          * @throws      EmptyStackerException   If the named stacker is empty
402          */
403         protected function getLastValue ($stackerName) {
404                 // Is the stack not yet initialized or full?
405                 if (!$this->isStackInitialized($stackerName)) {
406                         // Throw an exception
407                         throw new NoStackerException(array($this, $stackerName), self::EXCEPTION_NO_STACKER_FOUND);
408                 } elseif ($this->isStackEmpty($stackerName)) {
409                         // Throw an exception
410                         throw new EmptyStackerException(array($this, $stackerName), self::EXCEPTION_STACKER_IS_EMPTY);
411                 }
412
413                 // Now get the last value
414                 $this->partialStub('stackerName=' . $stackerName);
415                 $value = NULL;
416
417                 // Return it
418                 return $value;
419         }
420
421         /**
422          * Get first value from named stacker
423          *
424          * @param       $stackerName    Name of the stack
425          * @return      $value                  Value of last added value
426          * @throws      NoStackerException      If the named stacker was not found
427          * @throws      EmptyStackerException   If the named stacker is empty
428          */
429         protected function getFirstValue ($stackerName) {
430                 // Is the stack not yet initialized or full?
431                 if (!$this->isStackInitialized($stackerName)) {
432                         // Throw an exception
433                         throw new NoStackerException(array($this, $stackerName), self::EXCEPTION_NO_STACKER_FOUND);
434                 } elseif ($this->isStackEmpty($stackerName)) {
435                         // Throw an exception
436                         throw new EmptyStackerException(array($this, $stackerName), self::EXCEPTION_STACKER_IS_EMPTY);
437                 }
438
439                 // Now get the first value
440                 $this->partialStub('stackerName=' . $stackerName);
441                 $value = NULL;
442
443                 // Return it
444                 return $value;
445         }
446
447         /**
448          * "Pops" last entry from stack
449          *
450          * @param       $stackerName    Name of the stack
451          * @return      $value                  Value "poped" from array
452          * @throws      NoStackerException      If the named stacker was not found
453          * @throws      EmptyStackerException   If the named stacker is empty
454          */
455         protected function popLast ($stackerName) {
456                 // Is the stack not yet initialized or full?
457                 if (!$this->isStackInitialized($stackerName)) {
458                         // Throw an exception
459                         throw new NoStackerException(array($this, $stackerName), self::EXCEPTION_NO_STACKER_FOUND);
460                 } elseif ($this->isStackEmpty($stackerName)) {
461                         // Throw an exception
462                         throw new EmptyStackerException(array($this, $stackerName), self::EXCEPTION_STACKER_IS_EMPTY);
463                 }
464
465                 // Now, remove the last entry, we don't care about the return value here, see elseif() block above
466                 $this->partialStub('stackerName=' . $stackerName);
467                 return NULL;
468         }
469
470         /**
471          * "Pops" first entry from stack
472          *
473          * @param       $stackerName    Name of the stack
474          * @return      $value                  Value "shifted" from array
475          * @throws      NoStackerException      If the named stacker was not found
476          * @throws      EmptyStackerException   If the named stacker is empty
477          */
478         protected function popFirst ($stackerName) {
479                 // Is the stack not yet initialized or full?
480                 if (!$this->isStackInitialized($stackerName)) {
481                         // Throw an exception
482                         throw new NoStackerException(array($this, $stackerName), self::EXCEPTION_NO_STACKER_FOUND);
483                 } elseif ($this->isStackEmpty($stackerName)) {
484                         // Throw an exception
485                         throw new EmptyStackerException(array($this, $stackerName), self::EXCEPTION_STACKER_IS_EMPTY);
486                 }
487
488                 // Now, remove the last entry, we don't care about the return value here, see elseif() block above
489                 $this->partialStub('stackerName=' . $stackerName);
490                 return NULL;
491         }
492 }
493
494 // [EOF]
495 ?>