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