Enable customizing the save statistics time interval
[qBittorrent.git] / src / base / search / searchhandler.cpp
blobfa43ef8802cb070aa517cc27c8a7df1345095b12
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * In addition, as a special exception, the copyright holders give permission to
21 * link this program with the OpenSSL project's "OpenSSL" library (or with
22 * modified versions of it that use the same license as the "OpenSSL" library),
23 * and distribute the linked executables. You must obey the GNU General Public
24 * License in all respects for all of the code used other than "OpenSSL". If you
25 * modify file(s), you may extend this exception to your version of the file(s),
26 * but you are not obligated to do so. If you do not wish to do so, delete this
27 * exception statement from your version.
30 #include "searchhandler.h"
32 #include <chrono>
34 #include <QList>
35 #include <QMetaObject>
36 #include <QProcess>
37 #include <QTimer>
39 #include "base/global.h"
40 #include "base/path.h"
41 #include "base/utils/foreignapps.h"
42 #include "base/utils/fs.h"
43 #include "searchpluginmanager.h"
45 using namespace std::chrono_literals;
47 namespace
49 enum SearchResultColumn
51 PL_DL_LINK,
52 PL_NAME,
53 PL_SIZE,
54 PL_SEEDS,
55 PL_LEECHS,
56 PL_ENGINE_URL,
57 PL_DESC_LINK,
58 PL_PUB_DATE,
59 NB_PLUGIN_COLUMNS
63 SearchHandler::SearchHandler(const QString &pattern, const QString &category, const QStringList &usedPlugins, SearchPluginManager *manager)
64 : QObject(manager)
65 , m_pattern {pattern}
66 , m_category {category}
67 , m_usedPlugins {usedPlugins}
68 , m_manager {manager}
69 , m_searchProcess {new QProcess(this)}
70 , m_searchTimeout {new QTimer(this)}
72 // Load environment variables (proxy)
73 m_searchProcess->setEnvironment(QProcess::systemEnvironment());
75 const QStringList params
77 Utils::ForeignApps::PYTHON_ISOLATE_MODE_FLAG,
78 (SearchPluginManager::engineLocation() / Path(u"nova2.py"_s)).toString(),
79 m_usedPlugins.join(u','),
80 m_category
83 // Launch search
84 m_searchProcess->setProgram(Utils::ForeignApps::pythonInfo().executableName);
85 m_searchProcess->setArguments(params + m_pattern.split(u' '));
87 connect(m_searchProcess, &QProcess::errorOccurred, this, &SearchHandler::processFailed);
88 connect(m_searchProcess, &QProcess::readyReadStandardOutput, this, &SearchHandler::readSearchOutput);
89 connect(m_searchProcess, qOverload<int, QProcess::ExitStatus>(&QProcess::finished)
90 , this, &SearchHandler::processFinished);
92 m_searchTimeout->setSingleShot(true);
93 connect(m_searchTimeout, &QTimer::timeout, this, &SearchHandler::cancelSearch);
94 m_searchTimeout->start(3min);
96 // deferred start allows clients to handle starting-related signals
97 QMetaObject::invokeMethod(this, [this]() { m_searchProcess->start(QIODevice::ReadOnly); }
98 , Qt::QueuedConnection);
101 bool SearchHandler::isActive() const
103 return (m_searchProcess->state() != QProcess::NotRunning);
106 void SearchHandler::cancelSearch()
108 if ((m_searchProcess->state() == QProcess::NotRunning) || m_searchCancelled)
109 return;
111 #ifdef Q_OS_WIN
112 m_searchProcess->kill();
113 #else
114 m_searchProcess->terminate();
115 #endif
116 m_searchCancelled = true;
117 m_searchTimeout->stop();
120 // Slot called when QProcess is Finished
121 // QProcess can be finished for 3 reasons:
122 // Error | Stopped by user | Finished normally
123 void SearchHandler::processFinished(const int exitcode)
125 m_searchTimeout->stop();
127 if (m_searchCancelled)
128 emit searchFinished(true);
129 else if ((m_searchProcess->exitStatus() == QProcess::NormalExit) && (exitcode == 0))
130 emit searchFinished(false);
131 else
132 emit searchFailed();
135 // search QProcess return output as soon as it gets new
136 // stuff to read. We split it into lines and parse each
137 // line to SearchResult calling parseSearchResult().
138 void SearchHandler::readSearchOutput()
140 QByteArray output = m_searchProcess->readAllStandardOutput();
141 output.replace('\r', "");
143 QList<QByteArray> lines = output.split('\n');
144 if (!m_searchResultLineTruncated.isEmpty())
145 lines.prepend(m_searchResultLineTruncated + lines.takeFirst());
146 m_searchResultLineTruncated = lines.takeLast().trimmed();
148 QList<SearchResult> searchResultList;
149 searchResultList.reserve(lines.size());
151 for (const QByteArray &line : asConst(lines))
153 SearchResult searchResult;
154 if (parseSearchResult(QString::fromUtf8(line), searchResult))
155 searchResultList << searchResult;
158 if (!searchResultList.isEmpty())
160 for (const SearchResult &result : searchResultList)
161 m_results.append(result);
162 emit newSearchResults(searchResultList);
166 void SearchHandler::processFailed()
168 if (!m_searchCancelled)
169 emit searchFailed();
172 // Parse one line of search results list
173 // Line is in the following form:
174 // file url | file name | file size | nb seeds | nb leechers | Search engine url
175 bool SearchHandler::parseSearchResult(const QStringView line, SearchResult &searchResult)
177 const QList<QStringView> parts = line.split(u'|');
178 const int nbFields = parts.size();
180 if (nbFields <= PL_ENGINE_URL)
181 return false; // Anything after ENGINE_URL is optional
183 searchResult = SearchResult();
184 searchResult.fileUrl = parts.at(PL_DL_LINK).trimmed().toString(); // download URL
185 searchResult.fileName = parts.at(PL_NAME).trimmed().toString(); // Name
186 searchResult.fileSize = parts.at(PL_SIZE).trimmed().toLongLong(); // Size
188 bool ok = false;
190 searchResult.nbSeeders = parts.at(PL_SEEDS).trimmed().toLongLong(&ok); // Seeders
191 if (!ok || (searchResult.nbSeeders < 0))
192 searchResult.nbSeeders = -1;
194 searchResult.nbLeechers = parts.at(PL_LEECHS).trimmed().toLongLong(&ok); // Leechers
195 if (!ok || (searchResult.nbLeechers < 0))
196 searchResult.nbLeechers = -1;
198 searchResult.siteUrl = parts.at(PL_ENGINE_URL).trimmed().toString(); // Search engine site URL
199 searchResult.engineName = m_manager->pluginNameBySiteURL(searchResult.siteUrl); // Search engine name
201 if (nbFields > PL_DESC_LINK)
202 searchResult.descrLink = parts.at(PL_DESC_LINK).trimmed().toString(); // Description Link
204 if (nbFields > PL_PUB_DATE)
206 const qint64 secs = parts.at(PL_PUB_DATE).trimmed().toLongLong(&ok);
207 if (ok && (secs > 0))
208 searchResult.pubDate = QDateTime::fromSecsSinceEpoch(secs); // Date
211 return true;
214 SearchPluginManager *SearchHandler::manager() const
216 return m_manager;
219 QList<SearchResult> SearchHandler::results() const
221 return m_results;
224 QString SearchHandler::pattern() const
226 return m_pattern;