]> git.mxchange.org Git - hub.git/blob - application/hub/main/source/urls/class_CrawlerUploadedListUrlSource.php
dd5ff03bcd9db1761e5ecaa168adc93abde7042b
[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          * "Cached" CSV path
27          */
28         private $csvFilePath = '';
29
30         /**
31          * Last CSV file instance
32          */
33         private $lastCsvFileInstance = NULL;
34
35         /**
36          * Stack for pushing data from this clas to another
37          */
38         private $stackSourceInstance = NULL;
39
40         /**
41          * Stack name for a CSV file
42          */
43         const STACK_NAME_CSV_FILE = 'csv_file';
44
45         /**
46          * Stack name for a CSV entry
47          */
48         const STACK_NAME_CSV_ENTRY = 'csv_entry';
49
50         /**
51          * Size of crawl (CSV) entry which is an indexed array:
52          *
53          * 0 = URL to crawl
54          * 1 = Crawl depth of URL
55          * 2 = Crawl depth of linked URLs (same other host only)
56          */
57         const CRAWL_ENTRY_SIZE = 3;
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 has 3 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__ . ']: EXIT!');
180         }
181
182         /**
183          * Checks whether a CSV file has been loaded (added to the stack)
184          *
185          * @return      $isAdded        Whether a CSV file has been loaded
186          */
187         private function isCsvFileAdded () {
188                 // Check whether the stacker is not empty
189                 $isAdded = (($this->getStackSourceInstance()->isStackInitialized(self::STACK_NAME_CSV_FILE)) && (!$this->getStackSourceInstance()->isStackEmpty(self::STACK_NAME_CSV_FILE)));
190
191                 // Return the result
192                 return $isAdded;
193         }
194
195         /**
196          * Checks whether a CSV entry has been added to the stack
197          *
198          * @return      $isAdded        Whether a CSV entry has been added
199          */
200         private function isCsvEntryAdded () {
201                 // Check whether the stacker is not empty
202                 $isAdded = (($this->getStackSourceInstance()->isStackInitialized(self::STACK_NAME_CSV_ENTRY)) && (!$this->getStackSourceInstance()->isStackEmpty(self::STACK_NAME_CSV_ENTRY)));
203
204                 // Return the result
205                 return $isAdded;
206         }
207
208         /**
209          * Initializes the import of the CSV file which is being processed by other task
210          *
211          * @return      void
212          * @throws      NullPointerException    If lastCsvFileInstance is not set
213          */
214         private function addCsvFile () {
215                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE [' . __METHOD__ . ':' . __LINE__ . ']: CALLED!');
216
217                 // Is the instance set?
218                 if (is_null($this->lastCsvFileInstance)) {
219                         // This should not happen
220                         throw new NullPointerException($this, self::EXCEPTION_IS_NULL_POINTER);
221                 } // END - if
222
223                 // Stack this file
224                 $this->getStackSourceInstance()->pushNamed(self::STACK_NAME_CSV_FILE, $this->lastCsvFileInstance);
225
226                 // ... and mark it as "imported"
227                 array_push($this->csvFileImported, basename($this->lastCsvFileInstance->getFileName()));
228
229                 // ... and finally NULL it (to save some RAM)
230                 $this->lastCsvFileInstance = NULL;
231
232                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE [' . __METHOD__ . ':' . __LINE__ . ']: EXIT!');
233         }
234
235         /**
236          * Parses the next stacked CSV file by reading only one line from it. Then
237          * the read line is being validated and if found good being feed to the next
238          * stack. The file is removed from stack only if it has been fully parsed.
239          *
240          * @return      void
241          */
242         private function parseCsvFile () {
243                 // Debug message
244                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE [' . __METHOD__ . ':' . __LINE__ . ']: CALLED!');
245
246                 // Get next entry
247                 $csvFileInstance = $this->getStackSourceInstance()->popNamed(self::STACK_NAME_CSV_FILE);
248
249                 // Read full "CSV line"
250                 $csvData = $csvFileInstance->readCsvFileLine($this->columnSeparator);
251
252                 // Debug message
253                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE [' . __METHOD__ . ':' . __LINE__ . ']: csvData[' . gettype($csvData) . ']=' . print_r($csvData, TRUE));
254
255                 // Expect always an array
256                 assert(is_array($csvData));
257
258                 // Is the array empty?
259                 if (count($csvData) == 0) {
260                         // Debug message
261                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE [' . __METHOD__ . ':' . __LINE__ . ']: File ' . $csvFileInstance->getFileName() . ' has been fully read.');
262
263                         // Try to close it
264                         $csvFileInstance->closeFile();
265
266                         // This file as been fully read, so don't push it back on stack.
267                         return;
268                 } // END - if
269
270                 // ...  with 3 elements, later enhancements may accept more
271                 assert(count($csvData) == self::CRAWL_ENTRY_SIZE);
272
273                 /*
274                  * Push the file back on stack as it may contain more entries. This way
275                  * all files got rotated on stack which may improve crawler performance.
276                  */
277                 $this->getStackSourceInstance()->pushNamed(self::STACK_NAME_CSV_FILE, $csvFileInstance);
278
279                 // Push array on next stack
280                 $this->getStackSourceInstance()->pushNamed(self::STACK_NAME_CSV_ENTRY, $csvData);
281
282                 // Debug message
283                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE [' . __METHOD__ . ':' . __LINE__ . ']: EXIT!');
284         }
285
286         /**
287          * Parses the next stacked CSV entry.
288          *
289          * @return      void
290          */
291         private function parseCsvEntry () {
292                 // Debug message
293                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE [' . __METHOD__ . ':' . __LINE__ . ']: CALLED!');
294
295                 // Pop it from stack
296                 $csvData = $this->getStackSourceInstance()->popNamed(self::STACK_NAME_CSV_ENTRY);
297
298                 // Debug message
299                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE [' . __METHOD__ . ':' . __LINE__ . ']: csvData[' . gettype($csvData) . ']=' . print_r($csvData, TRUE));
300
301                 // It must have 3 elements (see method parseCsvFile() for details)
302                 assert(count($csvData) == self::CRAWL_ENTRY_SIZE);
303
304                 // Save it in crawler queue (which will enrich it with way more informations
305                 $this->saveCsvDataInCrawlerQueue($csvData);
306
307                 // Debug message
308                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('CRAWLER-SOURCE [' . __METHOD__ . ':' . __LINE__ . ']: EXIT!');
309         }
310
311         /**
312          * Getter for stackSourceInstance variable
313          *
314          * @return      $stackSourceInstance    An instance of an additional stack
315          */
316         public final function getStackSourceInstance () {
317                 return $this->stackSourceInstance;
318         }
319
320         /**
321          * Processes entries in the stack.
322          *
323          * @return      void
324          * @todo        ~20% done
325          */
326         public function processStack () {
327                 // Does the stack have some entries left?
328                 if ($this->isCsvEntryAdded()) {
329                         /*
330                          * A CSV file has been found and "imported" (added to stack). Now
331                          * the file can be read line by line and checked every one of it.
332                          */
333                         $this->parseCsvEntry();
334                 } elseif ($this->isCsvFileAdded()) {
335                         /*
336                          * A CSV file has been found and "imported" (added to stack). Now
337                          * the file can be read line by line and checked every one of it.
338                          */
339                         $this->parseCsvFile();
340                 } elseif ($this->isCsvFileFound()) {
341                         /*
342                          * A file containing an URL list is found. Please note the format is
343                          * CSV-like as you may wish to provide meta data such as crawl
344                          * depth, handling of 3rd-party URLs and such.
345                          */
346                         $this->addCsvFile();
347                 } elseif (!$this->isUrlStackEmpty()) {
348                         /*
349                          * Handle next entry. This method will be called very often, so need
350                          * to process more than one entry at a time.
351                          */
352                         $this->processNextEntry();
353                 }
354
355                 $this->partialStub('Please implement this method.');
356         }
357 }
358
359 // [EOF]
360 ?>