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 "mainapplication.h"
19 
20 #include "common.h"
21 #include "cookiejar.h"
22 #include "database.h"
23 #include "globals.h"
24 #include "networkmanager.h"
25 #include "adblockmanager.h"
26 #include "settings.h"
27 #include "splashscreen.h"
28 #include "updatefeeds.h"
29 #include "VersionNo.h"
30 
MainApplication(int & argc,char ** argv)31 MainApplication::MainApplication(int &argc, char **argv)
32   : QtSingleApplication(argc, argv)
33   , isPortableAppsCom_(false)
34   , isClosing_(false)
35   , dbFileExists_(false)
36   , translator_(0)
37   , qt_translator_(0)
38   , mainWindow_(0)
39   , networkManager_(0)
40   , cookieJar_(0)
41   , diskCache_(0)
42   , downloadManager_(0)
43   , analytics_(0)
44 {
45   setApplicationName("QuiteRss");
46   setOrganizationName("QuiteRss");
47   setApplicationVersion(STRPRODUCTVER);
48   globals.init();
49 
50   QString message = arguments().value(1);
51   if (isRunning()) {
52     if (argc == 1) {
53       sendMessage("--show");
54     } else {
55       for (int i = 2; i < argc; ++i)
56         message += '\n' + arguments().value(i);
57       sendMessage(message);
58     }
59     isClosing_ = true;
60     return;
61   } else {
62     if (message.contains("--exit", Qt::CaseInsensitive)) {
63       isClosing_ = true;
64       return;
65     }
66   }
67 
68   setWindowIcon(QIcon(":/images/quiterss128"));
69   setQuitOnLastWindowClosed(false);
70 
71   createSettings();
72 
73   qWarning() << "Run application!";
74 
75   setStyleApplication();
76   setTranslateApplication();
77   showSplashScreen();
78 
79   createGoogleAnalytics();
80 
81   connectDatabase();
82   setProgressSplashScreen(30);
83   qWarning() << "Run application 2";
84   mainWindow_ = new MainWindow();
85   qWarning() << "Run application 3";
86   setProgressSplashScreen(60);
87 
88   loadSettings();
89   qWarning() << "Run application 4";
90   updateFeeds_ = new UpdateFeeds(mainWindow_);
91   setProgressSplashScreen(90);
92   qWarning() << "Run application 5";
93   mainWindow_->restoreFeedsOnStartUp();
94   setProgressSplashScreen(100);
95   qWarning() << "Run application 6";
96   if (!mainWindow_->startingTray_ || !mainWindow_->showTrayIcon_) {
97     mainWindow_->show();
98   }
99   mainWindow_->isMinimizeToTray_ = false;
100 
101   closeSplashScreen();
102 
103   if (mainWindow_->showTrayIcon_) {
104     QTimer::singleShot(0, mainWindow_->traySystem, SLOT(show()));
105   }
106 
107   if (updateFeedsStartUp_) {
108     QTimer::singleShot(0, mainWindow_, SLOT(slotGetAllFeeds()));
109   }
110 
111   receiveMessage(message);
112   connect(this, SIGNAL(messageReceived(QString)), SLOT(receiveMessage(QString)));
113 }
114 
~MainApplication()115 MainApplication::~MainApplication()
116 {
117 
118 }
119 
getInstance()120 MainApplication *MainApplication::getInstance()
121 {
122   return static_cast<MainApplication*>(QCoreApplication::instance());
123 }
124 
receiveMessage(const QString & message)125 void MainApplication::receiveMessage(const QString &message)
126 {
127   if (!message.isEmpty()) {
128     qWarning() << QString("Received message: %1").arg(message);
129 
130     QStringList params = message.split('\n');
131     foreach (QString param, params) {
132       if (param == "--show") {
133         if (isClosing_)
134           return;
135         mainWindow_->showWindows();
136       }
137       if (param == "--exit") mainWindow_->quitApp();
138       if (param.contains("feed:", Qt::CaseInsensitive)) {
139         QClipboard *clipboard = QApplication::clipboard();
140         if (param.contains("https://", Qt::CaseInsensitive)) {
141           param.remove(0, 5);
142           clipboard->setText(param);
143         } else {
144           param.remove(0, 7);
145           clipboard->setText("http://" + param);
146         }
147         mainWindow_->addFeed();
148       }
149     }
150   }
151 }
152 
createSettings()153 void MainApplication::createSettings()
154 {
155   Settings settings;
156   settings.beginGroup("Settings");
157   storeDBMemory_ = settings.value("storeDBMemory", true).toBool();
158   isSaveDataLastFeed_ = settings.value("createLastFeed", false).toBool();
159   styleApplication_ = settings.value("styleApplication", "greenStyle_").toString();
160   showSplashScreen_ = settings.value("showSplashScreen", true).toBool();
161   updateFeedsStartUp_ = settings.value("autoUpdatefeedsStartUp", false).toBool();
162 
163   QString strLang;
164   QString strLocalLang = QLocale::system().name();
165   bool findLang = false;
166   QDir langDir(resourcesDir() + "/lang");
167   foreach (QString file, langDir.entryList(QStringList("*.qm"), QDir::Files)) {
168     strLang = file.section('.', 0, 0).section('_', 1);
169     if (strLocalLang == strLang) {
170       strLang = strLocalLang;
171       findLang = true;
172       break;
173     }
174   }
175   if (!findLang) {
176     strLocalLang = strLocalLang.left(2);
177     foreach (QString file, langDir.entryList(QStringList("*.qm"), QDir::Files)) {
178       strLang = file.section('.', 0, 0).section('_', 1);
179       if (strLocalLang.contains(strLang, Qt::CaseInsensitive)) {
180         strLang = strLocalLang;
181         findLang = true;
182         break;
183       }
184     }
185   }
186   if (!findLang) strLang = "en";
187   langFileName_ = settings.value("langFileName", strLang).toString();
188 
189   settings.endGroup();
190 
191   proxyLoadSettings();
192 }
193 
createGoogleAnalytics()194 void MainApplication::createGoogleAnalytics()
195 {
196   Settings settings;
197   bool statisticsEnabled = settings.value("Settings/statisticsEnabled2", true).toBool();
198   if (statisticsEnabled) {
199     QString clientID;
200     if (!settings.contains("GAnalytics-cid")) {
201       settings.setValue("GAnalytics-cid", QUuid::createUuid().toString());
202     }
203     clientID = settings.value("GAnalytics-cid").toString();
204     analytics_ = new GAnalytics(this, TRACKING_ID, clientID);
205     analytics_->generateUserAgentEtc();
206     analytics_->startSession();
207   }
208 }
209 
connectDatabase()210 void MainApplication::connectDatabase()
211 {
212   QString fileName(dbFileName() % ".bak");
213   if (QFile(fileName).exists()) {
214     QString sourceFileName = QFile::symLinkTarget(dbFileName());
215     if (sourceFileName.isEmpty()) {
216       sourceFileName = dbFileName();
217     }
218     if (QFile::remove(sourceFileName)) {
219       if (!QFile::rename(fileName, sourceFileName))
220         qCritical() << "Failed to rename new database file!";
221     } else {
222       qCritical() << "Failed to delete old database file!";
223     }
224   }
225 
226 #if defined(HAVE_QT5) && defined(HAVE_X11)
227   fileName = "~/.local/share/data/QuiteRss/QuiteRss/feeds.db";
228   if (!QFile(dbFileName()).exists() && QFile(fileName).exists()) {
229     QFile::copy(fileName, dbFileName());
230   }
231 #endif
232 
233   if (QFile(dbFileName()).exists()) {
234     dbFileExists_ = true;
235   }
236 
237   Database::initialization();
238 }
239 
loadSettings()240 void MainApplication::loadSettings()
241 {
242   c2fLoadSettings();
243   reloadUserStyleBrowser();
244 }
245 
quitApplication()246 void MainApplication::quitApplication()
247 {
248   qWarning() << "quitApplication 1";
249   delete mainWindow_;
250   qWarning() << "quitApplication 2";
251   delete networkManager_;
252   delete cookieJar_;
253   delete closingWidget_;
254 
255   if (analytics_) {
256     analytics_->endSession();
257     analytics_->waitForIdle();
258     delete analytics_;
259   }
260 
261   qWarning() << "Quit application";
262 
263   quit();
264 }
265 
showClosingWidget()266 void MainApplication::showClosingWidget()
267 {
268   closingWidget_ = new QWidget(0, Qt::Dialog | Qt::WindowStaysOnTopHint | Qt::WindowCloseButtonHint);
269   closingWidget_->setFocusPolicy(Qt::NoFocus);
270   QVBoxLayout *layout = new QVBoxLayout(closingWidget_);
271   layout->addWidget(new QLabel(tr("Saving data...")));
272   closingWidget_->resize(150, 20);
273   closingWidget_->show();
274   closingWidget_->move(QApplication::desktop()->availableGeometry().width() - closingWidget_->frameSize().width(),
275                QApplication::desktop()->availableGeometry().height() - closingWidget_->frameSize().height());
276   closingWidget_->setFixedSize(closingWidget_->size());
277   qApp->processEvents();
278 }
279 
commitData(QSessionManager & manager)280 void MainApplication::commitData(QSessionManager &manager)
281 {
282   manager.release();
283   mainWindow_->quitApp();
284 }
285 
isPortable() const286 bool MainApplication::isPortable() const
287 {
288   return globals.isPortable_;
289 }
290 
isPortableAppsCom() const291 bool MainApplication::isPortableAppsCom() const
292 {
293   return isPortableAppsCom_;
294 }
295 
setClosing()296 void MainApplication::setClosing()
297 {
298   isClosing_ = true;
299 }
300 
isClosing() const301 bool MainApplication::isClosing() const
302 {
303   return isClosing_;
304 }
305 
isNoDebugOutput() const306 bool MainApplication::isNoDebugOutput() const
307 {
308   return globals.noDebugOutput_;
309 }
310 
resourcesDir() const311 QString MainApplication::resourcesDir() const
312 {
313   return globals.resourcesDir_;
314 }
315 
dataDir() const316 QString MainApplication::dataDir() const
317 {
318   return globals.dataDir_;
319 }
320 
absolutePath(const QString & path) const321 QString MainApplication::absolutePath(const QString &path) const
322 {
323   QString absolutePath = path;
324   if (isPortable()) {
325     if (!QDir::isAbsolutePath(path)) {
326       absolutePath = dataDir() % "/" % path;
327     }
328   }
329   return absolutePath;
330 }
331 
dbFileName() const332 QString MainApplication::dbFileName() const
333 {
334   return dataDir() % "/feeds.db";
335 }
336 
isSaveDataLastFeed() const337 bool MainApplication::isSaveDataLastFeed() const
338 {
339   return isSaveDataLastFeed_;
340 }
341 
storeDBMemory() const342 bool MainApplication::storeDBMemory() const
343 {
344   return storeDBMemory_;
345 }
346 
setStyleApplication()347 void MainApplication::setStyleApplication()
348 {
349   QString fileName(resourcesDir());
350   if (styleApplication_ == "systemStyle_") {
351     fileName.append("/style/system.qss");
352   } else if (styleApplication_ == "system2Style_") {
353     fileName.append("/style/system2.qss");
354   } else if (styleApplication_ == "darkStyle_") {
355     fileName.append("/style/dark.qss");
356   } else if (styleApplication_ == "orangeStyle_") {
357     fileName.append("/style/orange.qss");
358   } else if (styleApplication_ == "purpleStyle_") {
359     fileName.append("/style/purple.qss");
360   } else if (styleApplication_ == "pinkStyle_") {
361     fileName.append("/style/pink.qss");
362   } else if (styleApplication_ == "grayStyle_") {
363     fileName.append("/style/gray.qss");
364   } else {
365     fileName.append("/style/green.qss");
366   }
367   QFile file(fileName);
368   if (!file.open(QFile::ReadOnly)) {
369     file.setFileName(":/style/systemStyle");
370     file.open(QFile::ReadOnly);
371   }
372   setStyleSheet(QLatin1String(file.readAll()));
373   file.close();
374 
375   setStyle(new QProxyStyle);
376 }
377 
setTranslateApplication()378 void MainApplication::setTranslateApplication()
379 {
380   if (!translator_)
381     translator_ = new QTranslator(this);
382   removeTranslator(translator_);
383   translator_->load(resourcesDir() + QString("/lang/quiterss_%1").arg(langFileName_));
384   installTranslator(translator_);
385 
386   if (!qt_translator_)
387     qt_translator_ = new QTranslator(this);
388   removeTranslator(qt_translator_);
389 #ifdef HAVE_X11
390   qt_translator_->load(QLibraryInfo::location (QLibraryInfo::TranslationsPath) + "/qtbase_" + langFileName_);
391 #else
392   qt_translator_->load(resourcesDir() + "/lang/qtbase_" + langFileName_);
393 #endif
394   installTranslator(qt_translator_);
395 }
396 
showSplashScreen()397 void MainApplication::showSplashScreen()
398 {
399   Settings settings;
400   int versionDB = settings.value("VersionDB", "1").toInt();
401   if ((versionDB != Database::version()) && QFile::exists(settings.fileName()))
402     showSplashScreen_ = true;
403 
404   if (showSplashScreen_) {
405     splashScreen_ = new SplashScreen(QPixmap(":/images/images/splashScreen.png"));
406     splashScreen_->show();
407     processEvents();
408     if ((versionDB != Database::version()) && QFile::exists(settings.fileName())) {
409       splashScreen_->showMessage(QString("Converting database to version %1...").arg(Database::version()),
410                                 Qt::AlignRight | Qt::AlignTop, Qt::darkGray);
411     }
412   }
413 }
414 
closeSplashScreen()415 void MainApplication::closeSplashScreen()
416 {
417   if (showSplashScreen_) {
418     splashScreen_->finish(mainWindow_);
419     splashScreen_->deleteLater();
420   }
421 }
422 
setProgressSplashScreen(int value)423 void MainApplication::setProgressSplashScreen(int value)
424 {
425   if (showSplashScreen_)
426     splashScreen_->setProgress(value);
427 }
428 
mainWindow()429 MainWindow *MainApplication::mainWindow()
430 {
431   return mainWindow_;
432 }
433 
networkManager()434 NetworkManager *MainApplication::networkManager()
435 {
436   if (!networkManager_) {
437     networkManager_ = new NetworkManager(false, this);
438     setDiskCache();
439   }
440   return networkManager_;
441 }
442 
cookieJar()443 CookieJar *MainApplication::cookieJar()
444 {
445   if (!cookieJar_) {
446     cookieJar_ = new CookieJar(this);
447   }
448   return cookieJar_;
449 }
450 
setDiskCache()451 void MainApplication::setDiskCache()
452 {
453   Settings settings;
454   settings.beginGroup("Settings");
455 
456   bool useDiskCache = settings.value("useDiskCache", true).toBool();
457   if (useDiskCache) {
458     if (!diskCache_) {
459       diskCache_ = new QNetworkDiskCache(this);
460     }
461 
462     QString diskCacheDirPath = settings.value("dirDiskCache", cacheDefaultDir()).toString();
463     if (diskCacheDirPath.isEmpty()) diskCacheDirPath = cacheDefaultDir();
464     diskCacheDirPath = absolutePath(diskCacheDirPath);
465 
466     bool cleanDiskCache = settings.value("cleanDiskCache", true).toBool();
467     if (cleanDiskCache) {
468       Common::removePath(diskCacheDirPath);
469       settings.setValue("cleanDiskCache", false);
470     }
471 
472     diskCache_->setCacheDirectory(diskCacheDirPath);
473     int maxDiskCache = settings.value("maxDiskCache", 50).toInt();
474     diskCache_->setMaximumCacheSize(maxDiskCache*1024*1024);
475 
476     networkManager()->setCache(diskCache_);
477   } else {
478     if (diskCache_) {
479       diskCache_->setMaximumCacheSize(0);
480       diskCache_->clear();
481     }
482   }
483 
484   settings.endGroup();
485 }
486 
cacheDefaultDir() const487 QString MainApplication::cacheDefaultDir() const
488 {
489   return globals.cacheDir_;
490 }
491 
soundNotifyDefaultFile() const492 QString MainApplication::soundNotifyDefaultFile() const
493 {
494   return globals.soundNotifyDir_ % "/notification.wav";
495 }
496 
styleSheetNewsDefaultFile() const497 QString MainApplication::styleSheetNewsDefaultFile() const
498 {
499   if (isPortable()) {
500     return "style/news.css";
501   } else {
502     return resourcesDir() % "/style/news.css";
503   }
504 }
505 
styleSheetWebDarkFile() const506 QString MainApplication::styleSheetWebDarkFile() const
507 {
508   if (isPortable()) {
509     return "style/web_dark.css";
510   } else {
511     return resourcesDir() % "/style/web_dark.css";
512   }
513 }
514 
updateFeeds()515 UpdateFeeds *MainApplication::updateFeeds()
516 {
517   return updateFeeds_;
518 }
519 
runUserFilter(int feedId,int filterId)520 void MainApplication::runUserFilter(int feedId, int filterId)
521 {
522   emit signalRunUserFilter(feedId, filterId);
523 }
524 
sqlQueryExec(const QString & query)525 void MainApplication::sqlQueryExec(const QString &query)
526 {
527   emit signalSqlQueryExec(query);
528 }
529 
530 /** @brief Click to Flash
531  *---------------------------------------------------------------------------*/
c2fLoadSettings()532 void MainApplication::c2fLoadSettings()
533 {
534   Settings settings;
535   settings.beginGroup("ClickToFlash");
536   c2fWhitelist_ = settings.value("whitelist", QStringList()).toStringList();
537   c2fEnabled_ = settings.value("enabled", true).toBool();
538 #if QT_VERSION >= 0x050900
539   c2fEnabled_ = false;
540 #endif
541   settings.endGroup();
542 }
543 
c2fSaveSettings()544 void MainApplication::c2fSaveSettings()
545 {
546   Settings settings;
547   settings.beginGroup("ClickToFlash");
548   settings.setValue("whitelist", c2fWhitelist_);
549   settings.setValue("enabled", c2fEnabled_);
550   settings.endGroup();
551 }
552 
c2fIsEnabled() const553 bool MainApplication::c2fIsEnabled() const
554 {
555   return c2fEnabled_;
556 }
557 
c2fSetEnabled(bool enabled)558 void MainApplication::c2fSetEnabled(bool enabled)
559 {
560   c2fEnabled_ = enabled;
561 }
562 
c2fGetWhitelist()563 QStringList MainApplication::c2fGetWhitelist()
564 {
565   return c2fWhitelist_;
566 }
567 
c2fSetWhitelist(QStringList whitelist)568 void MainApplication::c2fSetWhitelist(QStringList whitelist)
569 {
570   c2fWhitelist_ = whitelist;
571 }
572 
c2fAddWhitelist(const QString & site)573 void MainApplication::c2fAddWhitelist(const QString &site)
574 {
575   c2fWhitelist_.append(site);
576 }
577 
downloadManager()578 DownloadManager *MainApplication::downloadManager()
579 {
580   if (!downloadManager_) {
581     downloadManager_ = new DownloadManager();
582   }
583   return downloadManager_;
584 }
585 
reloadUserStyleBrowser()586 void MainApplication::reloadUserStyleBrowser()
587 {
588   Settings settings;
589   settings.beginGroup("Settings");
590   QString userStyleBrowser = settings.value("userStyleBrowser", QString()).toString();
591   QWebSettings::globalSettings()->setUserStyleSheetUrl(userStyleSheet(userStyleBrowser));
592   settings.endGroup();
593 }
594 
595 /** @brief Set user style sheet for browser
596  * @param filePath Filepath of user style
597  * @return URL-link to user style
598  *---------------------------------------------------------------------------*/
userStyleSheet(const QString & filePath) const599 QUrl MainApplication::userStyleSheet(const QString &filePath) const
600 {
601   QString userStyle;
602 
603 #ifndef HAVE_X11
604   // Don't grey out selection on losing focus (to prevent graying out found text)
605   QString highlightColor;
606   QString highlightedTextColor;
607 #ifdef Q_OS_MAC
608   highlightColor = QLatin1String("#b6d6fc");
609   highlightedTextColor = QLatin1String("#000");
610 #else
611   QPalette pal = style()->standardPalette();
612   highlightColor = pal.color(QPalette::Highlight).name();
613   highlightedTextColor = pal.color(QPalette::HighlightedText).name();
614 #endif
615   userStyle += QString("::selection {background: %1; color: %2;} ").arg(highlightColor, highlightedTextColor);
616 #endif
617 
618   userStyle += AdBlockManager::instance()->elementHidingRules();
619 
620   QFile file(filePath);
621   if (!filePath.isEmpty() && file.open(QFile::ReadOnly)) {
622     QString fileData = QString::fromUtf8(file.readAll());
623     fileData.remove(QLatin1Char('\n'));
624     userStyle.append(fileData);
625     file.close();
626   }
627 
628   const QString &encodedStyle = userStyle.toLatin1().toBase64();
629   const QString &dataString = QString("data:text/css;charset=utf-8;base64,%1").arg(encodedStyle);
630 
631   return QUrl(dataString);
632 }
633 
proxyLoadSettings()634 void MainApplication::proxyLoadSettings()
635 {
636   Settings settings;
637   settings.beginGroup("networkProxy");
638   networkProxy_.setType(static_cast<QNetworkProxy::ProxyType>(
639                           settings.value("type", QNetworkProxy::DefaultProxy).toInt()));
640   networkProxy_.setHostName(settings.value("hostName", "").toString());
641   networkProxy_.setPort(    settings.value("port",     "").toUInt());
642   networkProxy_.setUser(    settings.value("user",     "").toString());
643   networkProxy_.setPassword(settings.value("password", "").toString());
644   settings.endGroup();
645 
646   setProxy();
647 }
648 
proxySaveSettings(const QNetworkProxy & proxy)649 void MainApplication::proxySaveSettings(const QNetworkProxy &proxy)
650 {
651   networkProxy_ = proxy;
652 
653   Settings settings;
654   settings.beginGroup("networkProxy");
655   settings.setValue("type",     networkProxy_.type());
656   settings.setValue("hostName", networkProxy_.hostName());
657   settings.setValue("port",     networkProxy_.port());
658   settings.setValue("user",     networkProxy_.user());
659   settings.setValue("password", networkProxy_.password());
660   settings.endGroup();
661 
662   setProxy();
663 }
664 
setProxy()665 void MainApplication::setProxy()
666 {
667 
668   if (QNetworkProxy::DefaultProxy == networkProxy_.type())
669     QNetworkProxyFactory::setUseSystemConfiguration(true);
670   else
671     QNetworkProxy::setApplicationProxy(networkProxy_);
672 }
673