Introduced writeData() (low-level method).
[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                 // Simple flush file header which will create it.
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->writeData(0, $header);
249
250                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
251         }
252
253         /**
254          * Writes data at given position
255          *
256          * @param       $seekPosition   Seek position
257          * @param       $data                   Data to be written
258          * @return      void
259          */
260         private function writeData ($seekPosition, $data) {
261                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] seekPosition=%s,data()=%s - CALLED!', __METHOD__, __LINE__, $seekPosition, strlen($data)));
262
263                 // Write data at given position
264                 $this->getIteratorInstance()->writeAtPosition($seekPosition, $data);
265
266                 // Update seek position
267                 $this->updateSeekPosition();
268
269                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
270         }
271
272         /**
273          * Pre-allocates file (if enabled) with some space for later faster write access.
274          *
275          * @return      void
276          */
277         private function preAllocateFile () {
278                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] CALLED!', __METHOD__, __LINE__));
279
280                 // Is it enabled?
281                 if ($this->getConfigInstance()->getConfigEntry('file_stack_pre_allocate_enabled') != 'Y') {
282                         // Not enabled
283                         self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Not pre-allocating stack file.', __METHOD__, __LINE__));
284
285                         // Don't continue here.
286                         return;
287                 } // END - if
288
289                 // Message to user
290                 self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] Pre-allocating stack file ...', __METHOD__, __LINE__));
291
292                 /*
293                  * Calculate minimum length for one entry:
294                  * minimum length = hash length + separator + name + minimum entry size = ?? + 1 + 10 + 1 = ??
295                  */
296                 $minLengthEntry = self::getHashLength() + strlen(self::SEPARATOR_HASH_NAME) + self::COUNT_NAME + 1;
297                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] minLengthEntry=%s', __METHOD__, __LINE__, $minLengthEntry));
298
299                 // Calulcate seek position
300                 $seekPosition = $minLengthEntry * $this->getConfigInstance()->getConfigEntry('file_stack_pre_allocate_count');
301                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] seekPosition=%s', __METHOD__, __LINE__, $seekPosition));
302
303                 // Now simply write a NUL there. This will pre-allocate the file.
304                 $this->writeData($seekPosition, chr(0));
305
306                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput(sprintf('[%s:%d:] EXIT!', __METHOD__, __LINE__));
307         }
308
309         /**
310          * Initializes this file-based stack.
311          *
312          * @param       $fileName       File name of this stack
313          * @return      void
314          */
315         protected function initFileStack ($fileName) {
316                 // Get a file i/o pointer instance
317                 $pointerInstance = ObjectFactory::createObjectByConfiguredName('file_raw_input_output_class', array($fileName));
318
319                 // Get iterator instance
320                 $iteratorInstance = ObjectFactory::createObjectByConfiguredName('file_io_iterator_class', array($pointerInstance));
321
322                 // Is the instance implementing the right interface?
323                 assert($iteratorInstance instanceof SeekableWritableFileIterator);
324
325                 // Set iterator here
326                 $this->setIteratorInstance($iteratorInstance);
327
328                 // Is the file's header initialized?
329                 if ($this->isFileHeaderInitialized()) {
330                         // Then load it
331                         $this->loadFileHeader();
332                 } else {
333                         // No, then create it (which may pre-allocate the stack)
334                         $this->createFileHeader();
335
336                         // And pre-allocate a bit
337                         $this->preAllocateFile();
338                 }
339         }
340
341         /**
342          * Initializes given stacker
343          *
344          * @param       $stackerName    Name of the stack
345          * @param       $forceReInit    Force re-initialization
346          * @return      void
347          * @throws      AlreadyInitializedStackerException      If the stack is already initialized
348          */
349         public function initStack ($stackerName, $forceReInit = FALSE) {
350                 // Is the stack already initialized?
351                 if (($forceReInit === FALSE) && ($this->isStackInitialized($stackerName))) {
352                         // Then throw the exception
353                         throw new AlreadyInitializedStackerException(array($this, $stackerName, $forceReInit), self::EXCEPTION_STACKER_ALREADY_INITIALIZED);
354                 } // END - if
355
356                 // Initialize the given stack
357                 $this->partialStub('stackerName=' . $stackerName . ',forceReInit=' . intval($forceReInit));
358         }
359
360         /**
361          * Checks whether the given stack is initialized (set in array $stackers)
362          *
363          * @param       $stackerName    Name of the stack
364          * @return      $isInitialized  Whether the stack is initialized
365          */
366         public function isStackInitialized ($stackerName) {
367                 // Is is there?
368                 $this->partialStub('stackerName=' . $stackerName);
369                 $isInitialized = TRUE;
370
371                 // Return result
372                 return $isInitialized;
373         }
374
375         /**
376          * Getter for size of given stack (array count)
377          *
378          * @param       $stackerName    Name of the stack
379          * @return      $count                  Size of stack (array count)
380          * @throws      NoStackerException      If given stack is missing
381          */
382         public function getStackCount ($stackerName) {
383                 // Is the stack not yet initialized?
384                 if (!$this->isStackInitialized($stackerName)) {
385                         // Throw an exception
386                         throw new NoStackerException(array($this, $stackerName), self::EXCEPTION_NO_STACKER_FOUND);
387                 } // END - if
388
389                 // Now, count the array of entries
390                 $this->partialStub('stackerName=' . $stackerName);
391                 $count = 0;
392
393                 // Return result
394                 return $count;
395         }
396
397         /**
398          * Adds a value to given stack
399          *
400          * @param       $stackerName    Name of the stack
401          * @param       $value                  Value to add to this stacker
402          * @return      void
403          * @throws      FullStackerException    Thrown if the stack is full
404          */
405         protected function addValue ($stackerName, $value) {
406                 // Is the stack not yet initialized or full?
407                 if (!$this->isStackInitialized($stackerName)) {
408                         // Then do it here
409                         $this->initStack($stackerName);
410                 } elseif ($this->isStackFull($stackerName)) {
411                         // Stacker is full
412                         throw new FullStackerException(array($this, $stackerName, $value), self::EXCEPTION_STACKER_IS_FULL);
413                 }
414
415                 // Now add the value to the stack
416                 $this->partialStub('stackerName=' . $stackerName . ',value[]=' . gettype($value));
417         }
418
419         /**
420          * Get last value from named stacker
421          *
422          * @param       $stackerName    Name of the stack
423          * @return      $value                  Value of last added value
424          * @throws      NoStackerException      If the named stacker was not found
425          * @throws      EmptyStackerException   If the named stacker is empty
426          */
427         protected function getLastValue ($stackerName) {
428                 // Is the stack not yet initialized or full?
429                 if (!$this->isStackInitialized($stackerName)) {
430                         // Throw an exception
431                         throw new NoStackerException(array($this, $stackerName), self::EXCEPTION_NO_STACKER_FOUND);
432                 } elseif ($this->isStackEmpty($stackerName)) {
433                         // Throw an exception
434                         throw new EmptyStackerException(array($this, $stackerName), self::EXCEPTION_STACKER_IS_EMPTY);
435                 }
436
437                 // Now get the last value
438                 $this->partialStub('stackerName=' . $stackerName);
439                 $value = NULL;
440
441                 // Return it
442                 return $value;
443         }
444
445         /**
446          * Get first value from named stacker
447          *
448          * @param       $stackerName    Name of the stack
449          * @return      $value                  Value of last added value
450          * @throws      NoStackerException      If the named stacker was not found
451          * @throws      EmptyStackerException   If the named stacker is empty
452          */
453         protected function getFirstValue ($stackerName) {
454                 // Is the stack not yet initialized or full?
455                 if (!$this->isStackInitialized($stackerName)) {
456                         // Throw an exception
457                         throw new NoStackerException(array($this, $stackerName), self::EXCEPTION_NO_STACKER_FOUND);
458                 } elseif ($this->isStackEmpty($stackerName)) {
459                         // Throw an exception
460                         throw new EmptyStackerException(array($this, $stackerName), self::EXCEPTION_STACKER_IS_EMPTY);
461                 }
462
463                 // Now get the first value
464                 $this->partialStub('stackerName=' . $stackerName);
465                 $value = NULL;
466
467                 // Return it
468                 return $value;
469         }
470
471         /**
472          * "Pops" last entry from stack
473          *
474          * @param       $stackerName    Name of the stack
475          * @return      $value                  Value "poped" from array
476          * @throws      NoStackerException      If the named stacker was not found
477          * @throws      EmptyStackerException   If the named stacker is empty
478          */
479         protected function popLast ($stackerName) {
480                 // Is the stack not yet initialized or full?
481                 if (!$this->isStackInitialized($stackerName)) {
482                         // Throw an exception
483                         throw new NoStackerException(array($this, $stackerName), self::EXCEPTION_NO_STACKER_FOUND);
484                 } elseif ($this->isStackEmpty($stackerName)) {
485                         // Throw an exception
486                         throw new EmptyStackerException(array($this, $stackerName), self::EXCEPTION_STACKER_IS_EMPTY);
487                 }
488
489                 // Now, remove the last entry, we don't care about the return value here, see elseif() block above
490                 $this->partialStub('stackerName=' . $stackerName);
491                 return NULL;
492         }
493
494         /**
495          * "Pops" first entry from stack
496          *
497          * @param       $stackerName    Name of the stack
498          * @return      $value                  Value "shifted" from array
499          * @throws      NoStackerException      If the named stacker was not found
500          * @throws      EmptyStackerException   If the named stacker is empty
501          */
502         protected function popFirst ($stackerName) {
503                 // Is the stack not yet initialized or full?
504                 if (!$this->isStackInitialized($stackerName)) {
505                         // Throw an exception
506                         throw new NoStackerException(array($this, $stackerName), self::EXCEPTION_NO_STACKER_FOUND);
507                 } elseif ($this->isStackEmpty($stackerName)) {
508                         // Throw an exception
509                         throw new EmptyStackerException(array($this, $stackerName), self::EXCEPTION_STACKER_IS_EMPTY);
510                 }
511
512                 // Now, remove the last entry, we don't care about the return value here, see elseif() block above
513                 $this->partialStub('stackerName=' . $stackerName);
514                 return NULL;
515         }
516 }
517
518 // [EOF]
519 ?>