]> git.mxchange.org Git - hub.git/blob - application/hub/main/source/urls/class_CrawlerUploadedListUrlSource.php
Updated 'core'.
[hub.git] / application / hub / main / source / urls / class_CrawlerUploadedListUrlSource.php
1 <?php
2 /**
3  * A UploadedList URL source class for crawlers
4  *
5  * @author              Roland Haeder <webmaster@ship-simu.org>
6  * @version             0.0.0
7  * @copyright   Copyright (c) 2014 Crawler 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 CrawlerUploadedListUrlSource extends BaseUrlSource implements UrlSource, Registerable {
25         /**
26          * Stack name for a CSV file
27          */
28         const STACK_NAME_CSV_FILE = 'csv_file';
29
30         /**
31          * Stack name for a CSV entry
32          */
33         const STACK_NAME_CSV_ENTRY = 'csv_entry';
34
35         /**
36          * Size of crawl (CSV) entry which is an indexed array:
37          *
38          * 0 = URL to crawl
39          * 1 = Crawl depth of URL
40          * 2 = Crawl depth of linked URLs (same other host only)
41          */
42         const CRAWL_ENTRY_SIZE = 3;
43
44         /**
45          * "Cached" CSV path
46          */
47         private $csvFilePath = '';
48
49         /**
50          * Last CSV file instance
51          */
52         private $lastCsvFileInstance = NULL;
53
54         /**
55          * Stack for pushing data from this clas to another
56          */
57         private $stackSourceInstance = NULL;
58
59         /**
60          * "Imported" CSV files
61          */
62         private $csvFileImported = array();
63
64         /**
65          * "Cached" separator for columns
66          */
67         private $columnSeparator = '';
68
69         /**
70          * Protected constructor
71          *
72          * @return      void
73          */
74         protected function __construct () {
75                 // Call parent constructor
76                 parent::__construct(__CLASS__);
77
78                 // "Cache" CSV path for faster usage
79                 $this->csvFilePath = $this->getConfigInstance()->getConfigEntry('base_path') . '/' . $this->getConfigInstance()->getConfigEntry('crawler_csv_file_path');
80
81                 // Initialize directory instance
82                 $directoryInstance = ObjectFactory::createObjectByConfiguredName('directory_class', array($this->csvFilePath));
83
84                 // Set it here
85                 $this->setDirectoryInstance($directoryInstance);
86
87                 // Init stack instance
88                 $this->stackSourceInstance = ObjectFactory::createObjectByConfiguredName('crawler_uploaded_list_url_source_stack_class');
89
90                 // Init stacks
91                 $this->getStackSourceInstance()->initStack(self::STACK_NAME_CSV_FILE);
92                 $this->getStackSourceInstance()->initStack(self::STACK_NAME_CSV_ENTRY);
93
94                 // "Cache" column separator
95                 $this->columnSeparator = $this->getConfigInstance()->getConfigEntry('crawler_url_list_column_separator');
96         }
97
98         /**
99          * Checks whether a CSV file is found in configured path
100          *
101          * @return      $isFound        Whether a CSV file is found
102          */
103         private function isCsvFileFound () {
104                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE [' . __METHOD__ . ':' . __LINE__ . ']: CALLED!');
105
106                 // Is it valid?
107                 if (!$this->getDirectoryInstance()->getDirectoryIteratorInstance()->valid()) {
108                         // Rewind to start
109                         $this->getDirectoryInstance()->getDirectoryIteratorInstance()->rewind();
110                 } // END - if
111
112                 // Read next entry
113                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE [' . __METHOD__ . ':' . __LINE__ . ']: this->csvFileImported=' . print_r($this->csvFileImported, TRUE));
114                 $directoryEntry = $this->getDirectoryInstance()->readDirectoryExcept(array_merge(array('.htaccess', '.', '..'), $this->csvFileImported));
115
116                 // Debug message
117                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE[' . __METHOD__ . ':' . __LINE__ . '] directoryEntry(' . strlen($directoryEntry) . ')=' . $directoryEntry);
118
119                 // Is it empty or wrong file extension?
120                 if ((empty($directoryEntry)) || (substr($directoryEntry, -4, 4) != '.csv')) {
121                         // Skip further processing
122                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE[' . __METHOD__ . ':' . __LINE__ . '] directoryEntry(' . strlen($directoryEntry) . ')=' . $directoryEntry . ' - SKIPPED!');
123                         return FALSE;
124                 } // END - if
125
126                 // Initialize CSV file instance
127                 $this->lastCsvFileInstance = ObjectFactory::createObjectByConfiguredName('csv_input_file_class', array($this->csvFilePath . '/' . $directoryEntry));
128
129                 // Debug message
130                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE [' . __METHOD__ . ':' . __LINE__ . '] directoryEntry(' . strlen($directoryEntry) . ')=' . $directoryEntry . ' - Instance created - EXIT!');
131
132                 // Found an entry
133                 return TRUE;
134         }
135
136         /**
137          * Creates an instance of this class
138          *
139          * @return      $sourceInstance         An instance of a Source class
140          */
141         public final static function createCrawlerUploadedListUrlSource () {
142                 // Get new instance
143                 $sourceInstance = new CrawlerUploadedListUrlSource();
144
145                 // Init source
146                 $sourceInstance->initSource('crawler', 'uploaded_list');
147
148                 // Return the prepared instance
149                 return $sourceInstance;
150         }
151
152         /**
153          * Enriches and saves the given CSV entry (array) in the assigned
154          * file-based stack. To such entry a lot more informations are added, such
155          * as which files shall be crawled and many more.
156          *
157          * @param       $csvData        Array with data from a CSV file
158          * @return      void
159          */
160         private function saveCsvDataInCrawlerQueue (array $csvData) {
161                 // Debug message
162                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE [' . __METHOD__ . ':' . __LINE__ . ']: csvData()=' . count($csvData) . ' - CALLED!');
163
164                 // The array must have a fixed amount of elements, later enhancements may accept more
165                 assert(count($csvData) == self::CRAWL_ENTRY_SIZE);
166
167                 /*
168                  * First converted the indexed array into an assoziative array. Don't
169                  * forget to expand this array as well when you want to add another
170                  * column to the CSV file.
171                  */
172                 $csvArray = array(
173                         self::CRAWL_JOB_ARRAY_START_URL      => $csvData[0],
174                         self::CRAWL_JOB_ARRAY_DEPTH          => $csvData[1],
175                         self::CRAWL_JOB_ARRAY_EXTERNAL_DEPTH => $csvData[2]
176                 );
177
178                 // Debug message
179                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE [' . __METHOD__ . ':' . __LINE__ . ']: csvArray()=' . count($csvArray) . ' - BEFORE!');
180
181                 // Then add more data to it
182                 $this->enrichCrawlerQueueData($csvArray);
183
184                 // Debug message
185                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE [' . __METHOD__ . ':' . __LINE__ . ']: csvArray()=' . count($csvArray) . ' - AFTER!');
186
187                 /*
188                  * Then enqueue it in the file stack. The local crawler "task" will
189                  * then pick this up.
190                  */
191                 $this->enqueueInFileStack($csvArray);
192
193                 // Debug message
194                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE [' . __METHOD__ . ':' . __LINE__ . ']: EXIT!');
195         }
196
197         /**
198          * Checks whether a CSV file has been loaded (added to the stack)
199          *
200          * @return      $isAdded        Whether a CSV file has been loaded
201          */
202         private function isCsvFileAdded () {
203                 // Check whether the stacker is not empty
204                 $isAdded = (($this->getStackSourceInstance()->isStackInitialized(self::STACK_NAME_CSV_FILE)) && (!$this->getStackSourceInstance()->isStackEmpty(self::STACK_NAME_CSV_FILE)));
205
206                 // Return the result
207                 return $isAdded;
208         }
209
210         /**
211          * Checks whether a CSV entry has been added to the stack
212          *
213          * @return      $isAdded        Whether a CSV entry has been added
214          */
215         private function isCsvEntryAdded () {
216                 // Check whether the stacker is not empty
217                 $isAdded = (($this->getStackSourceInstance()->isStackInitialized(self::STACK_NAME_CSV_ENTRY)) && (!$this->getStackSourceInstance()->isStackEmpty(self::STACK_NAME_CSV_ENTRY)));
218
219                 // Return the result
220                 return $isAdded;
221         }
222
223         /**
224          * Initializes the import of the CSV file which is being processed by other task
225          *
226          * @return      void
227          * @throws      NullPointerException    If lastCsvFileInstance is not set
228          */
229         private function addCsvFile () {
230                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE [' . __METHOD__ . ':' . __LINE__ . ']: CALLED!');
231
232                 // Is the instance set?
233                 if (is_null($this->lastCsvFileInstance)) {
234                         // This should not happen
235                         throw new NullPointerException($this, self::EXCEPTION_IS_NULL_POINTER);
236                 } // END - if
237
238                 // Stack this file
239                 $this->getStackSourceInstance()->pushNamed(self::STACK_NAME_CSV_FILE, $this->lastCsvFileInstance);
240
241                 // ... and mark it as "imported"
242                 array_push($this->csvFileImported, basename($this->lastCsvFileInstance->getFileName()));
243
244                 // ... and finally NULL it (to save some RAM)
245                 $this->lastCsvFileInstance = NULL;
246
247                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE [' . __METHOD__ . ':' . __LINE__ . ']: EXIT!');
248         }
249
250         /**
251          * Parses the next stacked CSV file by reading only one line from it. Then
252          * the read line is being validated and if found good being feed to the next
253          * stack. The file is removed from stack only if it has been fully parsed.
254          *
255          * @return      void
256          */
257         private function parseCsvFile () {
258                 // Debug message
259                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE [' . __METHOD__ . ':' . __LINE__ . ']: CALLED!');
260
261                 // Get next entry
262                 $csvFileInstance = $this->getStackSourceInstance()->popNamed(self::STACK_NAME_CSV_FILE);
263
264                 // Read full "CSV line"
265                 $csvData = $csvFileInstance->readCsvFileLine($this->columnSeparator);
266
267                 // Debug message
268                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE [' . __METHOD__ . ':' . __LINE__ . ']: csvData[' . gettype($csvData) . ']=' . print_r($csvData, TRUE));
269
270                 // Expect always an array
271                 assert(is_array($csvData));
272
273                 // Is the array empty?
274                 if (count($csvData) == 0) {
275                         // Debug message
276                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE [' . __METHOD__ . ':' . __LINE__ . ']: File ' . $csvFileInstance->getFileName() . ' has been fully read.');
277
278                         // Try to close it by actually unsetting (destructing) it
279                         unset($csvFileInstance);
280
281                         // This file as been fully read, so don't push it back on stack.
282                         return;
283                 } // END - if
284
285                 // ...  with a fixed amount of elements, later enhancements may accept more
286                 assert(count($csvData) == self::CRAWL_ENTRY_SIZE);
287
288                 /*
289                  * Push the file back on stack as it may contain more entries. This way
290                  * all files got rotated on stack which may improve crawler performance.
291                  */
292                 $this->getStackSourceInstance()->pushNamed(self::STACK_NAME_CSV_FILE, $csvFileInstance);
293
294                 // Push array on next stack
295                 $this->getStackSourceInstance()->pushNamed(self::STACK_NAME_CSV_ENTRY, $csvData);
296
297                 // Debug message
298                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE [' . __METHOD__ . ':' . __LINE__ . ']: EXIT!');
299         }
300
301         /**
302          * Parses the next stacked CSV entry.
303          *
304          * @return      void
305          */
306         private function parseCsvEntry () {
307                 // Debug message
308                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE [' . __METHOD__ . ':' . __LINE__ . ']: CALLED!');
309
310                 // Pop it from stack
311                 $csvData = $this->getStackSourceInstance()->popNamed(self::STACK_NAME_CSV_ENTRY);
312
313                 // Debug message
314                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE [' . __METHOD__ . ':' . __LINE__ . ']: csvData[' . gettype($csvData) . ']=' . print_r($csvData, TRUE));
315
316                 // It must have a fixed amount of elements (see method parseCsvFile() for details)
317                 assert(count($csvData) == self::CRAWL_ENTRY_SIZE);
318
319                 // Save it in crawler queue (which will enrich it with way more informations
320                 $this->saveCsvDataInCrawlerQueue($csvData);
321
322                 // Debug message
323                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE [' . __METHOD__ . ':' . __LINE__ . ']: EXIT!');
324         }
325
326         /**
327          * Getter for stackSourceInstance variable
328          *
329          * @return      $stackSourceInstance    An instance of an additional stack
330          */
331         public final function getStackSourceInstance () {
332                 return $this->stackSourceInstance;
333         }
334
335         /**
336          * Fills the URL stack with new entries from source
337          *
338          * @return      void
339          * @todo        ~40% done
340          */
341         public function fillUrlStack () {
342                 // Does the stack have some entries left?
343                 if ($this->isCsvEntryAdded()) {
344                         /*
345                          * A CSV file has been found and "imported" (added to stack). Now
346                          * the file can be read line by line and checked every one of it.
347                          */
348                         $this->parseCsvEntry();
349                 } elseif ($this->isCsvFileAdded()) {
350                         /*
351                          * A CSV file has been found and "imported" (added to stack). Now
352                          * the file can be read line by line and checked every one of it.
353                          */
354                         $this->parseCsvFile();
355                 } elseif ($this->isCsvFileFound()) {
356                         /*
357                          * A file containing an URL list is found. Please note the format is
358                          * CSV-like as you may wish to provide meta data such as crawl
359                          * depth, handling of 3rd-party URLs and such.
360                          */
361                         $this->addCsvFile();
362                 }
363
364                 $this->partialStub('Please implement this method.');
365         }
366 }
367
368 // [EOF]
369 ?>