68352e163c2ac8429d55d3212ef23249b479d6c5
[jenirok] / src / common / dastelefonbuch.cpp
1 /*
2  * This file is part of Jenirok.
3  *
4  * Jenirok is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * Jenirok is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with Jenirok.  If not, see <http://www.gnu.org/licenses/>.
16  *
17  */
18
19 #include <QtCore/QDebug>
20 #include "dastelefonbuch.h"
21
22
23 DasTelefonbuch::DasTelefonbuch(QObject* parent): Source(parent)
24 {
25 }
26
27 DasTelefonbuch::~DasTelefonbuch()
28 {
29     abort();
30 }
31
32 void DasTelefonbuch::abort()
33 {
34     Source::abort();
35
36     for(int i = 0; i < pendingSearches_.size(); i++)
37     {
38         delete pendingSearches_[i];
39         pendingSearches_[i] = 0;
40     }
41
42     pendingSearches_.clear();
43 }
44
45 void DasTelefonbuch::search(Source::SearchDetails const& details)
46 {
47     resetTimeout();
48
49     SearchData* newData = new SearchData;
50     newData->details = details;
51     newData->currentPage = 1;
52     newData->finishedSearches = 0;
53
54     if(details.type == Source::BOTH)
55     {
56         newData->totalSearches = 2;
57         Source::SearchDetails tmpDetails = details;
58         tmpDetails.type = Source::PERSONS;
59         int id1 = sendQuery(tmpDetails, 1);
60         tmpDetails.type = Source::YELLOW_PAGES;
61         int id2 = sendQuery(tmpDetails, 1);
62         newData->searchIds.insert(id1);
63         newData->searchIds.insert(id2);
64     }
65     else
66     {
67         newData->totalSearches = 1;
68         int id = sendQuery(details, 1);
69         newData->searchIds.insert(id);
70     }
71
72     pendingSearches_.push_back(newData);
73 }
74
75 void DasTelefonbuch::handleHttpData(int id, QByteArray const& data)
76 {
77     QString strData(data);
78
79     for(int i = 0; i < pendingSearches_.size(); i++)
80     {
81         if(pendingSearches_.at(i) && pendingSearches_.at(i)->searchIds.find(id) !=
82             pendingSearches_.at(i)->searchIds.end())
83         {
84             addNumbers(pendingSearches_.at(i), strData, i);
85             break;
86         }
87     }
88 }
89
90 void DasTelefonbuch::handleHttpError(int id)
91 {
92     for(int i = 0; i < pendingSearches_.size(); i++)
93     {
94         if(pendingSearches_.at(i) && pendingSearches_.at(i)->searchIds.find(id) !=
95             pendingSearches_.at(i)->searchIds.end())
96         {
97
98             setError(Source::CONNECTION_FAILURE, http_.errorString());
99             emitRequestFinished(pendingSearches_.at(i), true, i);
100             break;
101         }
102     }
103 }
104
105 void DasTelefonbuch::addNumbers(SearchData* searchData,
106                            QString const& data,
107                            int index)
108 {
109     if(data.isEmpty())
110     {
111         qDebug() << "Server returned no data";
112         setError(CONNECTION_FAILURE, "Server returned no data");
113         emitRequestFinished(searchData, true, index);
114         return;
115     }
116
117     if(data.indexOf("<span>1&nbsp;Gesamttreffer</span>") > 0)
118     {
119         addOnlyNumber(searchData, data, index);
120         return;
121     }
122
123     int pos = 0;
124     static QRegExp rx("<table.*class=\"bg-01 entry(.*)</table>");
125     static QRegExp name("<div class=\"long hide\">(.*)</a>");
126     static QRegExp number("<td class=\"col4\">(.*)</td>");
127     static QRegExp address("<td class=\"col2\">(.*)</td>.*<td class=\"col3\">(.*)</td>");
128
129     rx.setMinimal(true);
130     name.setMinimal(true);
131     address.setMinimal(true);
132     number.setMinimal(true);
133
134     int maxResults = getMaxResults();
135
136     while((pos = rx.indexIn(data, pos)) != -1)
137     {
138         pos += rx.matchedLength();
139
140         if(searchData->results.size() >= maxResults)
141         {
142             break;
143         }
144
145         QString part = rx.cap(1);
146         Source::Result result;
147         QString nameStr;
148         QString numberStr;
149         QString streetStr;
150         QString cityStr;
151
152         if(name.indexIn(part) != -1)
153         {
154             nameStr = name.cap(1);
155         }
156
157         if(address.indexIn(part) != -1)
158         {
159             streetStr = address.cap(1);
160             cityStr = address.cap(2);
161         }
162
163         if(number.indexIn(part) != -1)
164         {
165             numberStr = number.cap(1);
166         }
167
168         if(formatResult(nameStr, numberStr, streetStr,
169                         cityStr, result))
170         {
171             emit resultAvailable(result, searchData->details);
172             searchData->results.push_back(result);
173         }
174
175     }
176
177     searchData->finishedSearches++;
178
179     if(searchData->results.size() >= maxResults)
180     {
181         emitRequestFinished(searchData, false, index);
182     }
183     else
184     {
185         /* TODO: Paging not implemented yet
186         if(data.indexOf("Neste") > 0)
187         {
188             searchData->currentPage++;
189             int id = sendQuery(searchData->details, searchData->currentPage);
190             searchData->searchIds.insert(id);
191         }
192         */
193         if (false)
194         {
195         }
196         else if(searchData->finishedSearches >= searchData->totalSearches)
197         {
198             emitRequestFinished(searchData, false, index);
199         }
200     }
201
202 }
203
204 void DasTelefonbuch::addOnlyNumber(SearchData* searchData,
205                               QString const& data,
206                               int index)
207 {
208     static QRegExp name("<div class=\"long hide\">(.*) (.*)</a>");
209     static QRegExp number("<td class=\"col4\">(.*)</td>");
210     static QRegExp address("<td class=\"col2\">(.*)</td>.*<td class=\"col3\">(.*)</td>");
211     name.setMinimal(true);
212     number.setMinimal(true);
213     address.setMinimal(true);
214
215     Source::Result result;
216
217     QString nameStr;
218     QString numberStr;
219     QString streetStr;
220     QString cityStr;
221
222     if(name.indexIn(data) != -1)
223     {
224         nameStr = name.cap(2).simplified() + name.cap(1).simplified();
225         nameStr.replace("\n","");
226     }
227
228     if(number.indexIn(data) != -1)
229     {
230         numberStr = number.cap(1);
231     }
232
233     if(address.indexIn(data) != -1)
234     {
235         streetStr = address.cap(1);
236         cityStr = address.cap(2);
237     }
238
239     if(formatResult(nameStr, numberStr, streetStr,
240                     cityStr, result))
241     {
242         searchData->results.push_back(result);
243         emit resultAvailable(result, searchData->details);
244     }
245
246     emitRequestFinished(searchData, false, index);
247 }
248
249 bool DasTelefonbuch::formatResult(QString& name, QString& number,
250                              QString& street, QString& city,
251                              Source::Result& result)
252 {
253     name = stripTags(name);
254     name = htmlEntityDecode(name);
255     result.name = name.trimmed();
256     number = stripTags(number);
257     number = cleanUpNumber(number);
258     result.number = number.trimmed();
259     street = stripTags(street);
260     street = htmlEntityDecode(street);
261     city = stripTags(city);
262     city = htmlEntityDecode(city);
263     result.street = street.trimmed();
264     result.city = city.trimmed();
265     result.country = "Germany";
266
267     if(!result.name.isEmpty() && (!getFindNumber() || !result.number.isEmpty()))
268     {
269         return true;
270     }
271     return false;
272 }
273
274 void DasTelefonbuch::emitRequestFinished(SearchData* data,
275                                     bool error, int index)
276 {
277     QVector<Source::Result> results = data->results;
278     Source::SearchDetails details = data->details;
279
280     emit requestFinished(results, details, error);
281     delete pendingSearches_[index];
282     pendingSearches_[index] = 0;
283     pendingSearches_.removeAt(index);
284 }
285
286 int DasTelefonbuch::sendQuery(Source::SearchDetails const& details,
287                          int page)
288 {
289     Q_UNUSED(page);
290
291     QUrl url("http://www.dastelefonbuch.de/");
292
293     //Pretending we are a Firefox-Plugin allows a simpler query-String
294     url.addQueryItem("sourceid","Mozilla-search");
295     //But we will need to give a proper User-Agent-String...see below
296
297     url.addQueryItem("cmd","search");
298
299     QString query = details.query;
300
301     if(!details.location.isEmpty())
302     {
303         query += "+" + details.location;
304     }
305
306     //Query is added as "kw"
307     url.addQueryItem("kw", query);
308
309     /* TODO No differentiation between personal and professional entries yet
310     if(details.type == Source::YELLOW_PAGES)
311     {
312         url.addQueryItem("t", "c");
313     }
314     else
315     {
316         url.addQueryItem("t", "p");
317     }
318    */
319
320     /* TODO No multi-page results yet.
321     if(page > 1)
322     {
323         url.addQueryItem("p", QString::number(page));
324     }
325     */
326
327     fixUrl(url);
328
329     //Remember, we are firefox, therefore setting User-Agent here...
330     QHttpRequestHeader header("GET", url.encodedPath()+ '?' + url.encodedQuery());
331
332     header.setValue("User-Agent","Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.0.10) Gecko/2009042316 Firefox/3.0.10");
333     header.setValue("Host", url.encodedHost());
334
335     http_.setHost(url.host(), url.port(80));
336
337     return http_.request(header);
338 }
339
340 void DasTelefonbuch::getSearchTypes(QList<SearchType>& types) const
341 {
342     types.clear();
343     types.push_back(BOTH);
344 }