879f19d0187b09700380ccd3bdf086dbcc883b4a
[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                         // No, then create it (which may pre-allocate the stack)
410                         $this->createFileHeader();
411
412                         // And pre-allocate a bit
413                         $this->preAllocateFile();
414                 } // END - if
415
416                 // Load the file header
417                 $this->readFileHeader();
418         }
419
420         /**
421          * Adds a value to given stack
422          *
423          * @param       $stackerName    Name of the stack
424          * @param       $value                  Value to add to this stacker
425          * @return      void
426          * @throws      FullStackerException    If the stack is full
427          */
428         protected function addValue ($stackerName, $value) {
429                 // Do some tests
430                 if ($this->isStackFull($stackerName)) {
431                         // Stacker is full
432                         throw new FullStackerException(array($this, $stackerName, $value), self::EXCEPTION_STACKER_IS_FULL);
433                 } // END - if
434
435                 // Now add the value to the stack
436                 $this->partialStub('stackerName=' . $stackerName . ',value[]=' . gettype($value));
437         }
438
439         /**
440          * Get last value from named stacker
441          *
442          * @param       $stackerName    Name of the stack
443          * @return      $value                  Value of last added value
444          * @throws      EmptyStackerException   If the stack is empty
445          */
446         protected function getLastValue ($stackerName) {
447                 // Is the stack not yet initialized or full?
448                 if ($this->isStackEmpty($stackerName)) {
449                         // Throw an exception
450                         throw new EmptyStackerException(array($this, $stackerName), self::EXCEPTION_STACKER_IS_EMPTY);
451                 } // END - if
452
453                 // Now get the last value
454                 $this->partialStub('stackerName=' . $stackerName);
455                 $value = NULL;
456
457                 // Return it
458                 return $value;
459         }
460
461         /**
462          * Get first value from named stacker
463          *
464          * @param       $stackerName    Name of the stack
465          * @return      $value                  Value of last added value
466          * @throws      EmptyStackerException   If the stack is empty
467          */
468         protected function getFirstValue ($stackerName) {
469                 // Is the stack not yet initialized or full?
470                 if ($this->isStackEmpty($stackerName)) {
471                         // Throw an exception
472                         throw new EmptyStackerException(array($this, $stackerName), self::EXCEPTION_STACKER_IS_EMPTY);
473                 } // END - if
474
475                 // Now get the first value
476                 $this->partialStub('stackerName=' . $stackerName);
477                 $value = NULL;
478
479                 // Return it
480                 return $value;
481         }
482
483         /**
484          * "Pops" last entry from stack
485          *
486          * @param       $stackerName    Name of the stack
487          * @return      $value                  Value "poped" from array
488          * @throws      EmptyStackerException   If the stack is empty
489          */
490         protected function popLast ($stackerName) {
491                 // Is the stack not yet initialized or full?
492                 if ($this->isStackEmpty($stackerName)) {
493                         // Throw an exception
494                         throw new EmptyStackerException(array($this, $stackerName), self::EXCEPTION_STACKER_IS_EMPTY);
495                 } // END - if
496
497                 // Now, remove the last entry, we don't care about the return value here, see elseif() block above
498                 $this->partialStub('stackerName=' . $stackerName);
499                 return NULL;
500         }
501
502         /**
503          * "Pops" first entry from stack
504          *
505          * @param       $stackerName    Name of the stack
506          * @return      $value                  Value "shifted" from array
507          * @throws      EmptyStackerException   If the named stacker is empty
508          */
509         protected function popFirst ($stackerName) {
510                 // Is the stack not yet initialized or full?
511                 if ($this->isStackEmpty($stackerName)) {
512                         // Throw an exception
513                         throw new EmptyStackerException(array($this, $stackerName), self::EXCEPTION_STACKER_IS_EMPTY);
514                 } // END - if
515
516                 // Now, remove the last entry, we don't care about the return value here, see elseif() block above
517                 $this->partialStub('stackerName=' . $stackerName);
518                 return NULL;
519         }
520
521         /**
522          * Checks whether the given stack is full
523          *
524          * @param       $stackerName    Name of the stack
525          * @return      $isFull                 Whether the stack is full
526          */
527         protected final function isStackFull ($stackerName) {
528                 // File-based stacks will only run full if the disk space is low.
529                 // @TODO Please implement this, returning FALSE
530                 $isFull = FALSE;
531
532                 // Return result
533                 return $isFull;
534         }
535
536         /**
537          * Checks whether the given stack is empty
538          *
539          * @param       $stackerName            Name of the stack
540          * @return      $isEmpty                        Whether the stack is empty
541          * @throws      NoStackerException      If given stack is missing
542          */
543         public final function isStackEmpty ($stackerName) {
544                 // So, is the stack empty?
545                 $isEmpty = (($this->getStackCount($stackerName)) == 0);
546
547                 // Return result
548                 return $isEmpty;
549         }
550
551         /**
552          * Initializes given stacker
553          *
554          * @param       $stackerName    Name of the stack
555          * @param       $forceReInit    Force re-initialization
556          * @return      void
557          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
558          */
559         public function initStack ($stackerName, $forceReInit = FALSE) {
560                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
561         }
562
563         /**
564          * Initializes all stacks
565          *
566          * @return      void
567          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
568          */
569         public function initStacks (array $stacks, $forceReInit = FALSE) {
570                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
571         }
572
573         /**
574          * Checks whether the given stack is initialized (set in array $stackers)
575          *
576          * @param       $stackerName    Name of the stack
577          * @return      $isInitialized  Whether the stack is initialized
578          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
579          */
580         public function isStackInitialized ($stackerName) {
581                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
582         }
583
584         /**
585          * Getter for size of given stack (array count)
586          *
587          * @param       $stackerName    Name of the stack
588          * @return      $count                  Size of stack (array count)
589          */
590         public function getStackCount ($stackerName) {
591                 // Now, count the array of entries
592                 $this->partialStub('stackerName=' . $stackerName);
593                 $count = 0;
594
595                 // Return result
596                 return $count;
597         }
598 }
599
600 // [EOF]
601 ?>