1 /* ============================================================
2 * QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader
3 * Copyright (C) 2011-2020 QuiteRSS Team <quiterssteam@gmail.com>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 * ============================================================ */
18 #include "parseobject.h"
19
20 #include "mainapplication.h"
21 #include "database.h"
22 #include "VersionNo.h"
23 #include "common.h"
24
25 #include <QDebug>
26 #include <QDesktopServices>
27 #include <QTextDocumentFragment>
28 #if defined(Q_OS_WIN)
29 #include <windows.h>
30 #endif
31 #include <qzregexp.h>
32
ParseObject(QObject * parent)33 ParseObject::ParseObject(QObject *parent)
34 : QObject(parent)
35 {
36 setObjectName("parseObject_");
37
38 db_ = Database::connection("secondConnection");
39
40 parseTimer_ = new QTimer(this);
41 parseTimer_->setSingleShot(true);
42 parseTimer_->setInterval(10);
43 connect(parseTimer_, SIGNAL(timeout()), this, SLOT(getQueuedXml()),
44 Qt::QueuedConnection);
45
46 connect(this, SIGNAL(signalReadyParse(QByteArray,int,QDateTime,QString)),
47 SLOT(slotParse(QByteArray,int,QDateTime,QString)));
48 }
49
~ParseObject()50 ParseObject::~ParseObject()
51 {
52
53 }
54
disconnectObjects()55 void ParseObject::disconnectObjects()
56 {
57 disconnect(this);
58 }
59
60 /** @brief Queueing xml-data
61 *----------------------------------------------------------------------------*/
parseXml(QByteArray data,int feedId,QDateTime dtReply,QString codecName)62 void ParseObject::parseXml(QByteArray data, int feedId,
63 QDateTime dtReply, QString codecName)
64 {
65 idsQueue_.enqueue(feedId);
66 xmlsQueue_.enqueue(data);
67 dtReadyQueue_.enqueue(dtReply);
68 codecNameQueue_.enqueue(codecName);
69 qDebug() << "xmlsQueue_ <<" << feedId << "count=" << xmlsQueue_.count();
70
71 if (!parseTimer_->isActive())
72 parseTimer_->start();
73 }
74
75 /** @brief Process xml-data queue
76 *----------------------------------------------------------------------------*/
getQueuedXml()77 void ParseObject::getQueuedXml()
78 {
79 if (!mutex_.tryLock())
80 return;
81
82 if (idsQueue_.count()) {
83 int feedId = idsQueue_.dequeue();
84 QByteArray currentXml_ = xmlsQueue_.dequeue();
85 QDateTime currentDtReady_ = dtReadyQueue_.dequeue();
86 QString currentCodecName_ = codecNameQueue_.dequeue();
87 qDebug() << "xmlsQueue_ >>" << feedId << "count=" << xmlsQueue_.count();
88
89 emit signalReadyParse(currentXml_, feedId, currentDtReady_, currentCodecName_);
90 }
91
92 mutex_.unlock();
93
94 if (idsQueue_.count())
95 parseTimer_->start();
96 }
97
98 /** @brief Parse xml-data
99 *----------------------------------------------------------------------------*/
slotParse(const QByteArray & xmlData,const int & feedId,const QDateTime & dtReply,const QString & codecName)100 void ParseObject::slotParse(const QByteArray &xmlData, const int &feedId,
101 const QDateTime &dtReply, const QString &codecName)
102 {
103 if (mainApp->isSaveDataLastFeed()) {
104 QFile file(mainApp->dataDir() + "/lastfeed.dat");
105 file.open(QIODevice::WriteOnly);
106 file.write(xmlData);
107 file.close();
108 }
109
110 qDebug() << "=================== parseXml:start ============================";
111
112 db_.transaction();
113
114 // extract feed id, duplicate news mode and date to avoid from feed table
115 parseFeedId_ = feedId;
116 QString feedUrl;
117 duplicateNewsMode_ = false;
118 addSingleNewsAnyDate_ = false;
119 avoidedOldSingleNews_ = false;
120 avoidedOldSingleNewsDate_ = QDate::currentDate();
121 QSqlQuery q(db_);
122 q.setForwardOnly(true);
123 q.exec(QString("SELECT duplicateNewsMode, xmlUrl, addSingleNewsAnyDateOn, avoidedOldSingleNewsDateOn, avoidedOldSingleNewsDate"
124 " FROM feeds WHERE id=='%1'").arg(parseFeedId_));
125 if (q.first()) {
126 duplicateNewsMode_ = q.value(0).toBool();
127 feedUrl = q.value(1).toString();
128 addSingleNewsAnyDate_ = q.value(2).toBool();
129 avoidedOldSingleNews_ = q.value(3).toBool();
130 avoidedOldSingleNewsDate_ = q.value(4).toDate();
131 }
132
133 // id not found (ex. feed deleted while updating)
134 if (feedUrl.isEmpty()) {
135 qWarning() << QString("Feed with id = '%1' not found").arg(parseFeedId_);
136 emit signalFinishUpdate(parseFeedId_, false, 0, "0");
137 db_.commit();
138 return;
139 }
140
141 qDebug() << QString("Feed '%1' found with id = %2").arg(feedUrl).arg(parseFeedId_);
142
143 // actually parsing
144 feedChanged_ = false;
145 lastBuildDate_ = dtReply;
146
147 bool codecOk = false;
148 QString convertData(xmlData);
149 QString feedType;
150 QDomDocument doc;
151 QString errorStr;
152 int errorLine;
153 int errorColumn;
154
155 QzRegExp rx("encoding=\"([^\"]+)", Qt::CaseInsensitive);
156 int pos = rx.indexIn(xmlData);
157 if (pos == -1) {
158 rx.setPattern("encoding='([^']+)");
159 pos = rx.indexIn(xmlData);
160 }
161 if (pos > -1) {
162 QString codecNameT = rx.cap(1);
163 qDebug() << "Codec name (1):" << codecNameT;
164 QTextCodec *codec = QTextCodec::codecForName(codecNameT.toUtf8());
165 if (codec) {
166 convertData = codec->toUnicode(xmlData);
167 } else {
168 qWarning() << "Codec not found (1): " << codecNameT << feedUrl;
169 if (codecNameT.contains("us-ascii", Qt::CaseInsensitive)) {
170 QString str(xmlData);
171 convertData = str.remove(rx.cap(0)+"\"");
172 }
173 }
174 } else {
175 if (!codecName.isEmpty()) {
176 qDebug() << "Codec name (2):" << codecName;
177 QTextCodec *codec = QTextCodec::codecForName(codecName.toUtf8());
178 if (codec) {
179 convertData = codec->toUnicode(xmlData);
180 codecOk = true;
181 } else {
182 qWarning() << "Codec not found (2): " << codecName << feedUrl;
183 }
184 }
185 if (!codecOk) {
186 codecOk = false;
187 QStringList codecNameList;
188 codecNameList << "UTF-8" << "Windows-1251" << "KOI8-R" << "KOI8-U"
189 << "ISO 8859-5" << "IBM 866";
190 foreach (QString codecNameT, codecNameList) {
191 QTextCodec *codec = QTextCodec::codecForName(codecNameT.toUtf8());
192 if (codec && codec->canEncode(xmlData)) {
193 qDebug() << "Codec name (3):" << codecNameT;
194 convertData = codec->toUnicode(xmlData);
195 codecOk = true;
196 break;
197 }
198 }
199 if (!codecOk) {
200 convertData = QString::fromLocal8Bit(xmlData);
201 }
202 }
203 }
204
205 if (!doc.setContent(convertData, false, &errorStr, &errorLine, &errorColumn)) {
206 qWarning() << QString("Parse data error (2): url %1, id %2, line %3, column %4: %5").
207 arg(feedUrl).arg(parseFeedId_).
208 arg(errorLine).arg(errorColumn).arg(errorStr);
209 } else {
210 QDomElement rootElem = doc.documentElement();
211 feedType = rootElem.tagName();
212 qDebug() << "Feed type: " << feedType;
213
214 q.exec(QString("SELECT id, guid, title, published, link_href FROM news WHERE feedId='%1'").
215 arg(parseFeedId_));
216 if (q.lastError().isValid()) {
217 qWarning() << __PRETTY_FUNCTION__ << __LINE__
218 << "q.lastError(): " << q.lastError().text();
219 }
220 else {
221 while (q.next()) {
222 QString str = q.value(2).toString();
223 titleList_.append(str);
224
225 str = q.value(1).toString();
226 guidList_.append(str);
227
228 str = q.value(3).toString();
229 publishedList_.append(str);
230 str = q.value(4).toString();
231 linkList_.append(str);
232 }
233 }
234 q.finish();
235
236 if (feedType == "feed") {
237 parseAtom(feedUrl, doc);
238 } else if ((feedType == "rss") || (feedType == "rdf:RDF")) {
239 parseRss(feedUrl, doc);
240 }
241
242 guidList_.clear();
243 titleList_.clear();
244 publishedList_.clear();
245 linkList_.clear();
246 }
247
248 // Set feed update time and receive data from server time
249 QString updated = QLocale::c().toString(QDateTime::currentDateTimeUtc(),
250 "yyyy-MM-ddTHH:mm:ss");
251 QString lastBuildDate = lastBuildDate_.toString(Qt::ISODate);
252 q.prepare("UPDATE feeds SET updated=?, lastBuildDate=?, status=0 WHERE id=?");
253 q.addBindValue(updated);
254 q.addBindValue(lastBuildDate);
255 q.addBindValue(parseFeedId_);
256 q.exec();
257
258 int newCount = 0;
259 if (feedChanged_) {
260 runUserFilter(parseFeedId_);
261 newCount = recountFeedCounts(parseFeedId_, feedUrl, updated, lastBuildDate);
262 }
263
264 q.finish();
265 db_.commit();
266
267 emit signalFinishUpdate(parseFeedId_, feedChanged_, newCount, "0");
268 qDebug() << "=================== parseXml:finish ===========================";
269 }
270
parseAtom(const QString & feedUrl,const QDomDocument & doc)271 void ParseObject::parseAtom(const QString &feedUrl, const QDomDocument &doc)
272 {
273 QDomElement rootElem = doc.documentElement();
274 FeedItemStruct feedItem;
275
276 feedItem.linkBase = rootElem.attribute("xml:base");
277 feedItem.title = toPlainText(rootElem.namedItem("title").toElement().text());
278 feedItem.description = rootElem.namedItem("subtitle").toElement().text();
279 feedItem.updated = rootElem.namedItem("updated").toElement().text();
280 feedItem.updated = parseDate(feedItem.updated, feedUrl);
281 QDomElement authorElem = rootElem.namedItem("author").toElement();
282 if (!authorElem.isNull()) {
283 feedItem.author = toPlainText(authorElem.namedItem("name").toElement().text());
284 if (feedItem.author.isEmpty()) feedItem.author = toPlainText(authorElem.text());
285 feedItem.authorUri = authorElem.namedItem("uri").toElement().text();
286 feedItem.authorEmail = authorElem.namedItem("email").toElement().text();
287 }
288 feedItem.language = rootElem.namedItem("language").toElement().text();
289 QDomNodeList linksList = rootElem.elementsByTagName("link");
290 for (int j = 0; j < linksList.size(); j++) {
291 if (linksList.at(j).toElement().attribute("rel") == "alternate") {
292 feedItem.link = linksList.at(j).toElement().attribute("href");
293 break;
294 }
295 }
296 if (feedItem.link.isEmpty()) {
297 for (int j = 0; j < linksList.size(); j++) {
298 if (!(linksList.at(j).toElement().attribute("rel") == "self")) {
299 feedItem.link = linksList.at(j).toElement().attribute("href");
300 break;
301 }
302 }
303 }
304
305 if (QUrl(feedItem.link).host().isEmpty() || (QUrl(feedItem.link).host().indexOf('.')) == -1) {
306 if (!feedItem.linkBase.isEmpty() && !QUrl(feedItem.linkBase).host().isEmpty())
307 feedItem.link = QUrl(feedItem.linkBase).scheme() % "://" % QUrl(feedItem.linkBase).host();
308 else
309 feedItem.link = QUrl(feedUrl).scheme() % "://" % QUrl(feedUrl).host();
310 }
311 if (feedItem.linkBase.isEmpty() && !QUrl(feedItem.link).host().isEmpty())
312 feedItem.linkBase = QUrl(feedItem.link).scheme() % "://" % QUrl(feedItem.link).host();
313 if (QUrl(feedItem.link).host().isEmpty())
314 feedItem.link = feedItem.linkBase + feedItem.link;
315 feedItem.link = toPlainText(feedItem.link);
316 QUrl url = QUrl(feedItem.link);
317 if (url.scheme().isEmpty())
318 url.setScheme(QUrl(feedUrl).scheme());
319 feedItem.link = url.toString();
320
321 QSqlQuery q(db_);
322 q.setForwardOnly(true);
323 QString qStr ("UPDATE feeds "
324 "SET title=?, description=?, htmlUrl=?, "
325 "author_name=?, author_email=?, "
326 "author_uri=?, pubdate=?, language=? "
327 "WHERE id==?");
328 q.prepare(qStr);
329 q.addBindValue(feedItem.title);
330 q.addBindValue(feedItem.description);
331 q.addBindValue(feedItem.link);
332 q.addBindValue(feedItem.author);
333 q.addBindValue(feedItem.authorEmail);
334 q.addBindValue(feedItem.authorUri);
335 q.addBindValue(feedItem.updated);
336 q.addBindValue(feedItem.language);
337 q.addBindValue(parseFeedId_);
338 q.exec();
339
340 QDomNodeList newsList = doc.elementsByTagName("entry");
341 for (int i = 0; i < newsList.size(); i++) {
342 NewsItemStruct newsItem;
343 newsItem.id = newsList.item(i).namedItem("id").toElement().text();
344 newsItem.title = toPlainText(newsList.item(i).namedItem("title").toElement().text());
345 newsItem.updated = newsList.item(i).namedItem("published").toElement().text();
346 if (newsItem.updated.isEmpty())
347 newsItem.updated = newsList.item(i).namedItem("updated").toElement().text();
348 newsItem.updated = parseDate(newsItem.updated, feedUrl);
349 QDomElement authorElem = newsList.item(i).namedItem("author").toElement();
350 if (!authorElem.isNull()) {
351 newsItem.author = toPlainText(authorElem.namedItem("name").toElement().text());
352 if (newsItem.author.isEmpty()) newsItem.author = toPlainText(authorElem.text());
353 newsItem.authorUri = authorElem.namedItem("uri").toElement().text();
354 newsItem.authorEmail = authorElem.namedItem("email").toElement().text();
355 }
356
357 newsItem.description = newsList.item(i).namedItem("summary").toElement().text();
358 QDomNode nodeSummary = newsList.item(i).namedItem("summary");
359 if (!nodeSummary.isNull() && newsItem.description.isEmpty()) {
360 QTextStream in(&newsItem.description);
361 nodeSummary.save(in, 0);
362 }
363 QDomNode nodeContent = newsList.item(i).namedItem("content");
364 if (nodeContent.toElement().attribute("type") == "xhtml") {
365 QTextStream in(&newsItem.content);
366 nodeContent.save(in, 0);
367 } else {
368 newsItem.content = nodeContent.toElement().text();
369 }
370 QString imgUrl = newsList.item(i).namedItem("media:thumbnail").toElement().attribute("url");
371 QString community = getCommunity(newsList.item(i).namedItem("media:community"));
372 nodeContent = newsList.item(i).namedItem("media:group");
373 if (!nodeContent.isNull()) {
374 QString description = nodeContent.namedItem("media:description").toElement().text();
375 if (description.length() > newsItem.content.length())
376 newsItem.content = description;
377 newsItem.content = fromPlainText(newsItem.content);
378 if (imgUrl.isEmpty())
379 imgUrl = nodeContent.namedItem("media:thumbnail").toElement().attribute("url");
380 if (community.isEmpty())
381 community = getCommunity(nodeContent.namedItem("media:community"));
382 }
383 if (!(newsItem.content.isEmpty() ||
384 (newsItem.description.length() > newsItem.content.length()))) {
385 newsItem.description = newsItem.content;
386 }
387 newsItem.content.clear();
388 if (!imgUrl.isEmpty()) {
389 newsItem.description = "<p class=\"description\">" + newsItem.description + "</p>";
390 newsItem.description += "<img src=\"" + imgUrl + "\" alt=\"image\"/>";
391 }
392 if (!community.isEmpty())
393 newsItem.description += community;
394
395 QDomNodeList categoryElem = newsList.item(i).toElement().elementsByTagName("category");
396 for (int j = 0; j < categoryElem.size(); j++) {
397 if (!newsItem.category.isEmpty()) newsItem.category.append(", ");
398 QString category = categoryElem.at(j).toElement().attribute("label");
399 if (category.isEmpty())
400 category = categoryElem.at(j).toElement().attribute("term");
401 newsItem.category.append(toPlainText(category));
402 }
403 QDomElement enclosureElem = newsList.item(i).namedItem("enclosure").toElement();
404 newsItem.eUrl = enclosureElem.attribute("url");
405 newsItem.eType = enclosureElem.attribute("type");
406 newsItem.eLength = enclosureElem.attribute("length");
407 QDomNodeList linksList = newsList.item(i).toElement().elementsByTagName("link");
408 for (int j = 0; j < linksList.size(); j++) {
409 if (linksList.at(j).toElement().attribute("type") == "text/html") {
410 if (linksList.at(j).toElement().attribute("rel") == "self")
411 newsItem.link = linksList.at(j).toElement().attribute("href");
412 if (linksList.at(j).toElement().attribute("rel") == "alternate")
413 newsItem.linkAlternate = linksList.at(j).toElement().attribute("href");
414 if (linksList.at(j).toElement().attribute("rel") == "replies")
415 newsItem.comments = linksList.at(j).toElement().attribute("href");
416 } else if (newsItem.linkAlternate.isEmpty()) {
417 if (linksList.at(j).toElement().attribute("rel") == "alternate")
418 newsItem.linkAlternate = linksList.at(j).toElement().attribute("href");
419 }
420 }
421 for (int j = 0; j < linksList.size(); j++) {
422 if (newsItem.linkAlternate.isEmpty()) {
423 if (!(linksList.at(j).toElement().attribute("rel") == "self")) {
424 newsItem.linkAlternate = linksList.at(j).toElement().attribute("href");
425 break;
426 }
427 }
428 }
429
430 if (!newsItem.link.isEmpty() && QUrl(newsItem.link).host().isEmpty())
431 newsItem.link = feedItem.linkBase + newsItem.link;
432 newsItem.link = toPlainText(newsItem.link);
433 if (!newsItem.linkAlternate.isEmpty() && QUrl(newsItem.linkAlternate).host().isEmpty())
434 newsItem.linkAlternate = feedItem.linkBase + newsItem.linkAlternate;
435 newsItem.linkAlternate = toPlainText(newsItem.linkAlternate);
436 if (newsItem.link.isEmpty()) {
437 newsItem.link = newsItem.linkAlternate;
438 newsItem.linkAlternate.clear();
439 }
440 url = QUrl(newsItem.link);
441 if (url.scheme().isEmpty())
442 url.setScheme(QUrl(feedUrl).scheme());
443 newsItem.link = url.toString();
444
445 addAtomNewsIntoBase(&newsItem);
446 }
447 }
448
addAtomNewsIntoBase(NewsItemStruct * newsItem)449 void ParseObject::addAtomNewsIntoBase(NewsItemStruct *newsItem)
450 {
451 Common::sleep(5);
452
453 // search news duplicates in base
454 QSqlQuery q(db_);
455 q.setForwardOnly(true);
456 QString qStr;
457 qDebug() << "atomId:" << newsItem->id;
458 qDebug() << "title:" << newsItem->title;
459 qDebug() << "published:" << newsItem->updated;
460
461 bool isDuplicate = false;
462 for (int i = 0; i < guidList_.count(); ++i) {
463 if (!newsItem->id.isEmpty()) { // search by guid if present
464 if (guidList_.at(i) == newsItem->id) {
465 if (duplicateNewsMode_) { // autodelete duplicate news enabled
466 isDuplicate = true;
467 } else { // autodelete dupl. news disabled
468 if (!newsItem->updated.isEmpty()) { // search by pubDate if present
469 if (publishedList_.at(i) == newsItem->updated)
470 isDuplicate = true;
471 } else { // ... or by title
472 if (!newsItem->title.isEmpty() && (titleList_.at(i) == newsItem->title))
473 isDuplicate = true;
474 }
475 }
476 }
477 } else { // guid is absent
478 if (!newsItem->updated.isEmpty()) { // search by pubDate if present
479 if (publishedList_.at(i) == newsItem->updated)
480 isDuplicate = true;
481 } else { // ... or by title
482 if (!newsItem->title.isEmpty() && (titleList_.at(i) == newsItem->title))
483 isDuplicate = true;
484 }
485 }
486 if (isDuplicate) break;
487 }
488
489 // Verify old news before a date to avoid adding them to base
490 bool isOld = false;
491 QDateTime pubDate_ = QDateTime::fromString(newsItem->updated, "yyyy-MM-ddTHH:mm:ss");
492 QDateTime avoidedDate_ = QDateTime(mainApp->mainWindow()->avoidedOldNewsDate_);
493 if (!addSingleNewsAnyDate_) { //
494 if (avoidedOldSingleNews_ ) { // avoid adding old single news
495 if (QDateTime(avoidedOldSingleNewsDate_) > pubDate_)
496 isOld = true;
497 } else if (mainApp->mainWindow()->avoidOldNews_ && avoidedDate_ > pubDate_) { // avoid adding old news
498 isOld = true;
499 }
500 }
501
502 // if duplicates not found and is old news, add them into base
503 if (!isDuplicate && !isOld) {
504 bool read = false;
505 if (mainApp->mainWindow()->markIdenticalNewsRead_) {
506 q.prepare("SELECT id FROM news WHERE title LIKE :title AND feedId!=:id");
507 q.bindValue(":id", parseFeedId_);
508 q.bindValue(":title", newsItem->title);
509 q.exec();
510 if (q.first()) read = true;
511 }
512
513 qStr = QString("INSERT INTO news("
514 "feedId, description, content, guid, title, author_name, "
515 "author_uri, author_email, published, received, "
516 "link_href, link_alternate, category, comments, "
517 "enclosure_url, enclosure_type, enclosure_length, new, read) "
518 "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
519 q.prepare(qStr);
520 q.addBindValue(parseFeedId_);
521 q.addBindValue(newsItem->description);
522 q.addBindValue(newsItem->content);
523 q.addBindValue(newsItem->id);
524 q.addBindValue(newsItem->title);
525 q.addBindValue(newsItem->author);
526 q.addBindValue(newsItem->authorUri);
527 q.addBindValue(newsItem->authorEmail);
528 QString updated = newsItem->updated;
529 if (updated.isEmpty())
530 updated = QDateTime::currentDateTimeUtc().toString(Qt::ISODate);
531 q.addBindValue(updated);
532 q.addBindValue(QDateTime::currentDateTime().toString(Qt::ISODate));
533 q.addBindValue(newsItem->link);
534 q.addBindValue(newsItem->linkAlternate);
535 q.addBindValue(newsItem->category);
536 q.addBindValue(newsItem->comments);
537 q.addBindValue(newsItem->eUrl);
538 q.addBindValue(newsItem->eType);
539 q.addBindValue(newsItem->eLength);
540 q.addBindValue(read ? 0 : 1);
541 q.addBindValue(read ? 2 : 0);
542 if (!q.exec()) {
543 qWarning() << __PRETTY_FUNCTION__ << __LINE__
544 << "q.lastError(): " << q.lastError().text();
545 }
546 q.finish();
547 qDebug() << "q.exec(" << q.lastQuery() << ")";
548 qDebug() << " " << parseFeedId_;
549 qDebug() << " " << newsItem->description;
550 qDebug() << " " << newsItem->content;
551 qDebug() << " " << newsItem->id;
552 qDebug() << " " << newsItem->title;
553 qDebug() << " " << newsItem->author;
554 qDebug() << " " << newsItem->authorUri;
555 qDebug() << " " << newsItem->authorEmail;
556 qDebug() << " " << newsItem->updated;
557 qDebug() << " " << QDateTime::currentDateTime().toString();
558 qDebug() << " " << newsItem->link;
559 qDebug() << " " << newsItem->linkAlternate;
560 qDebug() << " " << newsItem->category;
561 qDebug() << " " << newsItem->comments;
562 qDebug() << " " << newsItem->eUrl;
563 qDebug() << " " << newsItem->eType;
564 qDebug() << " " << newsItem->eLength;
565
566 if (lastBuildDate_ < QDateTime::fromString(newsItem->updated, Qt::ISODate))
567 lastBuildDate_ = QDateTime::fromString(newsItem->updated, Qt::ISODate);
568 feedChanged_ = true;
569 }
570 }
571
parseRss(const QString & feedUrl,const QDomDocument & doc)572 void ParseObject::parseRss(const QString &feedUrl, const QDomDocument &doc)
573 {
574 QDomNode channel = doc.documentElement().namedItem("channel");
575 if (channel.isNull())
576 channel = doc.documentElement().namedItem("rss:channel");
577 FeedItemStruct feedItem;
578
579 feedItem.title = toPlainText(channel.namedItem("title").toElement().text());
580 if (feedItem.title.isEmpty())
581 feedItem.title = toPlainText(channel.namedItem("rss:title").toElement().text());
582 feedItem.description = channel.namedItem("description").toElement().text();
583 if (feedItem.description.isEmpty())
584 feedItem.description = toPlainText(channel.namedItem("rss:description").toElement().text());
585 feedItem.link = toPlainText(channel.namedItem("link").toElement().text());
586 if (feedItem.link.isEmpty())
587 feedItem.link = toPlainText(channel.namedItem("rss:link").toElement().text());
588 QUrl url = QUrl(feedItem.link);
589 if (url.host().isEmpty())
590 url.setHost(QUrl(feedUrl).host());
591 if (url.scheme().isEmpty())
592 url.setScheme(QUrl(feedUrl).scheme());
593 feedItem.link = url.toString();
594
595 feedItem.updated = channel.namedItem("pubDate").toElement().text();
596 if (feedItem.updated.isEmpty())
597 feedItem.updated = channel.namedItem("pubdate").toElement().text();
598 feedItem.updated = parseDate(feedItem.updated, feedUrl);
599 feedItem.author = toPlainText(channel.namedItem("author").toElement().text());
600 feedItem.language = channel.namedItem("language").toElement().text();
601 if (feedItem.language.isEmpty())
602 feedItem.language = channel.namedItem("dc:language").toElement().text();
603
604 QSqlQuery q(db_);
605 q.setForwardOnly(true);
606 QString qStr("UPDATE feeds "
607 "SET title=?, description=?, htmlUrl=?, "
608 "author_name=?, pubdate=?, language=? "
609 "WHERE id==?");
610 q.prepare(qStr);
611 q.addBindValue(feedItem.title);
612 q.addBindValue(feedItem.description);
613 q.addBindValue(feedItem.link);
614 q.addBindValue(feedItem.author);
615 q.addBindValue(feedItem.updated);
616 q.addBindValue(feedItem.language);
617 q.addBindValue(parseFeedId_);
618 q.exec();
619
620 QDomNodeList newsList = doc.elementsByTagName("item");
621 if (newsList.isEmpty())
622 newsList = doc.elementsByTagName("rss:item");
623 for (int i = 0; i < newsList.size(); i++) {
624 NewsItemStruct newsItem;
625 newsItem.id = newsList.item(i).namedItem("guid").toElement().text();
626 newsItem.title = toPlainText(newsList.item(i).namedItem("title").toElement().text());
627 if (newsItem.title.isEmpty())
628 newsItem.title = toPlainText(newsList.item(i).namedItem("rss:title").toElement().text());
629 newsItem.updated = newsList.item(i).namedItem("pubDate").toElement().text();
630 if (newsItem.updated.isEmpty())
631 newsItem.updated = newsList.item(i).namedItem("pubdate").toElement().text();
632 if (newsItem.updated.isEmpty())
633 newsItem.updated = newsList.item(i).namedItem("dc:date").toElement().text();
634 newsItem.updated = parseDate(newsItem.updated, feedUrl);
635 newsItem.author = toPlainText(newsList.item(i).namedItem("author").toElement().text());
636 if (newsItem.author.isEmpty())
637 newsItem.author = toPlainText(newsList.item(i).namedItem("dc:creator").toElement().text());
638 newsItem.link = toPlainText(newsList.item(i).namedItem("link").toElement().text());
639 if (newsItem.link.isEmpty()) {
640 newsItem.link = toPlainText(newsList.item(i).namedItem("rss:link").toElement().text());
641 if (newsItem.link.isEmpty()) {
642 if (newsList.item(i).namedItem("guid").toElement().attribute("isPermaLink") == "true")
643 newsItem.link = newsItem.id;
644 }
645 }
646 url = QUrl(newsItem.link);
647 if (url.host().isEmpty())
648 url.setHost(QUrl(feedUrl).host());
649 if (url.scheme().isEmpty())
650 url.setScheme(QUrl(feedUrl).scheme());
651 newsItem.link = url.toString();
652
653 newsItem.description = newsList.item(i).namedItem("description").toElement().text();
654 QDomNode nodeSummary = newsList.item(i).namedItem("description");
655 if (!nodeSummary.isNull() && newsItem.description.isEmpty()) {
656 QTextStream in(&newsItem.description);
657 nodeSummary.save(in, 0);
658 }
659 newsItem.content = newsList.item(i).namedItem("content:encoded").toElement().text();
660 QDomNode nodeContent = newsList.item(i).namedItem("content:encoded");
661 if (!nodeContent.isNull() && newsItem.content.isEmpty()) {
662 QTextStream in(&newsItem.content);
663 nodeContent.save(in, 0);
664 }
665 QString imgUrl = newsList.item(i).namedItem("media:thumbnail").toElement().attribute("url");
666 QString community = getCommunity(newsList.item(i).namedItem("media:community"));
667 nodeContent = newsList.item(i).namedItem("media:group");
668 if (!nodeContent.isNull()) {
669 QString description = nodeContent.namedItem("media:description").toElement().text();
670 if (description.length() > newsItem.content.length())
671 newsItem.content = description;
672 newsItem.content = fromPlainText(newsItem.content);
673 if (imgUrl.isEmpty())
674 imgUrl = nodeContent.namedItem("media:thumbnail").toElement().attribute("url");
675 if (community.isEmpty())
676 community = getCommunity(nodeContent.namedItem("media:community"));
677 }
678 if (!(newsItem.content.isEmpty() ||
679 (newsItem.description.length() > newsItem.content.length()))) {
680 newsItem.description = newsItem.content;
681 }
682 newsItem.content.clear();
683 if (!imgUrl.isEmpty()) {
684 newsItem.description = "<p class=\"description\">" + newsItem.description + "</p>";
685 newsItem.description += "<img src=\"" + imgUrl + "\" alt=\"image\"/>";
686 }
687 if (!community.isEmpty())
688 newsItem.description += community;
689
690 QDomNodeList categoryElem = newsList.item(i).toElement().elementsByTagName("category");
691 for (int j = 0; j < categoryElem.size(); j++) {
692 if (!newsItem.category.isEmpty()) newsItem.category.append(", ");
693 newsItem.category.append(toPlainText(categoryElem.at(j).toElement().text()));
694 }
695 newsItem.comments = newsList.item(i).namedItem("comments").toElement().text();
696 QDomElement enclosureElem = newsList.item(i).namedItem("enclosure").toElement();
697 newsItem.eUrl = enclosureElem.attribute("url");
698 newsItem.eType = enclosureElem.attribute("type");
699 newsItem.eLength = enclosureElem.attribute("length");
700
701 if (newsItem.title.isEmpty()) {
702 newsItem.title = toPlainText(newsItem.description);
703 if (newsItem.title.size() > 50) {
704 newsItem.title.resize(50);
705 newsItem.title = newsItem.title % "...";
706 }
707 }
708
709 addRssNewsIntoBase(&newsItem);
710 }
711 }
712
addRssNewsIntoBase(NewsItemStruct * newsItem)713 void ParseObject::addRssNewsIntoBase(NewsItemStruct *newsItem)
714 {
715 Common::sleep(5);
716
717 // search news duplicates in base
718 QSqlQuery q(db_);
719 q.setForwardOnly(true);
720 QString qStr;
721 qDebug() << "guid: " << newsItem->id;
722 qDebug() << "link_href:" << newsItem->link;
723 qDebug() << "title:" << newsItem->title;
724 qDebug() << "published:" << newsItem->updated;
725
726 bool isDuplicate = false;
727 for (int i = 0; i < guidList_.count(); ++i) {
728 if (!newsItem->id.isEmpty()) { // search by guid if present
729 if (guidList_.at(i) == newsItem->id) {
730 if (!newsItem->updated.isEmpty()) { // search by pubDate if present
731 if (!duplicateNewsMode_) {
732 if (publishedList_.at(i) == newsItem->updated)
733 isDuplicate = true;
734 }
735 else {
736 isDuplicate = true;
737 }
738 } else { // ... or by title
739 if (!newsItem->title.isEmpty() && (titleList_.at(i) == newsItem->title))
740 isDuplicate = true;
741 }
742 }
743 if (!isDuplicate) {
744 if (!newsItem->updated.isEmpty()) {
745 if ((publishedList_.at(i) == newsItem->updated) &&
746 (titleList_.at(i) == newsItem->title)) {
747 isDuplicate = true;
748 }
749 }
750 }
751 }
752 else if (!newsItem->link.isEmpty()) { // search by link_href
753 if (linkList_.at(i) == newsItem->link) {
754 if (!newsItem->updated.isEmpty()) { // search by pubDate if present
755 if (!duplicateNewsMode_) {
756 if (publishedList_.at(i) == newsItem->updated)
757 isDuplicate = true;
758 }
759 else {
760 isDuplicate = true;
761 }
762 } else { // ... or by title
763 if (!newsItem->title.isEmpty() && (titleList_.at(i) == newsItem->title))
764 isDuplicate = true;
765 }
766 }
767 if (!isDuplicate) {
768 if (!newsItem->updated.isEmpty()) {
769 if ((publishedList_.at(i) == newsItem->updated) &&
770 (titleList_.at(i) == newsItem->title)) {
771 isDuplicate = true;
772 }
773 }
774 }
775 }
776 else { // guid is absent
777 if (!newsItem->updated.isEmpty()) { // search by pubDate if present
778 if (!duplicateNewsMode_) {
779 if (publishedList_.at(i) == newsItem->updated)
780 isDuplicate = true;
781 }
782 else {
783 isDuplicate = true;
784 }
785 } else { // ... or by title
786 if (!newsItem->title.isEmpty() && (titleList_.at(i) == newsItem->title))
787 isDuplicate = true;
788 }
789 if (!isDuplicate) {
790 if (!newsItem->updated.isEmpty()) {
791 if ((publishedList_.at(i) == newsItem->updated) &&
792 (titleList_.at(i) == newsItem->title)) {
793 isDuplicate = true;
794 }
795 }
796 }
797 }
798 if (isDuplicate) break;
799 }
800
801 // Verify old news before a date to avoid adding them to base
802 bool isOld = false;
803 QDateTime pubDate_ = QDateTime::fromString(newsItem->updated, "yyyy-MM-ddTHH:mm:ss");
804 QDateTime avoidedDate_ = QDateTime(mainApp->mainWindow()->avoidedOldNewsDate_);
805 if (!addSingleNewsAnyDate_) { //
806 if (avoidedOldSingleNews_ ) { // avoid adding old single news
807 if (QDateTime(avoidedOldSingleNewsDate_) > pubDate_)
808 isOld = true;
809 } else if (mainApp->mainWindow()->avoidOldNews_ && avoidedDate_ > pubDate_) { // avoid adding old news
810 isOld = true;
811 }
812 }
813
814 // if duplicates not found And old news, add them into base
815 if (!isDuplicate && !isOld) {
816 bool read = false;
817 if (mainApp->mainWindow()->markIdenticalNewsRead_) {
818 q.prepare("SELECT id FROM news WHERE title LIKE :title AND feedId!=:id");
819 q.bindValue(":id", parseFeedId_);
820 q.bindValue(":title", newsItem->title);
821 q.exec();
822 if (q.first()) read = true;
823 }
824
825 qStr = QString("INSERT INTO news("
826 "feedId, description, content, guid, title, author_name, "
827 "published, received, link_href, category, comments, "
828 "enclosure_url, enclosure_type, enclosure_length, new, read) "
829 "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
830 q.prepare(qStr);
831 q.addBindValue(parseFeedId_);
832 q.addBindValue(newsItem->description);
833 q.addBindValue(newsItem->content);
834 q.addBindValue(newsItem->id);
835 q.addBindValue(newsItem->title);
836 q.addBindValue(newsItem->author);
837 QString updated = newsItem->updated;
838 if (updated.isEmpty())
839 updated = QDateTime::currentDateTimeUtc().toString(Qt::ISODate);
840 q.addBindValue(updated);
841 q.addBindValue(QDateTime::currentDateTime().toString(Qt::ISODate));
842 q.addBindValue(newsItem->link);
843 q.addBindValue(newsItem->category);
844 q.addBindValue(newsItem->comments);
845 q.addBindValue(newsItem->eUrl);
846 q.addBindValue(newsItem->eType);
847 q.addBindValue(newsItem->eLength);
848 q.addBindValue(read ? 0 : 1);
849 q.addBindValue(read ? 2 : 0);
850 if (!q.exec()) {
851 qWarning() << __PRETTY_FUNCTION__ << __LINE__
852 << "q.lastError(): " << q.lastError().text();
853 }
854 q.finish();
855 qDebug() << "q.exec(" << q.lastQuery() << ")";
856 qDebug() << " " << parseFeedId_;
857 qDebug() << " " << newsItem->description;
858 qDebug() << " " << newsItem->content;
859 qDebug() << " " << newsItem->id;
860 qDebug() << " " << newsItem->title;
861 qDebug() << " " << newsItem->author;
862 qDebug() << " " << newsItem->updated;
863 qDebug() << " " << QDateTime::currentDateTime().toString();
864 qDebug() << " " << newsItem->link;
865 qDebug() << " " << newsItem->category;
866 qDebug() << " " << newsItem->comments;
867 qDebug() << " " << newsItem->eUrl;
868 qDebug() << " " << newsItem->eType;
869 qDebug() << " " << newsItem->eLength;
870
871 if (lastBuildDate_ < QDateTime::fromString(newsItem->updated, Qt::ISODate))
872 lastBuildDate_ = QDateTime::fromString(newsItem->updated, Qt::ISODate);
873 feedChanged_ = true;
874 }
875 }
876
toPlainText(const QString & text)877 QString ParseObject::toPlainText(const QString &text)
878 {
879 return QTextDocumentFragment::fromHtml(text).toPlainText().simplified();
880 }
881
fromPlainText(QString text)882 QString ParseObject::fromPlainText(QString text)
883 {
884 text = text.replace("\r\n", "<br>");
885 text = text.replace("\n", "<br>");
886 return text;
887 }
888
getCommunity(const QDomNode & nodeContent)889 QString ParseObject::getCommunity(const QDomNode &nodeContent)
890 {
891 QString community;
892 if (!nodeContent.isNull()) {
893 QString count = nodeContent.namedItem("media:starRating").toElement().attribute("count");
894 QString average = nodeContent.namedItem("media:starRating").toElement().attribute("average");
895 QString min = nodeContent.namedItem("media:starRating").toElement().attribute("min");
896 QString max = nodeContent.namedItem("media:starRating").toElement().attribute("max");
897 QString views = nodeContent.namedItem("media:statistics").toElement().attribute("views");
898 if (!count.isEmpty())
899 community = QString("Count: %1, average: %2, min: %3, max: %4<br>").
900 arg(count).arg(average).arg(min).arg(max);
901 if (!views.isEmpty())
902 community += QString("Views: %1").arg(views);
903 if (!community.isEmpty())
904 community = "<p><i>" + community + "</i></p>";
905 }
906 return community;
907 }
908
909 /** @brief Date/time string parsing
910 *----------------------------------------------------------------------------*/
parseDate(const QString & dateString,const QString & urlString)911 QString ParseObject::parseDate(const QString &dateString, const QString &urlString)
912 {
913 QDateTime dt;
914 QString temp;
915 QString timeZone;
916
917 if (dateString.isEmpty()) return QString();
918
919 QDateTime dtLocalTime = QDateTime::currentDateTime();
920 QDateTime dtUTC = QDateTime(dtLocalTime.date(), dtLocalTime.time(), Qt::UTC);
921 int nTimeShift = dtLocalTime.secsTo(dtUTC)/3600;
922
923 QString ds = dateString.simplified();
924 QLocale locale(QLocale::C);
925
926 if (ds.indexOf(',') != -1) {
927 ds = ds.remove(0, ds.indexOf(',')+1).simplified();
928 }
929
930 for (int i = 0; i < 2; i++, locale = QLocale::system()) {
931 temp = ds.left(23);
932 timeZone = ds.mid(temp.length(), 3);
933 if (timeZone.isEmpty()) timeZone = QString::number(nTimeShift);
934 dt = locale.toDateTime(temp, "yyyy-MM-ddTHH:mm:ss.z");
935 if (dt.isValid()) return locale.toString(dt.addSecs(timeZone.toInt() * -3600), "yyyy-MM-ddTHH:mm:ss");
936
937 temp = ds.left(19);
938 timeZone = ds.mid(temp.length(), 3);
939 if (timeZone.isEmpty()) timeZone = QString::number(nTimeShift);
940 dt = locale.toDateTime(temp, "yyyy-MM-ddTHH:mm:ss");
941 if (dt.isValid()) return locale.toString(dt.addSecs(timeZone.toInt() * -3600), "yyyy-MM-ddTHH:mm:ss");
942
943 temp = ds.left(23);
944 timeZone = ds.mid(temp.length()+1, 3);
945 if (timeZone.isEmpty()) timeZone = QString::number(nTimeShift);
946 dt = locale.toDateTime(temp, "yyyy-MM-dd HH:mm:ss.z");
947 if (dt.isValid()) return locale.toString(dt.addSecs(timeZone.toInt() * -3600), "yyyy-MM-ddTHH:mm:ss");
948
949 temp = ds.left(19);
950 timeZone = ds.mid(temp.length()+1, 3);
951 if (timeZone.isEmpty()) timeZone = QString::number(nTimeShift);
952 dt = locale.toDateTime(temp, "yyyy-MM-dd HH:mm:ss");
953 if (dt.isValid()) return locale.toString(dt.addSecs(timeZone.toInt() * -3600), "yyyy-MM-ddTHH:mm:ss");
954
955 temp = ds.left(20);
956 timeZone = ds.mid(temp.length()+1, 3);
957 if (timeZone.contains("EDT"))
958 timeZone="-4";
959 if (timeZone.isEmpty()) timeZone = QString::number(nTimeShift);
960 dt = locale.toDateTime(temp, "dd MMM yyyy HH:mm:ss");
961 if (dt.isValid()) return locale.toString(dt.addSecs(timeZone.toInt() * -3600), "yyyy-MM-ddTHH:mm:ss");
962
963 temp = ds.left(19);
964 timeZone = ds.mid(temp.length()+1, 3);
965 if (timeZone.isEmpty()) timeZone = QString::number(nTimeShift);
966 dt = locale.toDateTime(temp, "d MMM yyyy HH:mm:ss");
967 if (dt.isValid()) return locale.toString(dt.addSecs(timeZone.toInt() * -3600), "yyyy-MM-ddTHH:mm:ss");
968
969 temp = ds.left(11);
970 timeZone = ds.mid(temp.length()+1, 3);
971 if (timeZone.isEmpty()) timeZone = QString::number(nTimeShift);
972 dt = locale.toDateTime(temp, "dd MMM yyyy");
973 if (dt.isValid()) return locale.toString(dt.addSecs(timeZone.toInt() * -3600), "yyyy-MM-ddTHH:mm:ss");
974
975 temp = ds.left(10);
976 timeZone = ds.mid(temp.length()+1, 3);
977 if (timeZone.isEmpty()) timeZone = QString::number(nTimeShift);
978 dt = locale.toDateTime(temp, "d MMM yyyy");
979 if (dt.isValid()) return locale.toString(dt.addSecs(timeZone.toInt() * -3600), "yyyy-MM-ddTHH:mm:ss");
980
981 temp = ds.left(10);
982 timeZone = ds.mid(temp.length(), 3);
983 if (timeZone.isEmpty()) timeZone = QString::number(nTimeShift);
984 dt = locale.toDateTime(temp, "yyyy-MM-dd");
985 if (dt.isValid()) return locale.toString(dt.addSecs(timeZone.toInt() * -3600), "yyyy-MM-ddTHH:mm:ss");
986
987 // @HACK(arhohryakov:2012.01.01):
988 // "dd MMM yy HH:mm:ss" format doesn/t parse automatically
989 // Reformat it to "dd MMM yyyy HH:mm:ss"
990 QString temp2;
991 temp2 = ds; // save ds for output in case of error
992 if (70 < ds.mid(7, 2).toInt()) temp2.insert(7, "19");
993 else temp2.insert(7, "20");
994 temp = temp2.left(20);
995 timeZone = ds.mid(temp.length()+1-2, 3); // "-2", cause 2 symbols inserted
996 if (timeZone.isEmpty()) timeZone = QString::number(nTimeShift);
997 dt = locale.toDateTime(temp, "dd MMM yyyy HH:mm:ss");
998 if (dt.isValid()) return locale.toString(dt.addSecs(timeZone.toInt() * -3600), "yyyy-MM-ddTHH:mm:ss");
999 }
1000
1001 qDebug() << __LINE__ << "parseDate: error with" << dateString << urlString;
1002 return QString();
1003 }
1004
1005 /** @brief Apply user filters
1006 * @param feedId - Feed Id
1007 * @param filterId - Id of particular filter
1008 *---------------------------------------------------------------------------*/
runUserFilter(int feedId,int filterId)1009 void ParseObject::runUserFilter(int feedId, int filterId)
1010 {
1011 QSqlQuery q(db_);
1012 bool isAllFilters = true;
1013
1014 if (filterId != -1) {
1015 isAllFilters = false;
1016 q.exec(QString("SELECT enable, type FROM filters WHERE id='%1' AND feeds LIKE '%,%2,%'").
1017 arg(filterId).arg(feedId));
1018 } else {
1019 q.exec(QString("SELECT enable, type, id FROM filters WHERE feeds LIKE '%,%1,%' ORDER BY num").
1020 arg(feedId));
1021 }
1022
1023 while (q.next()) {
1024 if ((q.value(0).toInt() == 0) && isAllFilters) continue;
1025
1026 if (isAllFilters)
1027 filterId = q.value(2).toInt();
1028 int filterType = q.value(1).toInt();
1029
1030 QString qStr("UPDATE news SET");
1031 QString qStr1;
1032 QString qStr2;
1033 QString whereStr;
1034 QList<int> idLabelsList;
1035 QStringList soundList;
1036 QStringList colorList;
1037
1038 QSqlQuery q1(db_);
1039 q1.exec(QString("SELECT action, params FROM filterActions "
1040 "WHERE idFilter=='%1'").arg(filterId));
1041 while (q1.next()) {
1042 switch (q1.value(0).toInt()) {
1043 case 0: // action -> Mark news as read
1044 if (!qStr1.isNull()) qStr1.append(",");
1045 qStr1.append(" new=0, read=2");
1046 break;
1047 case 1: // action -> Add star
1048 if (!qStr1.isNull()) qStr1.append(",");
1049 qStr1.append(" starred=1");
1050 break;
1051 case 2: // action -> Delete
1052 if (!qStr1.isNull()) qStr1.append(",");
1053 qStr1.append(" new=0, read=2, deleted=1, ");
1054 qStr1.append(QString("deleteDate='%1'").
1055 arg(QDateTime::currentDateTime().toString(Qt::ISODate)));
1056 break;
1057 case 3: // action -> Add Label
1058 idLabelsList.append(q1.value(1).toInt());
1059 break;
1060 case 4: // action -> Play Sound
1061 soundList.append(q1.value(1).toString());
1062 break;
1063 case 5: // action -> Show News in Notifier
1064 colorList.append(q1.value(1).toString());
1065 break;
1066 }
1067 }
1068
1069 if (qStr1.isEmpty())
1070 qStr.clear();
1071 else
1072 qStr.append(qStr1);
1073
1074 whereStr = QString(" WHERE feedId='%1' AND deleted=0").arg(feedId);
1075
1076 qStr2.clear();
1077 switch (filterType) {
1078 case 1: // Match all conditions
1079 qStr2.append("AND ");
1080 break;
1081 case 2: // Match any condition
1082 qStr2.append("OR ");
1083 break;
1084 }
1085
1086 if ((filterType == 1) || (filterType == 2)) {
1087 whereStr.append(" AND ( ");
1088 qStr1.clear();
1089
1090 q1.exec(QString("SELECT field, condition, content FROM filterConditions "
1091 "WHERE idFilter=='%1'").arg(filterId));
1092 while (q1.next()) {
1093 if (!qStr1.isNull()) qStr1.append(qStr2);
1094 QString content = q1.value(2).toString().replace("'", "''");
1095 switch (q1.value(0).toInt()) {
1096 case 0: // field -> Title
1097 switch (q1.value(1).toInt()) {
1098 case 0: // condition -> contains
1099 qStr1.append(QString("UPPER(title) LIKE '%%1%' ").arg(content.toUpper()));
1100 break;
1101 case 1: // condition -> doesn't contains
1102 qStr1.append(QString("UPPER(title) NOT LIKE '%%1%' ").arg(content.toUpper()));
1103 break;
1104 case 2: // condition -> is
1105 qStr1.append(QString("UPPER(title) LIKE '%1' ").arg(content.toUpper()));
1106 break;
1107 case 3: // condition -> isn't
1108 qStr1.append(QString("UPPER(title) NOT LIKE '%1' ").arg(content.toUpper()));
1109 break;
1110 case 4: // condition -> begins with
1111 qStr1.append(QString("UPPER(title) LIKE '%1%' ").arg(content.toUpper()));
1112 break;
1113 case 5: // condition -> ends with
1114 qStr1.append(QString("UPPER(title) LIKE '%%1' ").arg(content.toUpper()));
1115 break;
1116 case 6: // condition -> regExp
1117 qStr1.append(QString("title REGEXP '%1' ").arg(content));
1118 break;
1119 }
1120 break;
1121 case 1: // field -> Description
1122 switch (q1.value(1).toInt()) {
1123 case 0: // condition -> contains
1124 qStr1.append(QString("UPPER(description) LIKE '%%1%' ").arg(content.toUpper()));
1125 break;
1126 case 1: // condition -> doesn't contains
1127 qStr1.append(QString("UPPER(description) NOT LIKE '%%1%' ").arg(content.toUpper()));
1128 break;
1129 case 2: // condition -> regExp
1130 qStr1.append(QString("description REGEXP '%1' ").arg(content));
1131 break;
1132 }
1133 break;
1134 case 2: // field -> Author
1135 switch (q1.value(1).toInt()) {
1136 case 0: // condition -> contains
1137 qStr1.append(QString("UPPER(author_name) LIKE '%%1%' ").arg(content.toUpper()));
1138 break;
1139 case 1: // condition -> doesn't contains
1140 qStr1.append(QString("UPPER(author_name) NOT LIKE '%%1%' ").arg(content.toUpper()));
1141 break;
1142 case 2: // condition -> is
1143 qStr1.append(QString("UPPER(author_name) LIKE '%1' ").arg(content.toUpper()));
1144 break;
1145 case 3: // condition -> isn't
1146 qStr1.append(QString("UPPER(author_name) NOT LIKE '%1' ").arg(content.toUpper()));
1147 break;
1148 case 4: // condition -> regExp
1149 qStr1.append(QString("author_name REGEXP '%1' ").arg(content));
1150 break;
1151 }
1152 break;
1153 case 3: // field -> Category
1154 switch (q1.value(1).toInt()) {
1155 case 0: // condition -> contains
1156 qStr1.append(QString("UPPER(category) LIKE '%%1%' ").arg(content.toUpper()));
1157 break;
1158 case 1: // condition -> doesn't contains
1159 qStr1.append(QString("UPPER(category) NOT LIKE '%%1%' ").arg(content.toUpper()));
1160 break;
1161 case 2: // condition -> is
1162 qStr1.append(QString("UPPER(category) LIKE '%1' ").arg(content.toUpper()));
1163 break;
1164 case 3: // condition -> isn't
1165 qStr1.append(QString("UPPER(category) NOT LIKE '%1' ").arg(content.toUpper()));
1166 break;
1167 case 4: // condition -> begins with
1168 qStr1.append(QString("UPPER(category) LIKE '%1%' ").arg(content.toUpper()));
1169 break;
1170 case 5: // condition -> ends with
1171 qStr1.append(QString("UPPER(category) LIKE '%%1' ").arg(content.toUpper()));
1172 break;
1173 case 6: // condition -> regExp
1174 qStr1.append(QString("category REGEXP '%1' ").arg(content));
1175 break;
1176 }
1177 break;
1178 case 4: // field -> Status
1179 if (q1.value(1).toInt() == 0) { // Status -> is
1180 switch (q1.value(2).toInt()) {
1181 case 0:
1182 qStr1.append("new==1 ");
1183 break;
1184 case 1:
1185 qStr1.append("read>=1 ");
1186 break;
1187 case 2:
1188 qStr1.append("starred==1 ");
1189 break;
1190 }
1191 } else { // Status -> isn't
1192 switch (q1.value(2).toInt()) {
1193 case 0:
1194 qStr1.append("new==0 ");
1195 break;
1196 case 1:
1197 qStr1.append("read==0 ");
1198 break;
1199 case 2:
1200 qStr1.append("starred==0 ");
1201 break;
1202 }
1203 }
1204 break;
1205 case 5: // field -> Link
1206 switch (q1.value(1).toInt()) {
1207 case 0: // condition -> contains
1208 qStr1.append(QString("link_href LIKE '%%1%' ").arg(content));
1209 break;
1210 case 1: // condition -> doesn't contains
1211 qStr1.append(QString("link_href NOT LIKE '%%1%' ").arg(content));
1212 break;
1213 case 2: // condition -> is
1214 qStr1.append(QString("link_href LIKE '%1' ").arg(content));
1215 break;
1216 case 3: // condition -> isn't
1217 qStr1.append(QString("link_href NOT LIKE '%1' ").arg(content));
1218 break;
1219 case 4: // condition -> begins with
1220 qStr1.append(QString("link_href LIKE '%1%' ").arg(content));
1221 break;
1222 case 5: // condition -> ends with
1223 qStr1.append(QString("link_href LIKE '%%1' ").arg(content));
1224 break;
1225 case 6: // condition -> regExp
1226 qStr1.append(QString("link_href REGEXP '%1' ").arg(content));
1227 break;
1228 }
1229 break;
1230 case 6: // field -> News
1231 switch (q1.value(1).toInt()) {
1232 case 0: // condition -> contains
1233 qStr1.append(QString("(UPPER(title) LIKE '%%1%' OR UPPER(description) LIKE '%%1%') ").arg(content.toUpper()));
1234 break;
1235 case 1: // condition -> doesn't contains
1236 qStr1.append(QString("(UPPER(title) NOT LIKE '%%1%' OR UPPER(description) NOT LIKE '%%1%') ").arg(content.toUpper()));
1237 break;
1238 case 2: // condition -> regExp
1239 qStr1.append(QString("(title REGEXP '%1' OR description REGEXP '%1') ").arg(content));
1240 break;
1241 }
1242 break;
1243 }
1244 }
1245 whereStr.append(qStr1).append(")");
1246 }
1247
1248 if (q1.exec(QString("SELECT id, label FROM news").append(whereStr))) {
1249 QSqlQuery q2(db_);
1250 bool isPlaySound = false;
1251
1252 while (q1.next()) {
1253 if (!qStr.isEmpty()) {
1254 qStr1 = qStr % QString(" WHERE id='%1'").arg(q1.value(0).toInt());
1255 if (!q2.exec(qStr1)) {
1256 qWarning() << __PRETTY_FUNCTION__ << __LINE__
1257 << "q.lastError(): " << q2.lastError().text();
1258 }
1259 }
1260
1261 if (!idLabelsList.isEmpty()) {
1262 QString idLabelsStr = q1.value(1).toString();
1263 foreach (int idLabel, idLabelsList) {
1264 if (idLabelsStr.contains(QString(",%1,").arg(idLabel))) continue;
1265 if (idLabelsStr.isEmpty()) idLabelsStr.append(",");
1266 idLabelsStr.append(QString("%1,").arg(idLabel));
1267
1268 }
1269 qStr1 = QString("UPDATE news SET label='%1' WHERE id='%2'").arg(idLabelsStr).
1270 arg(q1.value(0).toInt());
1271 if (!q2.exec(qStr1)) {
1272 qWarning() << __PRETTY_FUNCTION__ << __LINE__
1273 << "q.lastError(): " << q2.lastError().text();
1274 }
1275 }
1276
1277 if (!colorList.isEmpty()) {
1278 emit signalAddColorList(q1.value(0).toInt(), colorList.at(0));
1279 }
1280
1281 isPlaySound = true;
1282 }
1283
1284 if (isPlaySound && !soundList.isEmpty())
1285 emit signalPlaySound(soundList.at(0));
1286 } else {
1287 qWarning() << __PRETTY_FUNCTION__ << __LINE__
1288 << "q.lastError(): " << q1.lastError().text();
1289 }
1290
1291 }
1292 }
1293
1294 /** @brief Update feed counts and all its parent categories
1295 *
1296 * Update fields: unread news number,
1297 * new news number, categories last update date/time
1298 * @param feedId - Feed Id
1299 * @param feedUrl - Feed URL
1300 * @param updated - Time feed updated
1301 *----------------------------------------------------------------------------*/
recountFeedCounts(int feedId,const QString & feedUrl,const QString & updated,const QString & lastBuildDate)1302 int ParseObject::recountFeedCounts(int feedId, const QString &feedUrl,
1303 const QString &updated, const QString &lastBuildDate)
1304 {
1305 QSqlQuery q(db_);
1306 q.setForwardOnly(true);
1307 QString qStr;
1308 QString htmlUrl;
1309 QString title;
1310
1311 int feedParId = 0;
1312 q.exec(QString("SELECT parentId, htmlUrl, title FROM feeds WHERE id=='%1'").arg(feedId));
1313 if (q.first()) {
1314 feedParId = q.value(0).toInt();
1315 htmlUrl = q.value(1).toString();
1316 title = q.value(2).toString();
1317 }
1318
1319 FeedCountStruct counts;
1320 int undeleteCount = 0;
1321 int unreadCount = 0;
1322 int newNewsCount = 0;
1323
1324 // Count all news (not marked Deleted)
1325 qStr = QString("SELECT count(id) FROM news WHERE feedId=='%1' AND deleted==0").
1326 arg(feedId);
1327 q.exec(qStr);
1328 if (q.first()) undeleteCount = q.value(0).toInt();
1329
1330 // Count unread news
1331 qStr = QString("SELECT count(read) FROM news WHERE feedId=='%1' AND read==0 AND deleted==0").
1332 arg(feedId);
1333 q.exec(qStr);
1334 if (q.first()) unreadCount = q.value(0).toInt();
1335
1336 // Count new news
1337 qStr = QString("SELECT count(new) FROM news WHERE feedId=='%1' AND new==1 AND deleted==0").
1338 arg(feedId);
1339 q.exec(qStr);
1340 if (q.first()) newNewsCount = q.value(0).toInt();
1341
1342 int unreadCountOld = 0;
1343 int newCountOld = 0;
1344 int undeleteCountOld = 0;
1345 qStr = QString("SELECT unread, newCount, undeleteCount FROM feeds WHERE id=='%1'").
1346 arg(feedId);
1347 q.exec(qStr);
1348 if (q.first()) {
1349 unreadCountOld = q.value(0).toInt();
1350 newCountOld = q.value(1).toInt();
1351 undeleteCountOld = q.value(2).toInt();
1352 }
1353
1354 if ((unreadCount == unreadCountOld) && (newNewsCount == newCountOld) &&
1355 (undeleteCount == undeleteCountOld)) {
1356 counts.feedId = feedId;
1357 counts.unreadCount = unreadCount;
1358 counts.newCount = newNewsCount;
1359 counts.undeleteCount = undeleteCount;
1360 counts.updated = updated;
1361 counts.lastBuildDate = lastBuildDate;
1362
1363 emit feedCountsUpdate(counts);
1364 return 0;
1365 }
1366
1367 // Set number unread, new and all(undelete) news for feed
1368 qStr = QString("UPDATE feeds SET unread='%1', newCount='%2', undeleteCount='%3' "
1369 "WHERE id=='%4'").
1370 arg(unreadCount).arg(newNewsCount).arg(undeleteCount).arg(feedId);
1371 q.exec(qStr);
1372
1373 counts.feedId = feedId;
1374 counts.unreadCount = unreadCount;
1375 counts.newCount = newNewsCount;
1376 counts.undeleteCount = undeleteCount;
1377 counts.updated = updated;
1378 counts.lastBuildDate = lastBuildDate;
1379 counts.htmlUrl = htmlUrl;
1380 counts.xmlUrl = feedUrl;
1381 counts.title = title;
1382
1383 emit feedCountsUpdate(counts);
1384
1385 // Recount counters for all feed parents
1386 int l_feedParId = feedParId;
1387 while (l_feedParId) {
1388 QString updatedParent;
1389 int newCount = 0;
1390
1391 qStr = QString("SELECT sum(unread), sum(newCount), sum(undeleteCount), "
1392 "max(updated) FROM feeds WHERE parentId=='%1'").
1393 arg(l_feedParId);
1394 q.exec(qStr);
1395 if (q.first()) {
1396 unreadCount = q.value(0).toInt();
1397 newCount = q.value(1).toInt();
1398 undeleteCount = q.value(2).toInt();
1399 updatedParent = q.value(3).toString();
1400 }
1401 qStr = QString("UPDATE feeds SET unread='%1', newCount='%2', undeleteCount='%3', "
1402 "updated='%4' WHERE id=='%5'").
1403 arg(unreadCount).arg(newCount).arg(undeleteCount).arg(updatedParent).
1404 arg(l_feedParId);
1405 q.exec(qStr);
1406
1407 FeedCountStruct counts;
1408 counts.feedId = l_feedParId;
1409
1410 q.exec(QString("SELECT parentId FROM feeds WHERE id==%1").arg(l_feedParId));
1411 if (q.first()) l_feedParId = q.value(0).toInt();
1412
1413 counts.unreadCount = unreadCount;
1414 counts.newCount = newCount;
1415 counts.undeleteCount = undeleteCount;
1416 counts.updated = updatedParent;
1417
1418 emit feedCountsUpdate(counts);
1419 }
1420
1421 return (newNewsCount - newCountOld);
1422 }
1423