/* * 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 #include #include #include #include #include #include #include #include "GUI.h" #include "misc.h" #include "createtorrent_imp.h" #include "properties_imp.h" #include "DLListDelegate.h" #include "SearchListDelegate.h" #include "downloadThread.h" #include "downloadFromURLImp.h" /***************************************************** * * * GUI * * * *****************************************************/ // Constructor GUI::GUI(QWidget *parent, QStringList torrentCmdLine) : QMainWindow(parent){ setupUi(this); setWindowTitle(tr("qBittorrent ")+VERSION); QCoreApplication::setApplicationName("qBittorrent"); loadWindowSize(); s = new session(fingerprint("qB", 0, 7, 0, 0)); //s = new session(fingerprint("AZ", 2, 5, 0, 0)); //Azureus fingerprint // Setting icons this->setWindowIcon(QIcon(QString::fromUtf8(":/Icons/qbittorrent32.png"))); actionOpen->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/open.png"))); actionExit->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/exit.png"))); actionDownload_from_URL->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/url.png"))); actionOptions->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/settings.png"))); actionAbout->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/info.png"))); actionStart->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/play.png"))); actionPause->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/pause.png"))); actionDelete->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/delete.png"))); actionPause_All->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/pause_all.png"))); actionStart_All->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/play_all.png"))); actionClearLog->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/delete.png"))); actionPreview_file->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/preview.png"))); // actionDocumentation->setIcon(QIcon(QString::fromUtf8(":/Icons/help.png"))); actionConnexion_Status->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/disconnected.png"))); actionDelete_All->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/delete_all.png"))); actionTorrent_Properties->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/properties.png"))); 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 toolBar->layout()->setSpacing(7); // Set Download list model DLListModel = new QStandardItemModel(0,7); DLListModel->setHeaderData(NAME, Qt::Horizontal, tr("Name")); DLListModel->setHeaderData(SIZE, Qt::Horizontal, tr("Size")); DLListModel->setHeaderData(PROGRESS, Qt::Horizontal, tr("Progress")); DLListModel->setHeaderData(DLSPEED, Qt::Horizontal, tr("DL Speed")); DLListModel->setHeaderData(UPSPEED, Qt::Horizontal, tr("UP Speed")); DLListModel->setHeaderData(STATUS, Qt::Horizontal, tr("Status")); DLListModel->setHeaderData(ETA, Qt::Horizontal, tr("ETA")); downloadList->setModel(DLListModel); DLDelegate = new DLListDelegate(); downloadList->setItemDelegate(DLDelegate); // Load last columns width for download list if(!loadColWidthDLList()){ downloadList->header()->resizeSection(0, 200); } // creating options options = new options_imp(this); connect(options, SIGNAL(status_changed(const QString&)), this, SLOT(OptionsSaved(const QString&))); // Scan Dir timerScan = new QTimer(this); connect(timerScan, SIGNAL(timeout()), this, SLOT(scanDirectory())); // Set severity level of libtorrent session s->set_severity_level(alert::info); // To avoid some exceptions fs::path::default_name_check(fs::no_check); // DHT (Trackerless) DHTEnabled = false; // Configure BT session according to options configureSession(); s->disable_extensions(); // download thread downloader = new downloadThread(this); connect(downloader, SIGNAL(downloadFinished(QString, QString, int, QString)), this, SLOT(processDownloadedFile(QString, QString, int, QString))); nbTorrents = 0; tabs->setTabText(0, tr("Transfers") +" (0)"); //Resume unfinished torrent downloads resumeUnfinished(); // Add torrent given on command line ProcessParams(torrentCmdLine); // Make download list header clickable for sorting downloadList->header()->setClickable(true); downloadList->header()->setSortIndicatorShown(true); // Connecting Actions to slots connect(actionExit, SIGNAL(triggered()), this, SLOT(close())); connect(actionOpen, SIGNAL(triggered()), this, SLOT(askForTorrents())); connect(actionDelete_All, SIGNAL(triggered()), this, SLOT(deleteAll())); connect(actionDelete, SIGNAL(triggered()), this, SLOT(deleteSelection())); connect(actionOptions, SIGNAL(triggered()), this, SLOT(showOptions())); connect(actionDownload_from_URL, SIGNAL(triggered()), this, SLOT(askForTorrentUrl())); connect(actionPause_All, SIGNAL(triggered()), this, SLOT(pauseAll())); connect(actionStart_All, SIGNAL(triggered()), this, SLOT(startAll())); connect(actionPause, SIGNAL(triggered()), this, SLOT(pauseSelection())); connect(actionTorrent_Properties, SIGNAL(triggered()), this, SLOT(propertiesSelection())); connect(actionStart, SIGNAL(triggered()), this, SLOT(startSelection())); connect(actionAbout, SIGNAL(triggered()), this, SLOT(showAbout())); connect(actionCreate_torrent, SIGNAL(triggered()), this, SLOT(showCreateWindow())); connect(actionClearLog, SIGNAL(triggered()), this, SLOT(clearLog())); connect(downloadList, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(togglePausedState(const QModelIndex&))); connect(downloadList->header(), SIGNAL(sectionPressed(int)), this, SLOT(sortDownloadList(int))); connect(downloadList, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(displayDLListMenu(const QPoint&))); connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(displayGUIMenu(const QPoint&))); connect(actionPreview_file, SIGNAL(triggered()), this, SLOT(previewFileSelection())); connect(infoBar, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(displayInfoBarMenu(const QPoint&))); // Create tray icon if (!QSystemTrayIcon::isSystemTrayAvailable()){ std::cerr << "Error: System tray unavailable\n"; } myTrayIcon = new QSystemTrayIcon(QIcon(":/Icons/qbittorrent22.png"), this); // Start download list refresher refresher = new QTimer(this); connect(refresher, SIGNAL(timeout()), this, SLOT(updateDlList())); refresher->start(2000); // Center window centerWindow(); // Tray icon Menu myTrayIconMenu = new QMenu(this); myTrayIconMenu->addAction(actionOpen); myTrayIconMenu->addAction(actionDownload_from_URL); myTrayIconMenu->addSeparator(); myTrayIconMenu->addAction(actionStart_All); myTrayIconMenu->addAction(actionPause_All); myTrayIconMenu->addSeparator(); myTrayIconMenu->addAction(actionExit); myTrayIcon->setContextMenu(myTrayIconMenu); connect(myTrayIcon, SIGNAL(messageClicked()), this, SLOT(balloonClicked())); // End of Icon Menu connect(myTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(toggleVisibility(QSystemTrayIcon::ActivationReason))); myTrayIcon->show(); // Use a tcp server to allow only one instance of qBittorrent tcpServer = new QTcpServer(this); 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(AnotherInstanceConnected())); // 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")); SearchListModel->setHeaderData(SIZE, Qt::Horizontal, tr("Size")); SearchListModel->setHeaderData(PROGRESS, Qt::Horizontal, tr("Seeders")); SearchListModel->setHeaderData(DLSPEED, Qt::Horizontal, tr("Leechers")); 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); } // 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(); // Supported preview extensions // XXX: might be incomplete supported_preview_extensions<<"AVI"<<"DIVX"<<"MPG"<<"MPEG"<<"MP3"<<"OGG"<<"WMV"<<"WMA"<<"RMV"<<"RMVB"<<"ASF"<<"MOV"<<"WAV"<<"MP2"<<"SWF"<<"AC3"; previewProcess = new QProcess(this); connect(previewProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(cleanTempPreviewFile(int, QProcess::ExitStatus))); // Accept drag 'n drops setAcceptDrops(true); // Set info Bar infos setInfoBar(tr("qBittorrent ")+VERSION+tr(" started.")); qDebug("GUI Built"); } // Destructor GUI::~GUI(){ delete options; delete checkConnect; delete timerScan; delete searchProcess; delete refresher; delete myTrayIcon; delete myTrayIconMenu; delete tcpServer; delete DLDelegate; delete DLListModel; delete SearchListModel; delete SearchDelegate; delete previewProcess; delete downloader; delete s; } // Update Info Bar information void GUI::setInfoBar(const QString& info, const QString& color){ static unsigned short nbLines = 0; ++nbLines; // Check log size, clear it if too big if(nbLines > 200){ infoBar->clear(); nbLines = 1; } infoBar->append(""+ QTime::currentTime().toString("hh:mm:ss") + " - " + info + ""); } void GUI::balloonClicked(){ if(isHidden()){ show(); if(isMinimized()){ showNormal(); } raise(); activateWindow(); } } // Buggy with Qt 4.1.2 : should try with another version // void GUI::readParamsOnSocket(){ // QTcpSocket *clientConnection = tcpServer->nextPendingConnection(); // if(clientConnection != 0){ // connect(clientConnection, SIGNAL(disconnected()), clientConnection, SLOT(deleteLater())); // std::cout << "reading...\n"; // while (clientConnection->bytesAvailable() < (int)sizeof(quint16)) { // std::cout << "reading size chunk\n"; // if (!clientConnection->waitForReadyRead(5000)) { // std::cout << clientConnection->errorString().toStdString() << '\n'; // return; // } // } // quint16 blockSize; // QDataStream in(clientConnection); // in.setVersion(QDataStream::Qt_4_0); // in >> blockSize; // while (clientConnection->bytesAvailable() < blockSize) { // if (!clientConnection->waitForReadyRead(5000)) { // std::cout << clientConnection->errorString().toStdString() << '\n'; // return; // } // } // QString params; // in >> params; // std::cout << params.toStdString() << '\n'; // clientConnection->disconnectFromHost(); // } // std::cout << "end reading\n"; // } void GUI::AnotherInstanceConnected(){ clientConnection = tcpServer->nextPendingConnection(); if(clientConnection != 0){ connect(clientConnection, SIGNAL(disconnected()), this, SLOT(readParamsInFile())); } } void GUI::togglePausedState(const QModelIndex& index){ int row = index.row(); QString fileName = DLListModel->data(DLListModel->index(row, NAME)).toString(); torrent_handle h = handles.value(fileName); if(h.is_paused()){ startSelection(); }else{ pauseSelection(); } } void GUI::previewFileSelection(){ QModelIndex index; QModelIndexList selectedIndexes = downloadList->selectionModel()->selectedIndexes(); foreach(index, selectedIndexes){ if(index.column() == NAME){ // Get the file name QString fileName = index.data().toString(); torrent_handle h = handles.value(fileName); previewSelection = new previewSelect(this, h); break; } } } void GUI::cleanTempPreviewFile(int, QProcess::ExitStatus){ QFile::remove(QDir::tempPath()+QDir::separator()+"qBT_preview.tmp"); } void GUI::displayDLListMenu(const QPoint& pos){ QMenu myDLLlistMenu(this); // Clear selection downloadList->clearSelection(); // Select items QModelIndex index = downloadList->indexAt(pos); selectGivenRow(index); // Enable/disable pause/start action given the DL state QModelIndexList selectedIndexes = downloadList->selectionModel()->selectedIndexes(); foreach(index, selectedIndexes){ if(index.column() == NAME){ // Get the file name QString fileName = index.data().toString(); // Get handle and pause the torrent torrent_handle h = handles.value(fileName); if(h.is_paused()){ myDLLlistMenu.addAction(actionStart); }else{ myDLLlistMenu.addAction(actionPause); } myDLLlistMenu.addAction(actionDelete); myDLLlistMenu.addAction(actionTorrent_Properties); if(!options->getPreviewProgram().isEmpty() && isFilePreviewPossible(h)){ myDLLlistMenu.addAction(actionPreview_file); } break; } } // Call menu // XXX: why mapToGlobal() is not enough? myDLLlistMenu.exec(mapToGlobal(pos)+QPoint(22,180)); } void GUI::displayGUIMenu(const QPoint& pos){ QMenu myGUIMenu(this); myGUIMenu.addAction(actionOpen); myGUIMenu.addAction(actionDownload_from_URL); myGUIMenu.addAction(actionStart_All); myGUIMenu.addAction(actionPause_All); myGUIMenu.addAction(actionDelete_All); myGUIMenu.addAction(actionExit); myGUIMenu.exec(mapToGlobal(pos)); } void GUI::previewFile(const QString& filePath){ // Check if there is already one preview running if(previewProcess->state() == QProcess::NotRunning){ // First copy temporarily QString tmpPath = QDir::tempPath()+QDir::separator()+"qBT_preview.tmp"; QFile::remove(tmpPath); QFile::copy(filePath, tmpPath); // Launch program preview QStringList params; params << tmpPath; previewProcess->start(options->getPreviewProgram(), params, QIODevice::ReadOnly); }else{ QMessageBox::critical(0, tr("Preview process already running"), tr("There is already another preview process running.\nPlease close the other one first.")); } } void GUI::selectGivenRow(const QModelIndex& index){ int row = index.row(); for(int i=0; icolumnCount(); ++i){ downloadList->selectionModel()->select(DLListModel->index(row, i), QItemSelectionModel::Select); } } // Called when the other instance is disconnected // Means the file is written void GUI::readParamsInFile(){ QFile paramsFile(QDir::tempPath()+QDir::separator()+"qBT-params.txt"); if (!paramsFile.open(QIODevice::ReadOnly | QIODevice::Text)){ paramsFile.remove(); return; } QStringList params; while (!paramsFile.atEnd()) { QByteArray line = paramsFile.readLine(); if(line.at(line.size()-1) == '\n'){ line.truncate(line.size()-1); } params << line; } if(params.size()){ qDebug("Received parameters from another instance"); addTorrents(params); } paramsFile.close(); paramsFile.remove(); } void GUI::clearLog(){ infoBar->clear(); } void GUI::displayInfoBarMenu(const QPoint& pos){ // Log Menu QMenu myLogMenu(this); myLogMenu.addAction(actionClearLog); // XXX: Why mapToGlobal() is not enough? myLogMenu.exec(mapToGlobal(pos)+QPoint(22,383)); } // get information from torrent handles and // update download list accordingly void GUI::updateDlList(){ torrent_handle h; char tmp[MAX_CHAR_TMP]; char tmp2[MAX_CHAR_TMP]; // update global informations session_status sessionStatus = s->status(); snprintf(tmp, MAX_CHAR_TMP, "%.1f", sessionStatus.payload_upload_rate/1024.); snprintf(tmp2, MAX_CHAR_TMP, "%.1f", sessionStatus.payload_download_rate/1024.); myTrayIcon->setToolTip(tr("qBittorrent
DL Speed: ")+ QString(tmp2) +tr("KiB/s")+"
"+tr("UP Speed: ")+ QString(tmp) + tr("KiB/s")); // tray icon if(isMinimized() || isHidden() || tabs->currentIndex()){ // No need to update if qBittorrent DL list is hidden return; } LCD_UpSpeed->display(tmp); // UP LCD LCD_DownSpeed->display(tmp2); // DL LCD // browse handles foreach(h, handles){ try{ torrent_status torrentStatus = h.status(); QString fileName = QString(h.get_torrent_info().name().c_str()); if(!h.is_paused()){ int row = getRowFromName(fileName); if(row == -1){ std::cerr << "Error: Could not find filename in download list..\n"; continue; } // Parse download state torrent_info ti = h.get_torrent_info(); // Setting download state switch(torrentStatus.state){ case torrent_status::finished: case torrent_status::seeding: DLListModel->setData(DLListModel->index(row, UPSPEED), QVariant((double)torrentStatus.upload_payload_rate)); DLListModel->setData(DLListModel->index(row, STATUS), QVariant(tr("Finished"))); DLListModel->setData(DLListModel->index(row, DLSPEED), QVariant((double)0.)); DLListModel->setData(DLListModel->index(row, ETA), QVariant((qlonglong)-1)); DLListModel->setData(DLListModel->index(row, NAME), QVariant(QIcon(":/Icons/skin/seeding.png")), Qt::DecorationRole); DLListModel->setData(DLListModel->index(row, PROGRESS), QVariant((double)1.)); setRowColor(row, "orange"); break; case torrent_status::checking_files: case torrent_status::queued_for_checking: DLListModel->setData(DLListModel->index(row, STATUS), QVariant(tr("Checking..."))); DLListModel->setData(DLListModel->index(row, NAME), QVariant(QIcon(":/Icons/skin/connecting.png")), Qt::DecorationRole); setRowColor(row, "grey"); DLListModel->setData(DLListModel->index(row, PROGRESS), QVariant((double)torrentStatus.progress)); break; case torrent_status::connecting_to_tracker: if(torrentStatus.download_payload_rate > 0){ // Display "Downloading" status when connecting if download speed > 0 DLListModel->setData(DLListModel->index(row, STATUS), QVariant(tr("Downloading..."))); DLListModel->setData(DLListModel->index(row, ETA), QVariant((qlonglong)((ti.total_size()-torrentStatus.total_done)/(double)torrentStatus.download_payload_rate))); DLListModel->setData(DLListModel->index(row, NAME), QVariant(QIcon(":/Icons/skin/downloading.png")), Qt::DecorationRole); setRowColor(row, "green"); }else{ DLListModel->setData(DLListModel->index(row, STATUS), QVariant(tr("Connecting..."))); DLListModel->setData(DLListModel->index(row, ETA), QVariant((qlonglong)-1)); DLListModel->setData(DLListModel->index(row, NAME), QVariant(QIcon(":/Icons/skin/connecting.png")), Qt::DecorationRole); setRowColor(row, "grey"); } DLListModel->setData(DLListModel->index(row, PROGRESS), QVariant((double)torrentStatus.progress)); DLListModel->setData(DLListModel->index(row, DLSPEED), QVariant((double)torrentStatus.download_payload_rate)); DLListModel->setData(DLListModel->index(row, UPSPEED), QVariant((double)torrentStatus.upload_payload_rate)); break; case torrent_status::downloading: case torrent_status::downloading_metadata: if(torrentStatus.download_payload_rate > 0){ DLListModel->setData(DLListModel->index(row, STATUS), QVariant(tr("Downloading..."))); DLListModel->setData(DLListModel->index(row, NAME), QVariant(QIcon(":/Icons/skin/downloading.png")), Qt::DecorationRole); DLListModel->setData(DLListModel->index(row, ETA), QVariant((qlonglong)((ti.total_size()-torrentStatus.total_done)/(double)torrentStatus.download_payload_rate))); setRowColor(row, "green"); }else{ DLListModel->setData(DLListModel->index(row, STATUS), QVariant(tr("Stalled", "state of a torrent whose DL Speed is 0"))); DLListModel->setData(DLListModel->index(row, NAME), QVariant(QIcon(":/Icons/skin/stalled.png")), Qt::DecorationRole); DLListModel->setData(DLListModel->index(row, ETA), QVariant((qlonglong)-1)); setRowColor(row, "black"); } DLListModel->setData(DLListModel->index(row, PROGRESS), QVariant((double)torrentStatus.progress)); DLListModel->setData(DLListModel->index(row, DLSPEED), QVariant((double)torrentStatus.download_payload_rate)); DLListModel->setData(DLListModel->index(row, UPSPEED), QVariant((double)torrentStatus.upload_payload_rate)); break; default: DLListModel->setData(DLListModel->index(row, ETA), QVariant((qlonglong)-1)); } } }catch(invalid_handle e){ continue; } } } bool GUI::isFilePreviewPossible(const torrent_handle& h) const{ // See if there are supported files in the torrent torrent_info torrentInfo = h.get_torrent_info(); for(int i=0; i= 0){ return true; } } return false; } void GUI::sortDownloadListFloat(int index, Qt::SortOrder sortOrder){ QList > lines; // insertion sorting for(int i=0; irowCount(); ++i){ misc::insertSort(lines, QPair(i, DLListModel->data(DLListModel->index(i, index)).toDouble()), sortOrder); } // Insert items in new model, in correct order int nbRows_old = lines.size(); for(int row=0; rowinsertRow(DLListModel->rowCount()); int sourceRow = lines[row].first; for(int col=0; col<7; ++col){ DLListModel->setData(DLListModel->index(nbRows_old+row, col), DLListModel->data(DLListModel->index(sourceRow, col))); DLListModel->setData(DLListModel->index(nbRows_old+row, col), DLListModel->data(DLListModel->index(sourceRow, col), Qt::DecorationRole), Qt::DecorationRole); DLListModel->setData(DLListModel->index(nbRows_old+row, col), DLListModel->data(DLListModel->index(sourceRow, col), Qt::TextColorRole), Qt::TextColorRole); } } // Remove old rows DLListModel->removeRows(0, nbRows_old); } void GUI::sortDownloadListString(int index, Qt::SortOrder sortOrder){ QList > lines; // Insertion sorting for(int i=0; irowCount(); ++i){ misc::insertSortString(lines, QPair(i, DLListModel->data(DLListModel->index(i, index)).toString()), sortOrder); } // Insert items in new model, in correct order int nbRows_old = lines.size(); for(int row=0; rowinsertRow(DLListModel->rowCount()); int sourceRow = lines[row].first; for(int col=0; col<7; ++col){ DLListModel->setData(DLListModel->index(nbRows_old+row, col), DLListModel->data(DLListModel->index(sourceRow, col))); DLListModel->setData(DLListModel->index(nbRows_old+row, col), DLListModel->data(DLListModel->index(sourceRow, col), Qt::DecorationRole), Qt::DecorationRole); DLListModel->setData(DLListModel->index(nbRows_old+row, col), DLListModel->data(DLListModel->index(sourceRow, col), Qt::TextColorRole), Qt::TextColorRole); } } // Remove old rows DLListModel->removeRows(0, nbRows_old); } void GUI::sortDownloadList(int index){ static Qt::SortOrder sortOrder = Qt::AscendingOrder; if(downloadList->header()->sortIndicatorSection() == index){ if(sortOrder == Qt::AscendingOrder){ sortOrder = Qt::DescendingOrder; }else{ sortOrder = Qt::AscendingOrder; } } downloadList->header()->setSortIndicator(index, sortOrder); switch(index){ case SIZE: case ETA: case UPSPEED: case DLSPEED: case PROGRESS: sortDownloadListFloat(index, sortOrder); break; default: sortDownloadListString(index, sortOrder); } } 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){ if(isHidden()){ show(); if(isMinimized()){ if(isMaximized()){ showMaximized(); }else{ showNormal(); } } raise(); activateWindow(); }else{ hide(); } } } // Center window void GUI::centerWindow(){ int scrn = 0; QWidget *w = this->topLevelWidget(); if(w) scrn = QApplication::desktop()->screenNumber(w); else if(QApplication::desktop()->isVirtualDesktop()) scrn = QApplication::desktop()->screenNumber(QCursor::pos()); else scrn = QApplication::desktop()->screenNumber(this); QRect desk(QApplication::desktop()->availableGeometry(scrn)); this->move((desk.width() - this->frameGeometry().width()) / 2, (desk.height() - this->frameGeometry().height()) / 2); } void GUI::saveWindowSize() const{ QFile lastWindowSize(misc::qBittorrentPath()+"lastWindowSize.txt"); // delete old file lastWindowSize.remove(); if(lastWindowSize.open(QIODevice::WriteOnly | QIODevice::Text)){ lastWindowSize.write(QByteArray((misc::toString(this->size().width())+"\n").c_str())); lastWindowSize.write(QByteArray((misc::toString(this->size().height())+"\n").c_str())); lastWindowSize.close(); qDebug("Saved window size"); }else{ std::cerr << "Error: Could not save last windows size\n"; } } bool GUI::loadFilteredFiles(torrent_handle &h){ bool has_filtered_files = false; torrent_info torrentInfo = h.get_torrent_info(); QString fileName = QString(torrentInfo.name().c_str()); QFile pieces_file(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+fileName+".pieces"); // Read saved file if(!pieces_file.open(QIODevice::ReadOnly | QIODevice::Text)){ return has_filtered_files; } QByteArray pieces_selection = pieces_file.readAll(); pieces_file.close(); QList pieces_selection_list = pieces_selection.split('\n'); if(pieces_selection_list.size() != torrentInfo.num_files()+1){ std::cerr << "Error: Corrupted pieces file\n"; return has_filtered_files; } std::vector selectionBitmask; for(int i=0; i 1){ isFiltered = 0; } selectionBitmask.push_back(isFiltered); // h.filter_piece(i, isFiltered); if(isFiltered){ has_filtered_files = true; } } h.filter_files(selectionBitmask); return has_filtered_files; } bool GUI::hasFilteredFiles(const QString& fileName){ QFile pieces_file(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+fileName+".pieces"); // Read saved file if(!pieces_file.open(QIODevice::ReadOnly | QIODevice::Text)){ return false; } QByteArray pieces_selection = pieces_file.readAll(); pieces_file.close(); QList pieces_selection_list = pieces_selection.split('\n'); for(int i=0; i 1){ isFiltered = 0; } if(isFiltered){ return true; } } return false; } void GUI::loadWindowSize(){ qDebug("Loading window size"); QFile lastWindowSize(misc::qBittorrentPath()+"lastWindowSize.txt"); if(lastWindowSize.exists()){ if(lastWindowSize.open(QIODevice::ReadOnly | QIODevice::Text)){ int w, h; QByteArray line; // read width line = lastWindowSize.readLine(); // remove '\n' if(line.at(line.size()-1) == '\n'){ line.truncate(line.size()-1); } w = line.toInt(); // read height line = lastWindowSize.readLine(); // remove '\n' if(line.at(line.size()-1) == '\n'){ line.truncate(line.size()-1); } h = line.toInt(); lastWindowSize.close(); // Apply new size if(w && h){ this->resize(QSize(w, h)); qDebug("Window size loaded"); } } } } // Save last checked search engines to a file void GUI::saveCheckedSearchEngines(int) const{ qDebug("Saving checked window size"); QFile lastSearchEngines(misc::qBittorrentPath()+"lastSearchEngines.txt"); // delete old file lastSearchEngines.remove(); if(lastSearchEngines.open(QIODevice::WriteOnly | QIODevice::Text)){ if(mininova->isChecked()) lastSearchEngines.write(QByteArray("mininova\n")); if(piratebay->isChecked()) lastSearchEngines.write(QByteArray("piratebay\n")); // if(reactor->isChecked()) // lastSearchEngines.write(QByteArray("reactor\n")); if(isohunt->isChecked()) lastSearchEngines.write(QByteArray("isohunt\n")); // if(btjunkie->isChecked()) // lastSearchEngines.write(QByteArray("btjunkie\n")); if(meganova->isChecked()) lastSearchEngines.write(QByteArray("meganova\n")); lastSearchEngines.close(); qDebug("Saved checked search engines"); }else{ std::cerr << "Error: Could not save last checked search engines\n"; } } // Save columns width in a file to remember them // (download list) void GUI::saveColWidthDLList() const{ qDebug("Saving columns width in download list"); QFile lastDLListWidth(misc::qBittorrentPath()+"lastDLListWidth.txt"); // delete old file lastDLListWidth.remove(); QStringList width_list; for(int i=0; icolumnCount(); ++i){ width_list << QString(misc::toString(downloadList->columnWidth(i)).c_str()); } if(lastDLListWidth.open(QIODevice::WriteOnly | QIODevice::Text)){ lastDLListWidth.write(QByteArray(width_list.join(" ").toStdString().c_str())); lastDLListWidth.close(); qDebug("Columns width saved"); }else{ std::cerr << "Error: Could not save last columns width in download list\n"; } } // Load columns width in a file that were saved previously // (download list) bool GUI::loadColWidthDLList(){ qDebug("Loading colums width in download list"); QFile lastDLListWidth(misc::qBittorrentPath()+"lastDLListWidth.txt"); if(lastDLListWidth.exists()){ if(lastDLListWidth.open(QIODevice::ReadOnly | QIODevice::Text)){ QByteArray line = lastDLListWidth.readLine(); lastDLListWidth.close(); line.chop(1); QStringList width_list = QString(line).split(' '); if(width_list.size() != DLListModel-> columnCount()){ return false; } for(int i=0; iheader()->resizeSection(i, width_list.at(i).toInt()); } qDebug("Loaded columns width in download list"); return true; }else{ std::cerr << "Error: Could not load last columns width for download list\n"; return false; } } return false; } // Save columns width in a file to remember them // (download list) void GUI::saveColWidthSearchList() const{ qDebug("Saving columns width in search list"); QFile lastSearchListWidth(misc::qBittorrentPath()+"lastSearchListWidth.txt"); // delete old file lastSearchListWidth.remove(); QStringList width_list; for(int i=0; icolumnCount(); ++i){ width_list << QString(misc::toString(resultsBrowser->columnWidth(i)).c_str()); } if(lastSearchListWidth.open(QIODevice::WriteOnly | QIODevice::Text)){ lastSearchListWidth.write(QByteArray(width_list.join(" ").toStdString().c_str())); lastSearchListWidth.close(); qDebug("Columns width saved in search list"); }else{ std::cerr << "Error: Could not save last columns width in search results list\n"; } } // Load columns width in a file that were saved previously // (search list) bool GUI::loadColWidthSearchList(){ qDebug("Loading column width in search list"); QFile lastSearchListWidth(misc::qBittorrentPath()+"lastSearchListWidth.txt"); if(lastSearchListWidth.exists()){ if(lastSearchListWidth.open(QIODevice::ReadOnly | QIODevice::Text)){ QByteArray line = lastSearchListWidth.readLine(); lastSearchListWidth.close(); line.chop(1); QStringList width_list = QString(line).split(' '); if(width_list.size() != SearchListModel-> columnCount()){ return false; } for(int i=0; iheader()->resizeSection(i, width_list.at(i).toInt()); } qDebug("Columns width loaded in search list"); return true; }else{ std::cerr << "Error: Could not load last columns width for search results list\n"; return false; } } return false; } // load last checked search engines from a file void GUI::loadCheckedSearchEngines(){ qDebug("Loading checked search engines"); QFile lastSearchEngines(misc::qBittorrentPath()+"lastSearchEngines.txt"); QStringList searchEnginesList; if(lastSearchEngines.exists()){ if(lastSearchEngines.open(QIODevice::ReadOnly | QIODevice::Text)){ QByteArray searchEngine; while(!lastSearchEngines.atEnd()){ searchEngine = lastSearchEngines.readLine(); searchEnginesList << QString(searchEngine.data()); } lastSearchEngines.close(); if(searchEnginesList.indexOf("mininova\n") != -1){ mininova->setChecked(true); }else{ mininova->setChecked(false); } if(searchEnginesList.indexOf("piratebay\n") != -1){ piratebay->setChecked(true); }else{ piratebay->setChecked(false); } // if(searchEnginesList.indexOf("reactor\n") != -1){ // reactor->setChecked(true); // }else{ // reactor->setChecked(false); // } if(searchEnginesList.indexOf("isohunt\n") != -1){ isohunt->setChecked(true); }else{ isohunt->setChecked(false); } // if(searchEnginesList.indexOf("btjunkie\n") != -1){ // btjunkie->setChecked(true); // }else{ // btjunkie->setChecked(false); // } if(searchEnginesList.indexOf("meganova\n") != -1){ meganova->setChecked(true); }else{ meganova->setChecked(false); } qDebug("Checked search engines loaded"); }else{ std::cerr << "Error: Could not load last checked search engines\n"; } } } // Display About Dialog void GUI::showAbout(){ //About dialog aboutdlg = new about(this); } // Called when we close the program void GUI::closeEvent(QCloseEvent *e){ if(options->getConfirmOnExit()){ if(QMessageBox::question(this, tr("Are you sure you want to quit? -- qBittorrent"), tr("Are you sure you want to quit qbittorrent?"), tr("&Yes"), tr("&No"), QString(), 0, 1)){ e->ignore(); return; } } // Save DHT entry if(DHTEnabled){ try{ entry dht_state = s->dht_state(); boost::filesystem::ofstream out(misc::qBittorrentPath().toStdString()+"dht_state", std::ios_base::binary); out.unsetf(std::ios_base::skipws); bencode(std::ostream_iterator(out), dht_state); }catch (std::exception& e){ std::cerr << e.what() << "\n"; } } // Save window size, columns size saveWindowSize(); saveColWidthDLList(); saveColWidthSearchList(); // Create fast resume data saveFastResumeData(); // Hide tray icon myTrayIcon->hide(); // Accept exit e->accept(); } // Display window to create a torrent void GUI::showCreateWindow(){ createWindow = new createtorrent(this); } // Called when we minimize the program void GUI::hideEvent(QHideEvent *e){ if(options->getGoToSysTrayOnMinimizingWindow()){ // Hide window hide(); } // Accept exit e->accept(); } // Action executed when a file is dropped void GUI::dropEvent(QDropEvent *event){ event->acceptProposedAction(); QStringList files=event->mimeData()->text().split('\n'); // Add file to download list addTorrents(files); } // Decode if we accept drag 'n drop or not void GUI::dragEnterEvent(QDragEnterEvent *event){ if (event->mimeData()->hasFormat("text/plain")){ event->acceptProposedAction(); } } /***************************************************** * * * Torrent * * * *****************************************************/ // Display a dialog to allow user to add // torrents to download list void GUI::askForTorrents(){ QStringList pathsList; QString lastDir = misc::qBittorrentPath()+"lastDir.txt"; QString dirPath=QDir::homePath(); QFile lastDirFile(lastDir); // Load remembered last dir if(lastDirFile.exists()){ lastDirFile.open(QIODevice::ReadOnly | QIODevice::Text); dirPath=lastDirFile.readLine(); lastDirFile.close(); } // Open File Open Dialog // Note: it is possible to select more than one file pathsList = QFileDialog::getOpenFileNames(this, tr("Open Torrent Files"), dirPath, tr("Torrent Files")+" (*.torrent)"); if(!pathsList.empty()){ //Add torrents to download list addTorrents(pathsList); // Save last dir to remember it lastDirFile.open(QIODevice::WriteOnly | QIODevice::Text); QStringList top_dir = pathsList.at(0).split(QDir::separator()); top_dir.removeLast(); lastDirFile.write(QByteArray(top_dir.join(QDir::separator()).toStdString().c_str())); lastDirFile.close(); } } // Scan the first level of the directory for torrent files // and add them to download list void GUI::scanDirectory(){ QString dirText = options->getScanDir(); QString file; if(!dirText.isNull()){ QStringList to_add; QDir dir(dirText); QStringList files = dir.entryList(QDir::Files, QDir::Unsorted); foreach(file, files){ QString fullPath = dir.path()+QDir::separator()+file; if(fullPath.endsWith(".torrent")){ to_add << fullPath; } } if(!to_add.empty()){ addTorrents(to_add, true); } } } void GUI::saveFastResumeData() const{ qDebug("Saving fast resume data"); std::string file; QDir torrentBackup(misc::qBittorrentPath() + "BT_backup"); // Checking if torrentBackup Dir exists // create it if it is not if(! torrentBackup.exists()){ torrentBackup.mkpath(torrentBackup.path()); } // Write fast resume data foreach(torrent_handle h, handles){ // Pause download (needed before fast resume writing) h.pause(); // Extracting resume data if (h.has_metadata()){ QString filename = QString(h.get_torrent_info().name().c_str()); if(QFile::exists(torrentBackup.path()+QDir::separator()+filename+".torrent")){ // Remove old .fastresume data in case it exists QFile::remove(filename + ".fastresume"); // Write fast resume data entry resumeData = h.write_resume_data(); file = filename.toStdString() + ".fastresume"; boost::filesystem::ofstream out(fs::path(torrentBackup.path().toStdString()) / file, std::ios_base::binary); out.unsetf(std::ios_base::skipws); bencode(std::ostream_iterator(out), resumeData); } } // Remove torrent s->remove_torrent(h); } qDebug("Fast resume data saved"); } // delete All Downloads in the list void GUI::deleteAll(){ QDir torrentBackup(misc::qBittorrentPath() + "BT_backup"); QStringList torrents; QString scan_dir = options->getScanDir(); bool isScanningDir = !scan_dir.isNull(); if(isScanningDir && scan_dir.at(scan_dir.length()-1) != QDir::separator()){ scan_dir += QDir::separator(); } if(QMessageBox::question( this, tr("Are you sure? -- qBittorrent"), tr("Are you sure you want to delete all files in download list?"), tr("&Yes"), tr("&No"), QString(), 0, 1) == 0) { // User clicked YES // Remove torrents from BT session foreach(torrent_handle h, handles){ // remove it from scan dir or it will start again if(isScanningDir){ QFile::remove(scan_dir+QString(h.get_torrent_info().name().c_str())+".torrent"); } // remove it from session s->remove_torrent(h); } // Clear handles handles.clear(); // Clear torrent backup directory torrents = torrentBackup.entryList(); QString torrent; foreach(torrent, torrents){ if(torrent.endsWith(".fastresume") || torrent.endsWith(".torrent") || torrent.endsWith(".pieces") || torrent.endsWith(".paused") || torrent.endsWith(".incremental")){ torrentBackup.remove(torrent); } } // Clear Download list DLListModel->removeRows(0, DLListModel->rowCount()); nbTorrents = 0; tabs->setTabText(0, tr("Transfers") +" (0)"); //Update info Bar setInfoBar(tr("Download list cleared.")); } } // delete selected items in the list void GUI::deleteSelection(){ QDir torrentBackup(misc::qBittorrentPath() + "BT_backup"); QString scan_dir = options->getScanDir(); bool isScanningDir = !scan_dir.isNull(); if(isScanningDir && scan_dir.at(scan_dir.length()-1) != QDir::separator()){ scan_dir += QDir::separator(); } QModelIndexList selectedIndexes = downloadList->selectionModel()->selectedIndexes(); if(!selectedIndexes.isEmpty()){ if(QMessageBox::question( this, tr("Are you sure? -- qBittorrent"), tr("Are you sure you want to delete the selected item(s) in download list?"), tr("&Yes"), tr("&No"), QString(), 0, 1) == 0) { //User clicked YES QModelIndex index; QList > sortedIndexes; // We have to remove items from the bottom // to the top in order not to change indexes // of files to delete. foreach(index, selectedIndexes){ if(index.column() == NAME){ qDebug("row to delete: %d", index.row()); misc::insertSort2(sortedIndexes, QPair(index.row(), index), Qt::DescendingOrder); } } QPair sortedIndex; foreach(sortedIndex, sortedIndexes){ qDebug("deleting row: %d, %d, col: %d", sortedIndex.first, sortedIndex.second.row(), sortedIndex.second.column()); // Get the file name QString fileName = sortedIndex.second.data().toString(); // Delete item from download list DLListModel->removeRow(sortedIndex.first); // Get handle and remove the torrent QMap::iterator it = handles.find(fileName); if(it != handles.end() && it.key() == fileName) { torrent_handle h = it.value(); s->remove_torrent(h); // Remove torrent from handles handles.erase(it); // remove it from scan dir or it will start again if(isScanningDir){ QFile::remove(scan_dir+fileName+".torrent"); } // Remove it from torrent backup directory torrentBackup.remove(fileName+".torrent"); torrentBackup.remove(fileName+".fastresume"); torrentBackup.remove(fileName+".paused"); torrentBackup.remove(fileName+".incremental"); torrentBackup.remove(fileName+".pieces"); // Update info bar setInfoBar("'" + fileName +"' "+tr("removed.", " removed.")); --nbTorrents; tabs->setTabText(0, tr("Transfers") +" ("+QString(misc::toString(nbTorrents).c_str())+")"); }else{ std::cerr << "Error: Could not find the torrent handle supposed to be removed\n"; } } } } } // Will fast resume unfinished torrents in // backup directory void GUI::resumeUnfinished(){ qDebug("Resuming unfinished torrents"); QDir torrentBackup(misc::qBittorrentPath() + "BT_backup"); QStringList fileNames, filePaths; // Scan torrentBackup directory fileNames = torrentBackup.entryList(); QString fileName; foreach(fileName, fileNames){ if(fileName.endsWith(".torrent")){ filePaths.append(torrentBackup.path()+QDir::separator()+fileName); } } // Resume downloads addTorrents(filePaths); qDebug("Unfinished torrents resumed"); } // Method used to add torrents to download list void GUI::addTorrents(const QStringList& pathsList, bool fromScanDir, const QString& from_url){ torrent_handle h; entry resume_data; bool fastResume=false; QDir saveDir(options->getSavePath()), torrentBackup(misc::qBittorrentPath() + "BT_backup"); QString file, dest_file, scan_dir; // Checking if savePath Dir exists // create it if it is not if(! saveDir.exists()){ if(! saveDir.mkpath(saveDir.path())){ setInfoBar(tr("Couldn't create the directory:")+" '"+saveDir.path()+"'", "red"); return; } } // Checking if BT_backup Dir exists // create it if it is not if(! torrentBackup.exists()){ if(! torrentBackup.mkpath(torrentBackup.path())){ setInfoBar(tr("Couldn't create the directory:")+" '"+torrentBackup.path()+"'", "red"); return; } } // Processing torrents foreach(file, pathsList){ file = file.trimmed().replace("file://", ""); if(file.isEmpty()){ continue; } qDebug("Adding %s to download list", file.toStdString().c_str()); std::ifstream in(file.toStdString().c_str(), std::ios_base::binary); in.unsetf(std::ios_base::skipws); try{ // Decode torrent file entry e = bdecode(std::istream_iterator(in), std::istream_iterator()); //qDebug("Entry bdecoded"); // Getting torrent file informations torrent_info t(e); //qDebug("Got torrent infos"); if(handles.contains(QString(t.name().c_str()))){ // Update info Bar if(!fromScanDir){ if(!from_url.isNull()){ setInfoBar("'" + from_url + "' "+tr("already in download list.", " already in download list.")); }else{ setInfoBar("'" + file + "' "+tr("already in download list.", " already in download list.")); } }else{ // Delete torrent from scan dir QFile::remove(file); } continue; } //Getting fast resume data if existing if(torrentBackup.exists(QString::QString(t.name().c_str())+".fastresume")){ try{ std::stringstream strStream; strStream << t.name() << ".fastresume"; boost::filesystem::ifstream resume_file(fs::path(torrentBackup.path().toStdString()) / strStream.str(), std::ios_base::binary); resume_file.unsetf(std::ios_base::skipws); resume_data = bdecode(std::istream_iterator(resume_file), std::istream_iterator()); fastResume=true; }catch (invalid_encoding&) {} catch (fs::filesystem_error&) {} //qDebug("Got fast resume data"); } int row = DLListModel->rowCount(); // Adding files to bittorrent session if(hasFilteredFiles(QString(t.name().c_str()))){ h = s->add_torrent(t, fs::path(saveDir.path().toStdString()), resume_data, false); qDebug("Full allocation mode"); }else{ h = s->add_torrent(t, fs::path(saveDir.path().toStdString()), resume_data, true); qDebug("Compact allocation mode"); } h.set_max_connections(60); h.set_max_uploads(-1); // Load filtered files loadFilteredFiles(h); //qDebug("Added to session"); torrent_status torrentStatus = h.status(); DLListModel->setData(DLListModel->index(row, PROGRESS), QVariant((double)torrentStatus.progress)); handles.insert(QString(t.name().c_str()), h); QString newFile = torrentBackup.path() + QDir::separator() + QString(t.name().c_str())+".torrent"; if(file != newFile){ // Delete file from torrentBackup directory in case it exists because // QFile::copy() do not overwrite QFile::remove(newFile); // Copy it to torrentBackup directory QFile::copy(file, newFile); } //qDebug("Copied to torrent backup directory"); if(fromScanDir){ scan_dir = options->getScanDir(); if(scan_dir.at(scan_dir.length()-1) != QDir::separator()){ scan_dir += QDir::separator(); } //rename torrent file to match file name and find it easily later dest_file = scan_dir+t.name().c_str()+".torrent"; if(!QFile::exists(dest_file)){ QFile::rename(file, dest_file); } } // Adding torrent to download list DLListModel->insertRow(row); DLListModel->setData(DLListModel->index(row, NAME), QVariant(t.name().c_str())); DLListModel->setData(DLListModel->index(row, SIZE), QVariant((qlonglong)t.total_size())); DLListModel->setData(DLListModel->index(row, DLSPEED), QVariant((double)0.)); DLListModel->setData(DLListModel->index(row, UPSPEED), QVariant((double)0.)); DLListModel->setData(DLListModel->index(row, ETA), QVariant((qlonglong)-1)); // Pause torrent if it was paused last time if(QFile::exists(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+QString(t.name().c_str())+".paused")){ DLListModel->setData(DLListModel->index(row, STATUS), QVariant(tr("Paused"))); DLListModel->setData(DLListModel->index(row, NAME), QVariant(QIcon(":/Icons/skin/paused.png")), Qt::DecorationRole); setRowColor(row, "red"); }else{ DLListModel->setData(DLListModel->index(row, STATUS), QVariant(tr("Connecting..."))); DLListModel->setData(DLListModel->index(row, NAME), QVariant(QIcon(":/Icons/skin/connecting.png")), Qt::DecorationRole); setRowColor(row, "grey"); } //qDebug("Added to download list"); // Pause torrent if it was paused last time if(QFile::exists(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+QString(t.name().c_str())+".paused")){ h.pause(); } // Incremental download if(QFile::exists(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+QString(t.name().c_str())+".incremental")){ qDebug("Incremental download enabled for %s", t.name().c_str()); h.set_sequenced_download_threshold(15); } // Update info Bar if(!fastResume){ if(!from_url.isNull()){ setInfoBar("'" + from_url + "' "+tr("added to download list.")); }else{ setInfoBar("'" + file + "' "+tr("added to download list.")); } }else{ if(!from_url.isNull()){ setInfoBar("'" + from_url + "' "+tr("resumed. (fast resume)")); }else{ setInfoBar("'" + file + "' "+tr("resumed. (fast resume)")); } } ++nbTorrents; tabs->setTabText(0, tr("Transfers") +" ("+QString(misc::toString(nbTorrents).c_str())+")"); }catch (invalid_encoding& e){ // Raised by bdecode() std::cerr << "Could not decode file, reason: " << e.what() << '\n'; // Display warning to tell user we can't decode the torrent file if(!from_url.isNull()){ setInfoBar(tr("Unable to decode torrent file:")+" '"+from_url+"'", "red"); }else{ setInfoBar(tr("Unable to decode torrent file:")+" '"+file+"'", "red"); } setInfoBar(tr("This file is either corrupted or this isn't a torrent."),"red"); if(fromScanDir){ // Remove .corrupt file in case it already exists QFile::remove(file+".corrupt"); //Rename file extension so that it won't display error message more than once QFile::rename(file,file+".corrupt"); } } catch (invalid_torrent_file&){ // Raised by torrent_info constructor // Display warning to tell user we can't decode the torrent file if(!from_url.isNull()){ setInfoBar(tr("Unable to decode torrent file:")+" '"+from_url+"'", "red"); }else{ setInfoBar(tr("Unable to decode torrent file:")+" '"+file+"'", "red"); } setInfoBar(tr("This file is either corrupted or this isn't a torrent."),"red"); if(fromScanDir){ //Rename file extension so that it won't display error message more than once QFile::rename(file,file+".corrupt"); } } } } void GUI::reloadTorrent(const torrent_handle &h, bool compact_mode){ QDir saveDir(options->getSavePath()), torrentBackup(misc::qBittorrentPath() + "BT_backup"); QString fileName = QString(h.get_torrent_info().name().c_str()); qDebug("Reloading torrent: %s", fileName.toStdString().c_str()); torrent_handle new_h; entry resumeData; torrent_info t = h.get_torrent_info(); // Checking if torrentBackup Dir exists // create it if it is not if(! torrentBackup.exists()){ torrentBackup.mkpath(torrentBackup.path()); } // Write fast resume data // Pause download (needed before fast resume writing) h.pause(); // Extracting resume data if (h.has_metadata()){ // get fast resume data resumeData = h.write_resume_data(); } int row = -1; // Delete item from download list for(int i=0; irowCount(); ++i){ if(DLListModel->data(DLListModel->index(i, NAME)).toString()==fileName){ row = i; break; } } Q_ASSERT(row != -1); DLListModel->removeRow(row); // Remove torrent s->remove_torrent(h); handles.remove(fileName); // Add torrent again to session unsigned short timeout = 0; while(h.is_valid() && timeout < 6){ SleeperThread::msleep(1000); ++timeout; } if(h.is_valid()){ std::cerr << "Error: Couldn't reload the torrent\n"; return; } new_h = s->add_torrent(t, fs::path(saveDir.path().toStdString()), resumeData, compact_mode); if(compact_mode){ qDebug("Using compact allocation mode"); }else{ qDebug("Using full allocation mode"); } handles.insert(QString(t.name().c_str()), new_h); new_h.set_max_connections(60); new_h.set_max_uploads(-1); // Load filtered Files loadFilteredFiles(new_h); // Adding torrent to download list DLListModel->insertRow(row); DLListModel->setData(DLListModel->index(row, NAME), QVariant(t.name().c_str())); DLListModel->setData(DLListModel->index(row, SIZE), QVariant((qlonglong)t.total_size())); DLListModel->setData(DLListModel->index(row, DLSPEED), QVariant((double)0.)); DLListModel->setData(DLListModel->index(row, UPSPEED), QVariant((double)0.)); DLListModel->setData(DLListModel->index(row, ETA), QVariant((qlonglong)-1)); // Pause torrent if it was paused last time if(QFile::exists(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+QString(t.name().c_str())+".paused")){ DLListModel->setData(DLListModel->index(row, STATUS), QVariant(tr("Paused"))); DLListModel->setData(DLListModel->index(row, NAME), QVariant(QIcon(":/Icons/skin/paused.png")), Qt::DecorationRole); setRowColor(row, "red"); }else{ DLListModel->setData(DLListModel->index(row, STATUS), QVariant(tr("Connecting..."))); DLListModel->setData(DLListModel->index(row, NAME), QVariant(QIcon(":/Icons/skin/connecting.png")), Qt::DecorationRole); setRowColor(row, "grey"); } // Pause torrent if it was paused last time if(QFile::exists(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+fileName+".paused")){ new_h.pause(); } // Incremental download if(QFile::exists(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+fileName+".incremental")){ qDebug("Incremental download enabled for %s", fileName.toStdString().c_str()); new_h.set_sequenced_download_threshold(15); } } // As program parameters, we can get paths or urls. // This function parse the parameters and call // the right addTorrent function, considering // the parameter type. void GUI::ProcessParams(const QStringList& params){ QString param; foreach(param, params){ param = param.trimmed(); if(param.startsWith("http://", Qt::CaseInsensitive) || param.startsWith("ftp://", Qt::CaseInsensitive) || param.startsWith("https://", Qt::CaseInsensitive)){ downloadFromUrl(param); }else{ addTorrents(QStringList(param)); } } } // libtorrent allow to adjust ratio for each torrent // This function will apply to same ratio to all torrents void GUI::setGlobalRatio(float ratio){ foreach(torrent_handle h, handles){ h.set_ratio(ratio); } } // Show torrent properties dialog void GUI::showProperties(const QModelIndex &index){ int row = index.row(); QString fileName = DLListModel->data(DLListModel->index(row, NAME)).toString(); torrent_handle h = handles.value(fileName); QStringList errors = trackerErrors.value(fileName, QStringList(tr("None"))); properties *prop = new properties(this, h, errors); connect(prop, SIGNAL(changedFilteredFiles(torrent_handle, bool)), this, SLOT(reloadTorrent(torrent_handle, bool))); prop->show(); } // Set BT session configuration void GUI::configureSession(){ qDebug("Configuring session"); QPair limits; unsigned short old_listenPort, new_listenPort; session_settings proxySettings; // creating BT Session & listen // Configure session regarding options try { if(s->is_listening()){ old_listenPort = s->listen_port(); }else{ old_listenPort = 0; } std::pair new_listenPorts = options->getPorts(); if(listenPorts != new_listenPorts){ s->listen_on(new_listenPorts); listenPorts = new_listenPorts; } if(s->is_listening()){ new_listenPort = s->listen_port(); if(new_listenPort != old_listenPort){ setInfoBar(tr("Listening on port: ")+ QString(misc::toString(new_listenPort).c_str())); } } // Apply max connec limit (-1 if disabled) int max_connec = options->getMaxConnec(); s->set_max_connections(max_connec); limits = options->getLimits(); switch(limits.first){ case -1: // Download limit disabled case 0: s->set_download_rate_limit(-1); break; default: s->set_download_rate_limit(limits.first*1024); } switch(limits.second){ case -1: // Upload limit disabled case 0: s->set_upload_rate_limit(-1); break; default: s->set_upload_rate_limit(limits.second*1024); } // Apply ratio (0 if disabled) setGlobalRatio(options->getRatio()); // DHT (Trackerless) if(options->isDHTEnabled() && !DHTEnabled){ boost::filesystem::ifstream dht_state_file(misc::qBittorrentPath().toStdString()+"dht_state", std::ios_base::binary); dht_state_file.unsetf(std::ios_base::skipws); entry dht_state; try{ dht_state = bdecode(std::istream_iterator(dht_state_file), std::istream_iterator()); }catch (std::exception&) {} s->start_dht(dht_state); s->add_dht_router(std::make_pair(std::string("router.bittorrent.com"), 6881)); s->add_dht_router(std::make_pair(std::string("router.utorrent.com"), 6881)); s->add_dht_router(std::make_pair(std::string("router.bitcomet.com"), 6881)); DHTEnabled = true; qDebug("Enabling DHT Support"); }else{ if(!options->isDHTEnabled() && DHTEnabled){ s->stop_dht(); DHTEnabled = false; qDebug("Disabling DHT Support"); } } // Apply filtering settings if(options->isFilteringEnabled()){ qDebug("Ip Filter enabled"); s->set_ip_filter(options->getFilter()); }else{ qDebug("Ip Filter disabled"); s->set_ip_filter(ip_filter()); } // Apply Proxy settings if(options->isProxyEnabled()){ proxySettings.proxy_ip = options->getProxyIp().toStdString(); proxySettings.proxy_port = options->getProxyPort(); if(options->isProxyAuthEnabled()){ proxySettings.proxy_login = options->getProxyUsername().toStdString(); proxySettings.proxy_password = options->getProxyPassword().toStdString(); } } proxySettings.user_agent = "qBittorrent "VERSION; //proxySettings.user_agent = "Azureus"; s->set_settings(proxySettings); // Scan dir stuff if(options->getScanDir().isNull()){ if(timerScan->isActive()){ timerScan->stop(); } }else{ if(!timerScan->isActive()){ timerScan->start(5000); } } }catch(std::exception& e){ std::cerr << e.what() << "\n"; } qDebug("Session configured"); } // Pause All Downloads in DL list void GUI::pauseAll(){ QString fileName; bool changes=false; // Browse Handles to pause all downloads foreach(torrent_handle h, handles){ if(!h.is_paused()){ fileName = QString(h.get_torrent_info().name().c_str()); changes=true; h.pause(); // Create .paused file QFile paused_file(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+fileName+".paused"); paused_file.open(QIODevice::WriteOnly | QIODevice::Text); paused_file.close(); // update DL Status int row = getRowFromName(fileName); if(row == -1){ std::cerr << "Error: Filename could not be found in download list...\n"; continue; } DLListModel->setData(DLListModel->index(row, DLSPEED), QVariant((double)0.)); DLListModel->setData(DLListModel->index(row, UPSPEED), QVariant((double)0.)); DLListModel->setData(DLListModel->index(row, STATUS), QVariant(tr("Paused"))); DLListModel->setData(DLListModel->index(row, ETA), QVariant((qlonglong)-1)); DLListModel->setData(DLListModel->index(row, NAME), QVariant(QIcon(":/Icons/skin/paused.png")), Qt::DecorationRole); setRowColor(row, "red"); } } //Set Info Bar if(changes){ setInfoBar(tr("All Downloads Paused.")); } } // pause selected items in the list void GUI::pauseSelection(){ QModelIndexList selectedIndexes = downloadList->selectionModel()->selectedIndexes(); QModelIndex index; foreach(index, selectedIndexes){ if(index.column() == NAME){ // Get the file name QString fileName = index.data().toString(); // Get handle and pause the torrent torrent_handle h = handles.value(fileName); if(!h.is_paused()){ h.pause(); // Create .paused file QFile paused_file(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+fileName+".paused"); paused_file.open(QIODevice::WriteOnly | QIODevice::Text); paused_file.close(); // Update DL status int row = index.row(); DLListModel->setData(DLListModel->index(row, DLSPEED), QVariant((double)0.0)); DLListModel->setData(DLListModel->index(row, UPSPEED), QVariant((double)0.0)); DLListModel->setData(DLListModel->index(row, STATUS), QVariant(tr("Paused"))); DLListModel->setData(DLListModel->index(row, ETA), QVariant((qlonglong)-1)); setInfoBar("'"+ fileName +"' "+tr("paused.", " paused.")); DLListModel->setData(DLListModel->index(row, NAME), QIcon(":/Icons/skin/paused.png"), Qt::DecorationRole); setRowColor(row, "red"); } } } } // Start All Downloads in DL list void GUI::startAll(){ QString fileName; bool changes=false; // Browse Handles to pause all downloads foreach(torrent_handle h, handles){ if(h.is_paused()){ fileName = QString(h.get_torrent_info().name().c_str()); changes=true; h.resume(); // Delete .paused file QFile::remove(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+fileName+".paused"); // update DL Status int row = getRowFromName(fileName); if(row == -1){ std::cerr << "Error: Filename could not be found in download list...\n"; continue; } DLListModel->setData(DLListModel->index(row, STATUS), QVariant(tr("Connecting..."))); DLListModel->setData(DLListModel->index(row, NAME), QVariant(QIcon(":/Icons/skin/connecting.png")), Qt::DecorationRole); setRowColor(row, "grey"); } } //Set Info Bar if(changes){ setInfoBar(tr("All Downloads Resumed.")); } } // start selected items in the list void GUI::startSelection(){ QModelIndexList selectedIndexes = downloadList->selectionModel()->selectedIndexes(); QModelIndex index; foreach(index, selectedIndexes){ if(index.column() == NAME){ // Get the file name QString fileName = index.data().toString(); // Get handle and pause the torrent torrent_handle h = handles.value(fileName); if(h.is_paused()){ h.resume(); // Delete .paused file QFile::remove(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+fileName+".paused"); // Update DL status int row = index.row(); DLListModel->setData(DLListModel->index(row, STATUS), QVariant(tr("Connecting..."))); setInfoBar("'"+ fileName +"' "+tr("resumed.", " resumed.")); DLListModel->setData(DLListModel->index(row, NAME), QVariant(QIcon(":/Icons/skin/connecting.png")), Qt::DecorationRole); setRowColor(row, "grey"); } } } } void GUI::addUnauthenticatedTracker(QPair tracker){ // Trackers whose authentication was cancelled if(unauthenticated_trackers.indexOf(tracker) < 0){ unauthenticated_trackers << tracker; } } // display properties of selected items void GUI::propertiesSelection(){ QModelIndexList selectedIndexes = downloadList->selectionModel()->selectedIndexes(); QModelIndex index; foreach(index, selectedIndexes){ if(index.column() == NAME){ showProperties(index); } } } // Check connection status and display right icon void GUI::checkConnectionStatus(){ qDebug("Checking connection status"); char tmp[MAX_CHAR_TMP]; session_status sessionStatus = s->status(); // Update ratio info float ratio = 1.; if(sessionStatus.total_payload_download != 0){ ratio = (float)sessionStatus.total_payload_upload/(float)sessionStatus.total_payload_download; } if(ratio > 10.){ ratio = 10.; } snprintf(tmp, MAX_CHAR_TMP, "%.1f", ratio); LCD_Ratio->display(tmp); if(ratio < 0.5){ lbl_ratio_icon->setPixmap(QPixmap(QString::fromUtf8(":/Icons/unhappy.png"))); }else{ if(ratio > 1.0){ lbl_ratio_icon->setPixmap(QPixmap(QString::fromUtf8(":/Icons/smile.png"))); }else{ lbl_ratio_icon->setPixmap(QPixmap(QString::fromUtf8(":/Icons/stare.png"))); } } if(sessionStatus.has_incoming_connections){ // Connection OK actionConnexion_Status->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/connected.png"))); actionConnexion_Status->setText(tr("Connection Status:
Online")); }else{ if(sessionStatus.num_peers){ // Firewalled ? actionConnexion_Status->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/firewalled.png"))); actionConnexion_Status->setText(tr("Connection Status:
Firewalled?
No incoming connections...")); }else{ // Disconnected actionConnexion_Status->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/disconnected.png"))); actionConnexion_Status->setText(tr("Connection Status:
Offline
No peers found...")); } } // Check trackerErrors list size and clear it if it is too big if(trackerErrors.size() > 50){ trackerErrors.clear(); } // look at session alerts and display some infos std::auto_ptr a = s->pop_alert(); while (a.get()){ if (torrent_finished_alert* p = dynamic_cast(a.get())){ QString fileName = QString(p->handle.get_torrent_info().name().c_str()); // Level: info setInfoBar(fileName+tr(" has finished downloading.")); if(options->getUseOSDAlways() || (options->getUseOSDWhenHiddenOnly() && (isMinimized() || isHidden()))) { myTrayIcon->showMessage(tr("Download finished"), fileName+tr(" has finished downloading.", " has finished downloading."), QSystemTrayIcon::Information, TIME_TRAY_BALLOON); } QString scan_dir = options->getScanDir(); bool isScanningDir = !scan_dir.isNull(); if(isScanningDir && scan_dir.at(scan_dir.length()-1) != QDir::separator()){ scan_dir += QDir::separator(); } // Remove it from torrentBackup directory // No need to resume it if(options->getClearFinishedOnExit()){ QFile::remove(fileName+".torrent"); QFile::remove(fileName+".fastresume"); if(isScanningDir){ QFile::remove(scan_dir+fileName+".torrent"); } } } else if (dynamic_cast(a.get())){ // Level: fatal setInfoBar(tr("Couldn't listen on any of the given ports."), "red"); } else if (tracker_alert* p = dynamic_cast(a.get())){ // Level: fatal QString filename = QString(p->handle.get_torrent_info().name().c_str()); QStringList errors = trackerErrors.value(filename, QStringList()); errors.append(""+QTime::currentTime().toString("hh:mm:ss")+" - "+QString(a->msg().c_str())+""); trackerErrors.insert(filename, errors); // Authentication if(p->status_code == 401){ if(unauthenticated_trackers.indexOf(QPair(p->handle, p->handle.status().current_tracker)) < 0){ // Tracker login tracker_login = new trackerLogin(this, p->handle); } } } // else if (peer_error_alert* p = dynamic_cast(a.get())) // { // events.push_back(identify_client(p->id) + ": " + a->msg()); // } // else if (invalid_request_alert* p = dynamic_cast(a.get())) // { // events.push_back(identify_client(p->id) + ": " + a->msg()); // } a = s->pop_alert(); } qDebug("Connection status updated"); } /***************************************************** * * * Search * * * *****************************************************/ // 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; } // Getting checked search engines if(!mininova->isChecked() && ! piratebay->isChecked()/* && !reactor->isChecked()*/ && !isohunt->isChecked()/* && !btjunkie->isChecked()*/ && !meganova->isChecked()){ QMessageBox::critical(0, tr("No seach 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 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; std::string 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().toStdString(); } delete tmpfile; FILE *file = fopen(filePath.c_str(), "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(QString(filePath.c_str()))); float version_on_server = getNovaVersion(QString(filePath.c_str())); if(version_on_server == 0.0){ //First server is down, try mirror QFile::remove(filePath.c_str()); FILE *file = fopen(filePath.c_str(), "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(QString(filePath.c_str())); } 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")+QString(getNovaChangelog(QString(filePath.c_str())).data()), tr("&Yes"), tr("&No"), QString(), 0, 1)){ return; }else{ qDebug("Updating search plugin from qbittorrent.org"); QFile::remove(misc::qBittorrentPath()+"nova.py"); QFile::copy(QString(filePath.c_str()), 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 -- 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.c_str()); } // 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){ if(options->getUseOSDAlways() || (options->getUseOSDWhenHiddenOnly() && (isMinimized() || isHidden()))) { myTrayIcon->showMessage(tr("Search Engine"), tr("Search is 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 is finished")); } } } results_lbl->setText(tr("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()); downloadFromUrl(url); setRowColor(index.row(), "red", false); } } } /***************************************************** * * * Utils * * * *****************************************************/ // 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); } } } // return the row of in data model // corresponding to the given filename int GUI::getRowFromName(const QString& name) const{ for(int i=0; irowCount(); ++i){ if(DLListModel->data(DLListModel->index(i, NAME)) == name){ return i; } } return -1; } /***************************************************** * * * Options * * * *****************************************************/ // Set locale in options, this is required // because main() can set the locale and it // can't access options object directly. void GUI::setLocale(QString locale){ options->setLocale(locale); } // Display Program Options void GUI::showOptions() const{ options->show(); } // Is executed each time options are saved void GUI::OptionsSaved(const QString& info){ // Update info bar setInfoBar(info); // Update session configureSession(); } /***************************************************** * * * HTTP Downloader * * * *****************************************************/ void GUI::processDownloadedFile(QString url, QString file_path, int return_code, QString errorBuffer){ if(return_code){ // Download failed setInfoBar(tr("Couldn't download", "Couldn't download ")+" "+url+", "+tr("reason:", "Reason why the download failed")+" "+errorBuffer, "red"); QFile::remove(file_path); return; } // Add file to torrent download list addTorrents(QStringList(file_path), false, url); // Delete tmp file QFile::remove(file_path); } // Take an url string to a torrent file, // download the torrent file to a tmp location, then // add it to download list void GUI::downloadFromUrl(const QString& url){ setInfoBar(tr("Downloading", "Example: Downloading www.example.com/test.torrent")+" '"+url+"', "+tr("Please wait..."), "black"); // Launch downloader thread downloader->downloadUrl(url); // downloader->start(); } // Display an input dialog to prompt user for // an url void GUI::askForTorrentUrl(){ downloadFromURLDialog = new downloadFromURL(this); } void GUI::downloadFromURLList(const QStringList& url_list){ QString url; foreach(url, url_list){ downloadFromUrl(url); } }