diff --git a/src/GUI.cpp b/src/GUI.cpp index 3e978a3ec..05de2962a 100644 --- a/src/GUI.cpp +++ b/src/GUI.cpp @@ -22,19 +22,11 @@ #include #include #include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include #include -#include +#include +#include +#include #include #include @@ -49,12 +41,10 @@ #include "createtorrent_imp.h" #include "properties_imp.h" #include "DLListDelegate.h" -#include "SearchListDelegate.h" #include "downloadThread.h" #include "downloadFromURLImp.h" #include "torrentAddition.h" - -#define SEARCHHISTORY_MAXSIZE 50 +#include "searchEngine.h" /***************************************************** * * @@ -94,7 +84,6 @@ GUI::GUI(QWidget *parent, QStringList torrentCmdLine) : QMainWindow(parent){ actionCreate_torrent->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/new.png"))); info_icon->setPixmap(QPixmap(QString::fromUtf8(":/Icons/log.png"))); tabs->setTabIcon(0, QIcon(QString::fromUtf8(":/Icons/home.png"))); - tabs->setTabIcon(1, QIcon(QString::fromUtf8(":/Icons/skin/search.png"))); // Set default ratio lbl_ratio_icon->setPixmap(QPixmap(QString::fromUtf8(":/Icons/stare.png"))); // Fix Tool bar layout @@ -171,6 +160,10 @@ GUI::GUI(QWidget *parent, QStringList torrentCmdLine) : QMainWindow(parent){ std::cerr << "Error: System tray unavailable\n"; } myTrayIcon = new QSystemTrayIcon(QIcon(":/Icons/qbittorrent22.png"), this); + // Search engine tab + searchEngine = new SearchEngine(&BTSession, myTrayIcon); + tabs->addTab(searchEngine, tr("Search")); + tabs->setTabIcon(1, QIcon(QString::fromUtf8(":/Icons/skin/search.png"))); // Start download list refresher refresher = new QTimer(this); connect(refresher, SIGNAL(timeout()), this, SLOT(updateDlList())); @@ -190,65 +183,15 @@ GUI::GUI(QWidget *parent, QStringList torrentCmdLine) : QMainWindow(parent){ connect(myTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(toggleVisibility(QSystemTrayIcon::ActivationReason))); myTrayIcon->show(); // Use a tcp server to allow only one instance of qBittorrent - if (!tcpServer.listen(QHostAddress::LocalHost, 1666)) { + tcpServer = new QTcpServer(); + if (!tcpServer->listen(QHostAddress::LocalHost, 1666)) { std::cerr << "Couldn't create socket, single instance mode won't work...\n"; } - connect(&tcpServer, SIGNAL(newConnection()), this, SLOT(acceptConnection())); + connect(tcpServer, SIGNAL(newConnection()), this, SLOT(acceptConnection())); // Start connection checking timer checkConnect = new QTimer(this); connect(checkConnect, SIGNAL(timeout()), this, SLOT(checkConnectionStatus())); checkConnect->start(5000); - // Set Search results list model - SearchListModel = new QStandardItemModel(0,5); - SearchListModel->setHeaderData(NAME, Qt::Horizontal, tr("Name", "i.e: file name")); - SearchListModel->setHeaderData(SIZE, Qt::Horizontal, tr("Size", "i.e: file size")); - SearchListModel->setHeaderData(PROGRESS, Qt::Horizontal, tr("Seeders", "i.e: Number of full sources")); - SearchListModel->setHeaderData(DLSPEED, Qt::Horizontal, tr("Leechers", "i.e: Number of partial sources")); - SearchListModel->setHeaderData(UPSPEED, Qt::Horizontal, tr("Search engine")); - resultsBrowser->setModel(SearchListModel); - SearchDelegate = new SearchListDelegate(); - resultsBrowser->setItemDelegate(SearchDelegate); - // Make search list header clickable for sorting - resultsBrowser->header()->setClickable(true); - resultsBrowser->header()->setSortIndicatorShown(true); - // Load last columns width for search results list - if(!loadColWidthSearchList()){ - resultsBrowser->header()->resizeSection(0, 275); - } - - // new qCompleter to the search pattern - startSearchHistory(); - searchCompleter = new QCompleter(searchHistory, this); - searchCompleter->setCaseSensitivity(Qt::CaseInsensitive); - search_pattern->setCompleter(searchCompleter); - - // Boolean initialization - search_stopped = false; - // Connect signals to slots (search part) - connect(resultsBrowser, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(downloadSelectedItem(const QModelIndex&))); - connect(resultsBrowser->header(), SIGNAL(sectionPressed(int)), this, SLOT(sortSearchList(int))); - // Creating Search Process - searchProcess = new QProcess(this); - connect(searchProcess, SIGNAL(started()), this, SLOT(searchStarted())); - connect(searchProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(readSearchOutput())); - connect(searchProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(searchFinished(int,QProcess::ExitStatus))); - // Set search engines names - mininova->setText("Mininova"); - piratebay->setText("ThePirateBay"); -// reactor->setText("TorrentReactor"); - isohunt->setText("Isohunt"); -// btjunkie->setText("BTJunkie"); - meganova->setText("Meganova"); - // Check last checked search engines - loadCheckedSearchEngines(); - connect(mininova, SIGNAL(stateChanged(int)), this, SLOT(saveCheckedSearchEngines(int))); - connect(piratebay, SIGNAL(stateChanged(int)), this, SLOT(saveCheckedSearchEngines(int))); -// connect(reactor, SIGNAL(stateChanged(int)), this, SLOT(saveCheckedSearchEngines(int))); - connect(isohunt, SIGNAL(stateChanged(int)), this, SLOT(saveCheckedSearchEngines(int))); -// connect(btjunkie, SIGNAL(stateChanged(int)), this, SLOT(saveCheckedSearchEngines(int))); - connect(meganova, SIGNAL(stateChanged(int)), this, SLOT(saveCheckedSearchEngines(int))); - // Update nova.py search plugin if necessary - updateNova(); previewProcess = new QProcess(this); connect(previewProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(cleanTempPreviewFile(int, QProcess::ExitStatus))); // Accept drag 'n drops @@ -261,19 +204,14 @@ GUI::GUI(QWidget *parent, QStringList torrentCmdLine) : QMainWindow(parent){ // Destructor GUI::~GUI(){ - qDebug("GUI destruction"); - searchProcess->kill(); - searchProcess->waitForFinished(); - delete searchProcess; - delete searchCompleter; + delete searchEngine; delete checkConnect; delete refresher; delete myTrayIcon; delete myTrayIconMenu; delete DLDelegate; delete DLListModel; - delete SearchListModel; - delete SearchDelegate; + delete tcpServer; previewProcess->kill(); previewProcess->waitForFinished(); delete previewProcess; @@ -329,7 +267,7 @@ void GUI::balloonClicked(){ } void GUI::acceptConnection(){ - clientConnection = tcpServer.nextPendingConnection(); + clientConnection = tcpServer->nextPendingConnection(); connect(clientConnection, SIGNAL(disconnected()), this, SLOT(readParamsOnSocket())); qDebug("accepted connection from another instance"); } @@ -620,68 +558,6 @@ void GUI::sortDownloadList(int index){ } } -void GUI::sortSearchList(int index){ - static Qt::SortOrder sortOrder = Qt::AscendingOrder; - if(resultsBrowser->header()->sortIndicatorSection() == index){ - if(sortOrder == Qt::AscendingOrder){ - sortOrder = Qt::DescendingOrder; - }else{ - sortOrder = Qt::AscendingOrder; - } - } - resultsBrowser->header()->setSortIndicator(index, sortOrder); - switch(index){ - //case SIZE: - case SEEDERS: - case LEECHERS: - case SIZE: - sortSearchListInt(index, sortOrder); - break; - default: - sortSearchListString(index, sortOrder); - } -} - -void GUI::sortSearchListInt(int index, Qt::SortOrder sortOrder){ - QList > lines; - // Insertion sorting - for(int i=0; irowCount(); ++i){ - misc::insertSort(lines, QPair(i, SearchListModel->data(SearchListModel->index(i, index)).toLongLong()), sortOrder); - } - // Insert items in new model, in correct order - int nbRows_old = lines.size(); - for(int row=0; rowinsertRow(SearchListModel->rowCount()); - int sourceRow = lines[row].first; - for(int col=0; col<5; ++col){ - SearchListModel->setData(SearchListModel->index(nbRows_old+row, col), SearchListModel->data(SearchListModel->index(sourceRow, col))); - SearchListModel->setData(SearchListModel->index(nbRows_old+row, col), SearchListModel->data(SearchListModel->index(sourceRow, col), Qt::TextColorRole), Qt::TextColorRole); - } - } - // Remove old rows - SearchListModel->removeRows(0, nbRows_old); -} - -void GUI::sortSearchListString(int index, Qt::SortOrder sortOrder){ - QList > lines; - // Insetion sorting - for(int i=0; irowCount(); ++i){ - misc::insertSortString(lines, QPair(i, SearchListModel->data(SearchListModel->index(i, index)).toString()), sortOrder); - } - // Insert items in new model, in correct order - int nbRows_old = lines.size(); - for(int row=0; rowinsertRow(SearchListModel->rowCount()); - int sourceRow = lines[row].first; - for(int col=0; col<5; ++col){ - SearchListModel->setData(SearchListModel->index(nbRows_old+row, col), SearchListModel->data(SearchListModel->index(sourceRow, col))); - SearchListModel->setData(SearchListModel->index(nbRows_old+row, col), SearchListModel->data(SearchListModel->index(sourceRow, col), Qt::TextColorRole), Qt::TextColorRole); - } - } - // Remove old rows - SearchListModel->removeRows(0, nbRows_old); -} - // Toggle Main window visibility void GUI::toggleVisibility(QSystemTrayIcon::ActivationReason e){ if(e == QSystemTrayIcon::Trigger || e == QSystemTrayIcon::DoubleClick){ @@ -718,18 +594,6 @@ QPoint GUI::screenCenter(){ return QPoint((desk.width() - this->frameGeometry().width()) / 2, (desk.height() - this->frameGeometry().height()) / 2); } -// Save last checked search engines to a file -void GUI::saveCheckedSearchEngines(int) const{ - QSettings settings("qBittorrent", "qBittorrent"); - settings.beginGroup("SearchEngines"); - settings.setValue("mininova", mininova->isChecked()); - settings.setValue("piratebay", piratebay->isChecked()); - settings.setValue("isohunt", isohunt->isChecked()); - settings.setValue("meganova", meganova->isChecked()); - settings.endGroup(); - qDebug("Saved checked search engines"); -} - // Save columns width in a file to remember them // (download list) void GUI::saveColWidthDLList() const{ @@ -761,50 +625,6 @@ bool GUI::loadColWidthDLList(){ return true; } -// Save columns width in a file to remember them -// (download list) -void GUI::saveColWidthSearchList() const{ - qDebug("Saving columns width in search list"); - QSettings settings("qBittorrent", "qBittorrent"); - QStringList width_list; - for(int i=0; icolumnCount(); ++i){ - width_list << QString(misc::toString(resultsBrowser->columnWidth(i)).c_str()); - } - settings.setValue("SearchListColsWidth", width_list.join(" ")); - qDebug("Search list columns width saved"); -} - -// Load columns width in a file that were saved previously -// (search list) -bool GUI::loadColWidthSearchList(){ - qDebug("Loading columns width for search list"); - QSettings settings("qBittorrent", "qBittorrent"); - QString line = settings.value("SearchListColsWidth", QString()).toString(); - if(line.isEmpty()) - return false; - QStringList width_list = line.split(' '); - if(width_list.size() != SearchListModel->columnCount()) - return false; - for(int i=0; iheader()->resizeSection(i, width_list.at(i).toInt()); - } - qDebug("Search list columns width loaded"); - return true; -} - -// load last checked search engines from a file -void GUI::loadCheckedSearchEngines(){ - qDebug("Loading checked search engines"); - QSettings settings("qBittorrent", "qBittorrent"); - settings.beginGroup("SearchEngines"); - mininova->setChecked(settings.value("mininova", true).toBool()); - piratebay->setChecked(settings.value("piratebay", false).toBool()); - isohunt->setChecked(settings.value("isohunt", false).toBool()); - meganova->setChecked(settings.value("meganova", false).toBool()); - settings.endGroup(); - qDebug("Loaded checked search engines"); -} - // Display About Dialog void GUI::showAbout(){ //About dialog @@ -845,14 +665,11 @@ void GUI::closeEvent(QCloseEvent *e){ torrentBackup.remove(fileHash+".savepath"); } } - // save the searchHistory for later uses - saveSearchHistory(); // Save DHT entry BTSession.saveDHTEntry(); // Save window size, columns size writeSettings(); saveColWidthDLList(); - saveColWidthSearchList(); // Create fast resume data BTSession.saveFastResumeData(); // Hide tray icon @@ -1418,383 +1235,6 @@ void GUI::checkConnectionStatus(){ qDebug("Connection status updated"); } - -/***************************************************** - * * - * Search * - * * - *****************************************************/ - -// get the last searchs from a QSettings to a QStringList -void GUI::startSearchHistory(){ - QSettings settings("qBittorrent", "qBittorrent"); - settings.beginGroup("Search"); - searchHistory = settings.value("searchHistory",-1).toStringList(); - settings.endGroup(); -} - -// Save the history list into the QSettings for the next session -void GUI::saveSearchHistory() -{ - QSettings settings("qBittorrent", "qBittorrent"); - settings.beginGroup("Search"); - settings.setValue("searchHistory",searchHistory); - settings.endGroup(); -} - -// Function called when we click on search button -void GUI::on_search_button_clicked(){ - QString pattern = search_pattern->text().trimmed(); - // No search pattern entered - if(pattern.isEmpty()){ - QMessageBox::critical(0, tr("Empty search pattern"), tr("Please type a search pattern first")); - return; - } - // if the pattern is not in the pattern - if(searchHistory.indexOf(pattern) == -1){ - //update the searchHistory list - searchHistory.append(pattern); - // verify the max size of the history - if(searchHistory.size() > SEARCHHISTORY_MAXSIZE) - searchHistory = searchHistory.mid(searchHistory.size()/2,searchHistory.size()/2); - searchCompleter = new QCompleter(searchHistory, this); - searchCompleter->setCaseSensitivity(Qt::CaseInsensitive); - search_pattern->setCompleter(searchCompleter); - } - - - // Getting checked search engines - if(!mininova->isChecked() && ! piratebay->isChecked()/* && !reactor->isChecked()*/ && !isohunt->isChecked()/* && !btjunkie->isChecked()*/ && !meganova->isChecked()){ - QMessageBox::critical(0, tr("No search engine selected"), tr("You must select at least one search engine.")); - return; - } - QStringList params; - QStringList engineNames; - search_stopped = false; - // Get checked search engines - if(mininova->isChecked()){ - engineNames << "mininova"; - } - if(piratebay->isChecked()){ - engineNames << "piratebay"; - } -// if(reactor->isChecked()){ -// engineNames << "reactor"; -// } - if(isohunt->isChecked()){ - engineNames << "isohunt"; - } -// if(btjunkie->isChecked()){ -// engineNames << "btjunkie"; -// } - if(meganova->isChecked()){ - engineNames << "meganova"; - } - params << engineNames.join(","); - params << pattern.split(" "); - // Update GUI widgets - no_search_results = true; - nb_search_results = 0; - search_result_line_truncated.clear(); - results_lbl->setText(tr("Results")+" (0):"); - // Launch search - searchProcess->start(misc::qBittorrentPath()+"nova.py", params, QIODevice::ReadOnly); -} - -void GUI::searchStarted(){ - // Update GUI widgets - search_button->setEnabled(false); - search_button->repaint(); - search_status->setText(tr("Searching...")); - search_status->repaint(); - stop_search_button->setEnabled(true); - stop_search_button->repaint(); - // clear results window - SearchListModel->removeRows(0, SearchListModel->rowCount()); - // Clear previous results urls too - searchResultsUrls.clear(); -} - -// Download the given item from search results list -void GUI::downloadSelectedItem(const QModelIndex& index){ - int row = index.row(); - // Get Item url - QString url = searchResultsUrls.value(SearchListModel->data(SearchListModel->index(row, NAME)).toString()); - // Download from url - BTSession.downloadFromUrl(url); - // Set item color to RED - setRowColor(row, "red", false); -} - -// search Qprocess return output as soon as it gets new -// stuff to read. We split it into lines and add each -// line to search results calling appendSearchResult(). -void GUI::readSearchOutput(){ - QByteArray output = searchProcess->readAllStandardOutput(); - QList lines_list = output.split('\n'); - QByteArray line; - if(!search_result_line_truncated.isEmpty()){ - QByteArray end_of_line = lines_list.takeFirst(); - lines_list.prepend(search_result_line_truncated+end_of_line); - } - search_result_line_truncated = lines_list.takeLast().trimmed(); - foreach(line, lines_list){ - appendSearchResult(QString(line)); - } - results_lbl->setText(tr("Results")+" ("+QString(misc::toString(nb_search_results).c_str())+"):"); -} - -// Returns version of nova.py search engine -float GUI::getNovaVersion(const QString& novaPath) const{ - QFile dest_nova(novaPath); - if(!dest_nova.exists()){ - return 0.0; - } - if(!dest_nova.open(QIODevice::ReadOnly | QIODevice::Text)){ - return 0.0; - } - float version = 0.0; - while (!dest_nova.atEnd()){ - QByteArray line = dest_nova.readLine(); - if(line.startsWith("# Version: ")){ - line = line.split(' ').last(); - line.chop(1); // removes '\n' - version = line.toFloat(); - qDebug("Search plugin version: %.1f", version); - break; - } - } - return version; -} - -// Returns changelog of nova.py search engine -QByteArray GUI::getNovaChangelog(const QString& novaPath) const{ - QFile dest_nova(novaPath); - if(!dest_nova.exists()){ - return QByteArray("None"); - } - if(!dest_nova.open(QIODevice::ReadOnly | QIODevice::Text)){ - return QByteArray("None"); - } - QByteArray changelog; - bool in_changelog = false; - while (!dest_nova.atEnd()){ - QByteArray line = dest_nova.readLine(); - line = line.trimmed(); - if(line.startsWith("# Changelog:")){ - in_changelog = true; - }else{ - if(in_changelog && line.isEmpty()){ - // end of changelog - return changelog; - } - if(in_changelog){ - line.remove(0,1); - changelog.append(line); - } - } - } - return changelog; -} - -// Update nova.py search plugin if necessary -void GUI::updateNova() const{ - qDebug("Updating nova"); - float provided_nova_version = getNovaVersion(":/search_engine/nova.py"); - QFile::Permissions perm=QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadUser | QFile::WriteUser | QFile::ExeUser | QFile::ReadGroup | QFile::ReadGroup; - QFile(misc::qBittorrentPath()+"nova.py").setPermissions(perm); - if(provided_nova_version > getNovaVersion(misc::qBittorrentPath()+"nova.py")){ - qDebug("updating local search plugin with shipped one"); - // nova.py needs update - QFile::remove(misc::qBittorrentPath()+"nova.py"); - qDebug("Old nova removed"); - QFile::copy(":/search_engine/nova.py", misc::qBittorrentPath()+"nova.py"); - qDebug("New nova copied"); - QFile::Permissions perm=QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadUser | QFile::WriteUser | QFile::ExeUser | QFile::ReadGroup | QFile::ReadGroup; - QFile(misc::qBittorrentPath()+"nova.py").setPermissions(perm); - qDebug("local search plugin updated"); - } -} - -// Download nova.py from qbittorrent.org -// Check if our nova.py is outdated and -// ask user for action. -void GUI::on_update_nova_button_clicked(){ - CURL *curl; - QString filePath; - qDebug("Checking for search plugin updates on qbittorrent.org"); - // XXX: Trick to get a unique filename - QTemporaryFile *tmpfile = new QTemporaryFile; - if (tmpfile->open()) { - filePath = tmpfile->fileName(); - } - delete tmpfile; - FILE *file = fopen((const char*)filePath.toUtf8(), "w"); - if(!file){ - std::cerr << "Error: could not open temporary file...\n"; - } - // Initilization required by libcurl - curl = curl_easy_init(); - if(!curl){ - std::cerr << "Error: Failed to init curl...\n"; - fclose(file); - return; - } - // Set url to download - curl_easy_setopt(curl, CURLOPT_URL, "http://www.dchris.eu/nova/nova.zip"); - // Define our callback to get called when there's data to be written - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, misc::my_fwrite); - // Set destination file - curl_easy_setopt(curl, CURLOPT_WRITEDATA, file); - // Some SSL mambo jambo - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); - // Perform Download - curl_easy_perform(curl); /* ignores error */ - // Cleanup - curl_easy_cleanup(curl); - // Close tmp file - fclose(file); - qDebug("Version on qbittorrent.org: %f", getNovaVersion(filePath)); - float version_on_server = getNovaVersion(filePath); - if(version_on_server == 0.0){ - //First server is down, try mirror - QFile::remove(filePath); - FILE *file = fopen((const char*)filePath.toUtf8(), "w"); - if(!file){ - std::cerr << "Error: could not open temporary file...\n"; - } - curl = curl_easy_init(); - curl_easy_setopt(curl, CURLOPT_URL, "http://hydr0g3n.free.fr/nova/nova.py"); - // Define our callback to get called when there's data to be written - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, misc::my_fwrite); - // Set destination file - curl_easy_setopt(curl, CURLOPT_WRITEDATA, file); - // Some SSL mambo jambo - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); - // Perform Download - curl_easy_perform(curl); /* ignores error */ - // Cleanup - curl_easy_cleanup(curl); - // Close tmp file - fclose(file); - version_on_server = getNovaVersion(filePath); - } - if(version_on_server > getNovaVersion(misc::qBittorrentPath()+"nova.py")){ - if(QMessageBox::question(this, - tr("Search plugin update -- qBittorrent"), - tr("Search plugin can be updated, do you want to update it?\n\nChangelog:\n")+getNovaChangelog(filePath), - tr("&Yes"), tr("&No"), - QString(), 0, 1)){ - return; - }else{ - qDebug("Updating search plugin from qbittorrent.org"); - QFile::remove(misc::qBittorrentPath()+"nova.py"); - QFile::copy(filePath, misc::qBittorrentPath()+"nova.py"); - QFile::Permissions perm=QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadUser | QFile::WriteUser | QFile::ExeUser | QFile::ReadGroup | QFile::ReadGroup; - QFile(misc::qBittorrentPath()+"nova.py").setPermissions(perm); - } - }else{ - if(version_on_server == 0.0){ - QMessageBox::information(this, tr("Search plugin update")+" -- "+tr("qBittorrent"), - tr("Sorry, update server is temporarily unavailable.")); - }else{ - QMessageBox::information(this, tr("Search plugin update -- qBittorrent"), - tr("Your search plugin is already up to date.")); - } - } - // Delete tmp file - QFile::remove(filePath); -} - -// Slot called when search is Finished -// Search can be finished for 3 reasons : -// Error | Stopped by user | Finished normally -void GUI::searchFinished(int exitcode,QProcess::ExitStatus){ - QSettings settings("qBittorrent", "qBittorrent"); - int useOSD = settings.value("Options/OSDEnabled", 1).toInt(); - if(useOSD == 1 || (useOSD == 2 && (isMinimized() || isHidden()))) { - myTrayIcon->showMessage(tr("Search Engine"), tr("Search has finished"), QSystemTrayIcon::Information, TIME_TRAY_BALLOON); - } - if(exitcode){ - search_status->setText(tr("An error occured during search...")); - }else{ - if(search_stopped){ - search_status->setText(tr("Search aborted")); - }else{ - if(no_search_results){ - search_status->setText(tr("Search returned no results")); - }else{ - search_status->setText(tr("Search has finished")); - } - } - } - results_lbl->setText(tr("Results", "i.e: Search results")+" ("+QString(misc::toString(nb_search_results).c_str())+"):"); - search_button->setEnabled(true); - stop_search_button->setEnabled(false); -} - -// SLOT to append one line to search results list -// Line is in the following form : -// file url | file name | file size | nb seeds | nb leechers | Search engine url -void GUI::appendSearchResult(const QString& line){ - QStringList parts = line.split("|"); - if(parts.size() != 6){ - return; - } - QString url = parts.takeFirst(); - QString filename = parts.first(); - // XXX: Two results can't have the same name (right?) - if(searchResultsUrls.contains(filename)){ - return; - } - // Add item to search result list - int row = SearchListModel->rowCount(); - SearchListModel->insertRow(row); - for(int i=0; i<5; ++i){ - SearchListModel->setData(SearchListModel->index(row, i), QVariant(parts.at(i))); - } - // Add url to searchResultsUrls associative array - searchResultsUrls.insert(filename, url); - no_search_results = false; - ++nb_search_results; - // Enable clear & download buttons - clear_button->setEnabled(true); - download_button->setEnabled(true); -} - -// Stop search while it is working in background -void GUI::on_stop_search_button_clicked(){ - // Kill process - searchProcess->terminate(); - search_stopped = true; -} - -// Clear search results list -void GUI::on_clear_button_clicked(){ - searchResultsUrls.clear(); - SearchListModel->removeRows(0, SearchListModel->rowCount()); - // Disable clear & download buttons - clear_button->setEnabled(false); - download_button->setEnabled(false); -} - -// Download selected items in search results list -void GUI::on_download_button_clicked(){ - QModelIndexList selectedIndexes = resultsBrowser->selectionModel()->selectedIndexes(); - QModelIndex index; - foreach(index, selectedIndexes){ - if(index.column() == NAME){ - // Get Item url - QString url = searchResultsUrls.value(index.data().toString()); - BTSession.downloadFromUrl(url); - setRowColor(index.row(), "red", false); - } - } -} - - /***************************************************** * * * Utils * @@ -1802,16 +1242,9 @@ void GUI::on_download_button_clicked(){ *****************************************************/ // Set the color of a row in data model -void GUI::setRowColor(int row, const QString& color, bool inDLList){ - if(inDLList){ - for(int i=0; icolumnCount(); ++i){ - DLListModel->setData(DLListModel->index(row, i), QVariant(QColor(color)), Qt::TextColorRole); - } - }else{ - //Search list - for(int i=0; icolumnCount(); ++i){ - SearchListModel->setData(SearchListModel->index(row, i), QVariant(QColor(color)), Qt::TextColorRole); - } +void GUI::setRowColor(int row, const QString& color){ + for(int i=0; icolumnCount(); ++i){ + DLListModel->setData(DLListModel->index(row, i), QVariant(QColor(color)), Qt::TextColorRole); } } diff --git a/src/GUI.h b/src/GUI.h index a2926ed34..73d7547db 100644 --- a/src/GUI.h +++ b/src/GUI.h @@ -23,11 +23,7 @@ #define GUI_H #include -#include #include -#include -#include -#include #include #include @@ -45,15 +41,15 @@ #include "trackerLogin.h" #include "bittorrent.h" -#define TIME_TRAY_BALLOON 5000 - class createtorrent; class QTimer; -class QCompleter; class DLListDelegate; -class SearchListDelegate; class downloadThread; class downloadFromURL; +class SearchEngine; +class QTcpServer; +class QTcpSocket; +class QCloseEvent; using namespace libtorrent; namespace fs = boost::filesystem; @@ -77,24 +73,16 @@ class GUI : public QMainWindow, private Ui::MainWindow{ about *aboutdlg; QStandardItemModel *DLListModel; DLListDelegate *DLDelegate; - QStandardItemModel *SearchListModel; - SearchListDelegate *SearchDelegate; unsigned int nbTorrents; QLabel *connecStatusLblIcon; // Preview previewSelect *previewSelection; QProcess *previewProcess; - // Search related - QHash searchResultsUrls; - QProcess *searchProcess; - bool search_stopped; - bool no_search_results; - QByteArray search_result_line_truncated; - unsigned long nb_search_results; - QTcpServer tcpServer; + // Search + SearchEngine *searchEngine; + // Misc + QTcpServer *tcpServer; QTcpSocket *clientConnection; - QCompleter *searchCompleter; - QStringList searchHistory; protected slots: // GUI related slots @@ -110,18 +98,11 @@ class GUI : public QMainWindow, private Ui::MainWindow{ void openqBTBugTracker(); void readParamsOnSocket(); void acceptConnection(); - void saveCheckedSearchEngines(int) const; void saveColWidthDLList() const; - void saveColWidthSearchList() const; - void loadCheckedSearchEngines(); bool loadColWidthDLList(); - bool loadColWidthSearchList(); void sortDownloadList(int index); void sortDownloadListFloat(int index, Qt::SortOrder sortOrder); void sortDownloadListString(int index, Qt::SortOrder sortOrder); - void sortSearchList(int index); - void sortSearchListInt(int index, Qt::SortOrder sortOrder); - void sortSearchListString(int index, Qt::SortOrder sortOrder); void displayDLListMenu(const QPoint& pos); void selectGivenRow(const QModelIndex& index); void togglePausedState(const QModelIndex& index); @@ -152,21 +133,8 @@ class GUI : public QMainWindow, private Ui::MainWindow{ void processDownloadedFiles(const QString& path, const QString& url); void downloadFromURLList(const QStringList& urls); void displayDownloadingUrlInfos(const QString& url); - // Search slots - void on_search_button_clicked(); - void on_stop_search_button_clicked(); - void on_clear_button_clicked(); - void on_download_button_clicked(); - void on_update_nova_button_clicked(); - void appendSearchResult(const QString& line); - void searchFinished(int exitcode,QProcess::ExitStatus); - void readSearchOutput(); - void searchStarted(); - void downloadSelectedItem(const QModelIndex& index); - void startSearchHistory(); - void saveSearchHistory(); // Utils slots - void setRowColor(int row, const QString& color, bool inDLList=true); + void setRowColor(int row, const QString& color); // Options slots void showOptions(); void OptionsSaved(const QString& info, bool deleteOptions); @@ -193,9 +161,6 @@ class GUI : public QMainWindow, private Ui::MainWindow{ ~GUI(); // Methods int getRowFromHash(const QString& name) const; - float getNovaVersion(const QString& novaPath) const; - QByteArray getNovaChangelog(const QString& novaPath) const; - void updateNova() const; QPoint screenCenter(); }; diff --git a/src/MainWindow.ui b/src/MainWindow.ui index 21c7c72d5..1599d94eb 100644 --- a/src/MainWindow.ui +++ b/src/MainWindow.ui @@ -349,379 +349,6 @@ - - - Search - - - - 9 - - - 6 - - - - - 0 - - - 6 - - - - - - 131 - 132 - - - - - 125 - 132 - - - - Search Engines - - - - 9 - - - 6 - - - - - - - - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - - - 6 - - - - - 0 - - - 6 - - - - - - 16777215 - 35 - - - - - Sans Serif - 9 - 75 - false - true - false - false - - - - Search Pattern: - - - - - - - - 16777215 - 22 - - - - - - - - - 16777215 - 29 - - - - Search - - - - - - - false - - - - 16777215 - 29 - - - - Stop - - - - - - - - - 0 - - - 6 - - - - - - 16777215 - 35 - - - - - Sans Serif - 9 - 75 - false - true - false - false - - - - Status: - - - - - - - - 400 - 0 - - - - - 16777215 - 35 - - - - - Sans Serif - 9 - 50 - true - false - false - false - - - - Stopped - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - 0 - - - 6 - - - - - 0 - - - 6 - - - - - - 16777215 - 20 - - - - - Sans Serif - 9 - 75 - false - true - false - false - - - - Results: - - - - - - - Qt::Horizontal - - - - 721 - 20 - - - - - - - - - - - 0 - 200 - - - - Qt::CustomContextMenu - - - true - - - QAbstractItemView::ExtendedSelection - - - 1 - - - false - - - - - - - 0 - - - 6 - - - - - false - - - Download - - - - - - - false - - - Clear - - - - - - - Qt::Horizontal - - - - 601 - 20 - - - - - - - - Update search plugin - - - - - - - - - @@ -902,22 +529,5 @@ - - - search_pattern - returnPressed() - search_button - click() - - - 405 - 125 - - - 543 - 123 - - - - + diff --git a/src/bittorrent.cpp b/src/bittorrent.cpp index 857da8021..d9466155b 100644 --- a/src/bittorrent.cpp +++ b/src/bittorrent.cpp @@ -18,14 +18,17 @@ * * Contact : chris@qbittorrent.org */ + +#include +#include +#include +#include + #include "bittorrent.h" #include "misc.h" #include "downloadThread.h" #include "UPnP.h" -#include -#include - // Main constructor bittorrent::bittorrent(){ // Supported preview extensions diff --git a/src/bittorrent.h b/src/bittorrent.h index 77babe307..99104c7b0 100644 --- a/src/bittorrent.h +++ b/src/bittorrent.h @@ -21,11 +21,6 @@ #ifndef __BITTORRENT_H__ #define __BITTORRENT_H__ -#include -#include -#include -#include - #include #include #include @@ -43,6 +38,9 @@ #include "deleteThread.h" +class QTimer; +class QString; + using namespace libtorrent; namespace fs = boost::filesystem; diff --git a/src/main.cpp b/src/main.cpp index c049ee300..ede6bcfd6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -26,6 +26,8 @@ #include #include #include +#include +#include #ifdef Q_WS_WIN #include diff --git a/src/search.ui b/src/search.ui new file mode 100644 index 000000000..6abfc1cde --- /dev/null +++ b/src/search.ui @@ -0,0 +1,386 @@ + + search_engine + + + + 0 + 0 + 811 + 453 + + + + Search + + + + 9 + + + 6 + + + + + 0 + + + 6 + + + + + + 131 + 132 + + + + + 125 + 132 + + + + Search Engines + + + + 9 + + + 6 + + + + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + 6 + + + + + 0 + + + 6 + + + + + + 16777215 + 35 + + + + + Sans Serif + 9 + 75 + false + true + false + false + + + + Search Pattern: + + + + + + + + 16777215 + 22 + + + + + + + + + 16777215 + 29 + + + + Search + + + + + + + false + + + + 16777215 + 29 + + + + Stop + + + + + + + + + 0 + + + 6 + + + + + + 16777215 + 35 + + + + + Sans Serif + 9 + 75 + false + true + false + false + + + + Status: + + + + + + + + 400 + 0 + + + + + 16777215 + 35 + + + + + Sans Serif + 9 + 50 + true + false + false + false + + + + Stopped + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + 0 + + + 6 + + + + + 0 + + + 6 + + + + + + 16777215 + 20 + + + + + Sans Serif + 9 + 75 + false + true + false + false + + + + Results: + + + + + + + Qt::Horizontal + + + + 721 + 20 + + + + + + + + + + + 0 + 200 + + + + Qt::CustomContextMenu + + + true + + + QAbstractItemView::ExtendedSelection + + + 1 + + + false + + + + + + + 0 + + + 6 + + + + + false + + + Download + + + + + + + false + + + Clear + + + + + + + Qt::Horizontal + + + + 601 + 20 + + + + + + + + Update search plugin + + + + + + + + + + + + diff --git a/src/searchEngine.cpp b/src/searchEngine.cpp new file mode 100644 index 000000000..97da439cd --- /dev/null +++ b/src/searchEngine.cpp @@ -0,0 +1,607 @@ +/* + * Bittorrent Client using Qt4 and libtorrent. + * Copyright (C) 2006 Christophe Dumez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Contact : chris@qbittorrent.org + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "SearchListDelegate.h" +#include "searchEngine.h" +#include "bittorrent.h" + +#define SEARCH_NAME 0 +#define SEARCH_SIZE 1 +#define SEARCH_SEEDERS 2 +#define SEARCH_LEECHERS 3 +#define SEARCH_ENGINE 4 + +#define SEARCHHISTORY_MAXSIZE 50 + +SearchEngine::SearchEngine(bittorrent *BTSession, QSystemTrayIcon *myTrayIcon) : QWidget(){ + setupUi(this); + this->BTSession = BTSession; + this->myTrayIcon = myTrayIcon; + // Set Search results list model + SearchListModel = new QStandardItemModel(0,5); + SearchListModel->setHeaderData(SEARCH_NAME, Qt::Horizontal, tr("Name", "i.e: file name")); + SearchListModel->setHeaderData(SEARCH_SIZE, Qt::Horizontal, tr("Size", "i.e: file size")); + SearchListModel->setHeaderData(SEARCH_SEEDERS, Qt::Horizontal, tr("Seeders", "i.e: Number of full sources")); + SearchListModel->setHeaderData(SEARCH_LEECHERS, Qt::Horizontal, tr("Leechers", "i.e: Number of partial sources")); + SearchListModel->setHeaderData(SEARCH_ENGINE, Qt::Horizontal, tr("Search engine")); + resultsBrowser->setModel(SearchListModel); + SearchDelegate = new SearchListDelegate(); + resultsBrowser->setItemDelegate(SearchDelegate); + // Make search list header clickable for sorting + resultsBrowser->header()->setClickable(true); + resultsBrowser->header()->setSortIndicatorShown(true); + // Load last columns width for search results list + if(!loadColWidthSearchList()){ + resultsBrowser->header()->resizeSection(0, 275); + } + + // new qCompleter to the search pattern + startSearchHistory(); + searchCompleter = new QCompleter(searchHistory, this); + searchCompleter->setCaseSensitivity(Qt::CaseInsensitive); + search_pattern->setCompleter(searchCompleter); + + // Boolean initialization + search_stopped = false; + // Connect signals to slots (search part) + connect(resultsBrowser, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(downloadSelectedItem(const QModelIndex&))); + connect(resultsBrowser->header(), SIGNAL(sectionPressed(int)), this, SLOT(sortSearchList(int))); + // Creating Search Process + searchProcess = new QProcess(this); + connect(searchProcess, SIGNAL(started()), this, SLOT(searchStarted())); + connect(searchProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(readSearchOutput())); + connect(searchProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(searchFinished(int,QProcess::ExitStatus))); + // Set search engines names + mininova->setText("Mininova"); + piratebay->setText("ThePirateBay"); +// reactor->setText("TorrentReactor"); + isohunt->setText("Isohunt"); +// btjunkie->setText("BTJunkie"); + meganova->setText("Meganova"); + // Check last checked search engines + loadCheckedSearchEngines(); + connect(mininova, SIGNAL(stateChanged(int)), this, SLOT(saveCheckedSearchEngines(int))); + connect(piratebay, SIGNAL(stateChanged(int)), this, SLOT(saveCheckedSearchEngines(int))); +// connect(reactor, SIGNAL(stateChanged(int)), this, SLOT(saveCheckedSearchEngines(int))); + connect(isohunt, SIGNAL(stateChanged(int)), this, SLOT(saveCheckedSearchEngines(int))); +// connect(btjunkie, SIGNAL(stateChanged(int)), this, SLOT(saveCheckedSearchEngines(int))); + connect(meganova, SIGNAL(stateChanged(int)), this, SLOT(saveCheckedSearchEngines(int))); + // Update nova.py search plugin if necessary + updateNova(); +} + +SearchEngine::~SearchEngine(){ + qDebug("Search destruction"); + // save the searchHistory for later uses + saveSearchHistory(); + saveColWidthSearchList(); + searchProcess->kill(); + searchProcess->waitForFinished(); + delete searchProcess; + delete searchCompleter; + delete SearchListModel; + delete SearchDelegate; +} + +// Set the color of a row in data model +void SearchEngine::setRowColor(int row, const QString& color){ + for(int i=0; icolumnCount(); ++i){ + SearchListModel->setData(SearchListModel->index(row, i), QVariant(QColor(color)), Qt::TextColorRole); + } +} + +void SearchEngine::sortSearchList(int index){ + static Qt::SortOrder sortOrder = Qt::AscendingOrder; + if(resultsBrowser->header()->sortIndicatorSection() == index){ + if(sortOrder == Qt::AscendingOrder){ + sortOrder = Qt::DescendingOrder; + }else{ + sortOrder = Qt::AscendingOrder; + } + } + resultsBrowser->header()->setSortIndicator(index, sortOrder); + switch(index){ + //case SIZE: + case SEEDERS: + case LEECHERS: + case SIZE: + sortSearchListInt(index, sortOrder); + break; + default: + sortSearchListString(index, sortOrder); + } +} + +void SearchEngine::sortSearchListInt(int index, Qt::SortOrder sortOrder){ + QList > lines; + // Insertion sorting + for(int i=0; irowCount(); ++i){ + misc::insertSort(lines, QPair(i, SearchListModel->data(SearchListModel->index(i, index)).toLongLong()), sortOrder); + } + // Insert items in new model, in correct order + int nbRows_old = lines.size(); + for(int row=0; rowinsertRow(SearchListModel->rowCount()); + int sourceRow = lines[row].first; + for(int col=0; col<5; ++col){ + SearchListModel->setData(SearchListModel->index(nbRows_old+row, col), SearchListModel->data(SearchListModel->index(sourceRow, col))); + SearchListModel->setData(SearchListModel->index(nbRows_old+row, col), SearchListModel->data(SearchListModel->index(sourceRow, col), Qt::TextColorRole), Qt::TextColorRole); + } + } + // Remove old rows + SearchListModel->removeRows(0, nbRows_old); +} + +void SearchEngine::sortSearchListString(int index, Qt::SortOrder sortOrder){ + QList > lines; + // Insetion sorting + for(int i=0; irowCount(); ++i){ + misc::insertSortString(lines, QPair(i, SearchListModel->data(SearchListModel->index(i, index)).toString()), sortOrder); + } + // Insert items in new model, in correct order + int nbRows_old = lines.size(); + for(int row=0; rowinsertRow(SearchListModel->rowCount()); + int sourceRow = lines[row].first; + for(int col=0; col<5; ++col){ + SearchListModel->setData(SearchListModel->index(nbRows_old+row, col), SearchListModel->data(SearchListModel->index(sourceRow, col))); + SearchListModel->setData(SearchListModel->index(nbRows_old+row, col), SearchListModel->data(SearchListModel->index(sourceRow, col), Qt::TextColorRole), Qt::TextColorRole); + } + } + // Remove old rows + SearchListModel->removeRows(0, nbRows_old); +} + +// Save last checked search engines to a file +void SearchEngine::saveCheckedSearchEngines(int) const{ + QSettings settings("qBittorrent", "qBittorrent"); + settings.beginGroup("SearchEngines"); + settings.setValue("mininova", mininova->isChecked()); + settings.setValue("piratebay", piratebay->isChecked()); + settings.setValue("isohunt", isohunt->isChecked()); + settings.setValue("meganova", meganova->isChecked()); + settings.endGroup(); + qDebug("Saved checked search engines"); +} + +// Save columns width in a file to remember them +// (download list) +void SearchEngine::saveColWidthSearchList() const{ + qDebug("Saving columns width in search list"); + QSettings settings("qBittorrent", "qBittorrent"); + QStringList width_list; + for(int i=0; icolumnCount(); ++i){ + width_list << QString(misc::toString(resultsBrowser->columnWidth(i)).c_str()); + } + settings.setValue("SearchListColsWidth", width_list.join(" ")); + qDebug("Search list columns width saved"); +} + +// Load columns width in a file that were saved previously +// (search list) +bool SearchEngine::loadColWidthSearchList(){ + qDebug("Loading columns width for search list"); + QSettings settings("qBittorrent", "qBittorrent"); + QString line = settings.value("SearchListColsWidth", QString()).toString(); + if(line.isEmpty()) + return false; + QStringList width_list = line.split(' '); + if(width_list.size() != SearchListModel->columnCount()) + return false; + for(int i=0; iheader()->resizeSection(i, width_list.at(i).toInt()); + } + qDebug("Search list columns width loaded"); + return true; +} + +// load last checked search engines from a file +void SearchEngine::loadCheckedSearchEngines(){ + qDebug("Loading checked search engines"); + QSettings settings("qBittorrent", "qBittorrent"); + settings.beginGroup("SearchEngines"); + mininova->setChecked(settings.value("mininova", true).toBool()); + piratebay->setChecked(settings.value("piratebay", false).toBool()); + isohunt->setChecked(settings.value("isohunt", false).toBool()); + meganova->setChecked(settings.value("meganova", false).toBool()); + settings.endGroup(); + qDebug("Loaded checked search engines"); +} + +// get the last searchs from a QSettings to a QStringList +void SearchEngine::startSearchHistory(){ + QSettings settings("qBittorrent", "qBittorrent"); + settings.beginGroup("Search"); + searchHistory = settings.value("searchHistory",-1).toStringList(); + settings.endGroup(); +} + +// Save the history list into the QSettings for the next session +void SearchEngine::saveSearchHistory() +{ + QSettings settings("qBittorrent", "qBittorrent"); + settings.beginGroup("Search"); + settings.setValue("searchHistory",searchHistory); + settings.endGroup(); +} + +// Function called when we click on search button +void SearchEngine::on_search_button_clicked(){ + QString pattern = search_pattern->text().trimmed(); + // No search pattern entered + if(pattern.isEmpty()){ + QMessageBox::critical(0, tr("Empty search pattern"), tr("Please type a search pattern first")); + return; + } + // if the pattern is not in the pattern + if(searchHistory.indexOf(pattern) == -1){ + //update the searchHistory list + searchHistory.append(pattern); + // verify the max size of the history + if(searchHistory.size() > SEARCHHISTORY_MAXSIZE) + searchHistory = searchHistory.mid(searchHistory.size()/2,searchHistory.size()/2); + searchCompleter = new QCompleter(searchHistory, this); + searchCompleter->setCaseSensitivity(Qt::CaseInsensitive); + search_pattern->setCompleter(searchCompleter); + } + + + // Getting checked search engines + if(!mininova->isChecked() && ! piratebay->isChecked()/* && !reactor->isChecked()*/ && !isohunt->isChecked()/* && !btjunkie->isChecked()*/ && !meganova->isChecked()){ + QMessageBox::critical(0, tr("No search engine selected"), tr("You must select at least one search engine.")); + return; + } + QStringList params; + QStringList engineNames; + search_stopped = false; + // Get checked search engines + if(mininova->isChecked()){ + engineNames << "mininova"; + } + if(piratebay->isChecked()){ + engineNames << "piratebay"; + } +// if(reactor->isChecked()){ +// engineNames << "reactor"; +// } + if(isohunt->isChecked()){ + engineNames << "isohunt"; + } +// if(btjunkie->isChecked()){ +// engineNames << "btjunkie"; +// } + if(meganova->isChecked()){ + engineNames << "meganova"; + } + params << engineNames.join(","); + params << pattern.split(" "); + // Update SearchEngine widgets + no_search_results = true; + nb_search_results = 0; + search_result_line_truncated.clear(); + results_lbl->setText(tr("Results")+" (0):"); + // Launch search + searchProcess->start(misc::qBittorrentPath()+"nova.py", params, QIODevice::ReadOnly); +} + +void SearchEngine::searchStarted(){ + // Update SearchEngine widgets + search_button->setEnabled(false); + search_button->repaint(); + search_status->setText(tr("Searching...")); + search_status->repaint(); + stop_search_button->setEnabled(true); + stop_search_button->repaint(); + // clear results window + SearchListModel->removeRows(0, SearchListModel->rowCount()); + // Clear previous results urls too + searchResultsUrls.clear(); +} + +// Download the given item from search results list +void SearchEngine::downloadSelectedItem(const QModelIndex& index){ + int row = index.row(); + // Get Item url + QString url = searchResultsUrls.value(SearchListModel->data(SearchListModel->index(row, NAME)).toString()); + // Download from url + BTSession->downloadFromUrl(url); + // Set item color to RED + setRowColor(row, "red"); +} + +// search Qprocess return output as soon as it gets new +// stuff to read. We split it into lines and add each +// line to search results calling appendSearchResult(). +void SearchEngine::readSearchOutput(){ + QByteArray output = searchProcess->readAllStandardOutput(); + QList lines_list = output.split('\n'); + QByteArray line; + if(!search_result_line_truncated.isEmpty()){ + QByteArray end_of_line = lines_list.takeFirst(); + lines_list.prepend(search_result_line_truncated+end_of_line); + } + search_result_line_truncated = lines_list.takeLast().trimmed(); + foreach(line, lines_list){ + appendSearchResult(QString(line)); + } + results_lbl->setText(tr("Results")+" ("+QString(misc::toString(nb_search_results).c_str())+"):"); +} + +// Returns version of nova.py search engine +float SearchEngine::getNovaVersion(const QString& novaPath) const{ + QFile dest_nova(novaPath); + if(!dest_nova.exists()){ + return 0.0; + } + if(!dest_nova.open(QIODevice::ReadOnly | QIODevice::Text)){ + return 0.0; + } + float version = 0.0; + while (!dest_nova.atEnd()){ + QByteArray line = dest_nova.readLine(); + if(line.startsWith("# Version: ")){ + line = line.split(' ').last(); + line.chop(1); // removes '\n' + version = line.toFloat(); + qDebug("Search plugin version: %.1f", version); + break; + } + } + return version; +} + +// Returns changelog of nova.py search engine +QByteArray SearchEngine::getNovaChangelog(const QString& novaPath) const{ + QFile dest_nova(novaPath); + if(!dest_nova.exists()){ + return QByteArray("None"); + } + if(!dest_nova.open(QIODevice::ReadOnly | QIODevice::Text)){ + return QByteArray("None"); + } + QByteArray changelog; + bool in_changelog = false; + while (!dest_nova.atEnd()){ + QByteArray line = dest_nova.readLine(); + line = line.trimmed(); + if(line.startsWith("# Changelog:")){ + in_changelog = true; + }else{ + if(in_changelog && line.isEmpty()){ + // end of changelog + return changelog; + } + if(in_changelog){ + line.remove(0,1); + changelog.append(line); + } + } + } + return changelog; +} + +// Update nova.py search plugin if necessary +void SearchEngine::updateNova() const{ + qDebug("Updating nova"); + float provided_nova_version = getNovaVersion(":/search_engine/nova.py"); + QFile::Permissions perm=QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadUser | QFile::WriteUser | QFile::ExeUser | QFile::ReadGroup | QFile::ReadGroup; + QFile(misc::qBittorrentPath()+"nova.py").setPermissions(perm); + if(provided_nova_version > getNovaVersion(misc::qBittorrentPath()+"nova.py")){ + qDebug("updating local search plugin with shipped one"); + // nova.py needs update + QFile::remove(misc::qBittorrentPath()+"nova.py"); + qDebug("Old nova removed"); + QFile::copy(":/search_engine/nova.py", misc::qBittorrentPath()+"nova.py"); + qDebug("New nova copied"); + QFile::Permissions perm=QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadUser | QFile::WriteUser | QFile::ExeUser | QFile::ReadGroup | QFile::ReadGroup; + QFile(misc::qBittorrentPath()+"nova.py").setPermissions(perm); + qDebug("local search plugin updated"); + } +} + +// Download nova.py from qbittorrent.org +// Check if our nova.py is outdated and +// ask user for action. +void SearchEngine::on_update_nova_button_clicked(){ + CURL *curl; + QString filePath; + qDebug("Checking for search plugin updates on qbittorrent.org"); + // XXX: Trick to get a unique filename + QTemporaryFile *tmpfile = new QTemporaryFile; + if (tmpfile->open()) { + filePath = tmpfile->fileName(); + } + delete tmpfile; + FILE *file = fopen((const char*)filePath.toUtf8(), "w"); + if(!file){ + std::cerr << "Error: could not open temporary file...\n"; + } + // Initilization required by libcurl + curl = curl_easy_init(); + if(!curl){ + std::cerr << "Error: Failed to init curl...\n"; + fclose(file); + return; + } + // Set url to download + curl_easy_setopt(curl, CURLOPT_URL, "http://www.dchris.eu/nova/nova.zip"); + // Define our callback to get called when there's data to be written + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, misc::my_fwrite); + // Set destination file + curl_easy_setopt(curl, CURLOPT_WRITEDATA, file); + // Some SSL mambo jambo + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); + // Perform Download + curl_easy_perform(curl); /* ignores error */ + // Cleanup + curl_easy_cleanup(curl); + // Close tmp file + fclose(file); + qDebug("Version on qbittorrent.org: %f", getNovaVersion(filePath)); + float version_on_server = getNovaVersion(filePath); + if(version_on_server == 0.0){ + //First server is down, try mirror + QFile::remove(filePath); + FILE *file = fopen((const char*)filePath.toUtf8(), "w"); + if(!file){ + std::cerr << "Error: could not open temporary file...\n"; + } + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, "http://hydr0g3n.free.fr/nova/nova.py"); + // Define our callback to get called when there's data to be written + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, misc::my_fwrite); + // Set destination file + curl_easy_setopt(curl, CURLOPT_WRITEDATA, file); + // Some SSL mambo jambo + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); + // Perform Download + curl_easy_perform(curl); /* ignores error */ + // Cleanup + curl_easy_cleanup(curl); + // Close tmp file + fclose(file); + version_on_server = getNovaVersion(filePath); + } + if(version_on_server > getNovaVersion(misc::qBittorrentPath()+"nova.py")){ + if(QMessageBox::question(this, + tr("Search plugin update -- qBittorrent"), + tr("Search plugin can be updated, do you want to update it?\n\nChangelog:\n")+getNovaChangelog(filePath), + tr("&Yes"), tr("&No"), + QString(), 0, 1)){ + return; + }else{ + qDebug("Updating search plugin from qbittorrent.org"); + QFile::remove(misc::qBittorrentPath()+"nova.py"); + QFile::copy(filePath, misc::qBittorrentPath()+"nova.py"); + QFile::Permissions perm=QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadUser | QFile::WriteUser | QFile::ExeUser | QFile::ReadGroup | QFile::ReadGroup; + QFile(misc::qBittorrentPath()+"nova.py").setPermissions(perm); + } + }else{ + if(version_on_server == 0.0){ + QMessageBox::information(this, tr("Search plugin update")+" -- "+tr("qBittorrent"), + tr("Sorry, update server is temporarily unavailable.")); + }else{ + QMessageBox::information(this, tr("Search plugin update -- qBittorrent"), + tr("Your search plugin is already up to date.")); + } + } + // Delete tmp file + QFile::remove(filePath); +} + +// Slot called when search is Finished +// Search can be finished for 3 reasons : +// Error | Stopped by user | Finished normally +void SearchEngine::searchFinished(int exitcode,QProcess::ExitStatus){ + QSettings settings("qBittorrent", "qBittorrent"); + int useOSD = settings.value("Options/OSDEnabled", 1).toInt(); + if(useOSD == 1 || (useOSD == 2 && (isMinimized() || isHidden()))) { + myTrayIcon->showMessage(tr("Search Engine"), tr("Search has finished"), QSystemTrayIcon::Information, TIME_TRAY_BALLOON); + } + if(exitcode){ + search_status->setText(tr("An error occured during search...")); + }else{ + if(search_stopped){ + search_status->setText(tr("Search aborted")); + }else{ + if(no_search_results){ + search_status->setText(tr("Search returned no results")); + }else{ + search_status->setText(tr("Search has finished")); + } + } + } + results_lbl->setText(tr("Results", "i.e: Search results")+" ("+QString(misc::toString(nb_search_results).c_str())+"):"); + search_button->setEnabled(true); + stop_search_button->setEnabled(false); +} + +// SLOT to append one line to search results list +// Line is in the following form : +// file url | file name | file size | nb seeds | nb leechers | Search engine url +void SearchEngine::appendSearchResult(const QString& line){ + QStringList parts = line.split("|"); + if(parts.size() != 6){ + return; + } + QString url = parts.takeFirst(); + QString filename = parts.first(); + // XXX: Two results can't have the same name (right?) + if(searchResultsUrls.contains(filename)){ + return; + } + // Add item to search result list + int row = SearchListModel->rowCount(); + SearchListModel->insertRow(row); + for(int i=0; i<5; ++i){ + SearchListModel->setData(SearchListModel->index(row, i), QVariant(parts.at(i))); + } + // Add url to searchResultsUrls associative array + searchResultsUrls.insert(filename, url); + no_search_results = false; + ++nb_search_results; + // Enable clear & download buttons + clear_button->setEnabled(true); + download_button->setEnabled(true); +} + +// Stop search while it is working in background +void SearchEngine::on_stop_search_button_clicked(){ + // Kill process + searchProcess->terminate(); + search_stopped = true; +} + +// Clear search results list +void SearchEngine::on_clear_button_clicked(){ + searchResultsUrls.clear(); + SearchListModel->removeRows(0, SearchListModel->rowCount()); + // Disable clear & download buttons + clear_button->setEnabled(false); + download_button->setEnabled(false); +} + +// Download selected items in search results list +void SearchEngine::on_download_button_clicked(){ + QModelIndexList selectedIndexes = resultsBrowser->selectionModel()->selectedIndexes(); + QModelIndex index; + foreach(index, selectedIndexes){ + if(index.column() == NAME){ + // Get Item url + QString url = searchResultsUrls.value(index.data().toString()); + BTSession->downloadFromUrl(url); + setRowColor(index.row(), "red"); + } + } +} diff --git a/src/searchEngine.h b/src/searchEngine.h new file mode 100644 index 000000000..c61da7610 --- /dev/null +++ b/src/searchEngine.h @@ -0,0 +1,84 @@ +/* + * Bittorrent Client using Qt4 and libtorrent. + * Copyright (C) 2006 Christophe Dumez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Contact : chris@qbittorrent.org + */ + +#ifndef SEARCH_H +#define SEARCH_H + +#define TIME_TRAY_BALLOON 5000 + +#include +#include "ui_search.h" + +class QStandardItemModel; +class SearchListDelegate; +class bittorrent; +class QSystemTrayIcon; + +class SearchEngine : public QWidget, public Ui::search_engine{ + Q_OBJECT + + private: + // Search related + QHash searchResultsUrls; + QProcess *searchProcess; + bool search_stopped; + bool no_search_results; + QByteArray search_result_line_truncated; + unsigned long nb_search_results; + QCompleter *searchCompleter; + QStringList searchHistory; + QStandardItemModel *SearchListModel; + SearchListDelegate *SearchDelegate; + bittorrent *BTSession; + QSystemTrayIcon *myTrayIcon; + + public: + SearchEngine(bittorrent *BTSession, QSystemTrayIcon *myTrayIcon); + ~SearchEngine(); + float getNovaVersion(const QString& novaPath) const; + QByteArray getNovaChangelog(const QString& novaPath) const; + bool loadColWidthSearchList(); + + public slots: + // Search slots + void on_search_button_clicked(); + void on_stop_search_button_clicked(); + void on_clear_button_clicked(); + void on_download_button_clicked(); + void on_update_nova_button_clicked(); + void appendSearchResult(const QString& line); + void searchFinished(int exitcode,QProcess::ExitStatus); + void readSearchOutput(); + void setRowColor(int row, const QString& color); + void searchStarted(); + void downloadSelectedItem(const QModelIndex& index); + void startSearchHistory(); + void loadCheckedSearchEngines(); + void updateNova() const; + void saveSearchHistory(); + void saveColWidthSearchList() const; + void saveCheckedSearchEngines(int) const; + void sortSearchList(int index); + void sortSearchListInt(int index, Qt::SortOrder sortOrder); + void sortSearchListString(int index, Qt::SortOrder sortOrder); +}; + +#endif diff --git a/src/src.pro b/src/src.pro index 6432405d2..3a690599a 100644 --- a/src/src.pro +++ b/src/src.pro @@ -11,7 +11,7 @@ TARGET = qbittorrent CONFIG += qt thread x11 network # Update this VERSION for each release -DEFINES += VERSION=\\\"v0.10.0alpha2\\\" +DEFINES += VERSION=\\\"v0.10.0alpha3\\\" DEFINES += VERSION_MAJOR=0 DEFINES += VERSION_MINOR=10 DEFINES += VERSION_BUGFIX=0 @@ -115,16 +115,18 @@ HEADERS += GUI.h misc.h options_imp.h about_imp.h \ PreviewListDelegate.h trackerLogin.h \ downloadThread.h downloadFromURLImp.h \ torrentAddition.h deleteThread.h \ - bittorrent.h + bittorrent.h searchEngine.h FORMS += MainWindow.ui options.ui about.ui \ properties.ui createtorrent.ui preview.ui \ - login.ui downloadFromURL.ui addTorrentDialog.ui + login.ui downloadFromURL.ui addTorrentDialog.ui \ + search.ui SOURCES += GUI.cpp \ main.cpp \ options_imp.cpp \ properties_imp.cpp \ createtorrent_imp.cpp \ - bittorrent.cpp + bittorrent.cpp \ + searchEngine.cpp !contains(DEFINES, NO_UPNP){ message(UPnP Enabled) HEADERS += UPnP.h