/* * 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 #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" #include "torrentAddition.h" #define SEARCHHISTORY_MAXSIZE 50 /***************************************************** * * * GUI * * * *****************************************************/ // Constructor GUI::GUI(QWidget *parent, QStringList torrentCmdLine) : QMainWindow(parent){ setupUi(this); setWindowTitle(tr("qBittorrent ")+VERSION); readSettings(); s = new session(fingerprint("qB", VERSION_MAJOR, VERSION_MINOR, VERSION_BUGFIX, 0)); // 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"))); actionWebsite->setIcon(QIcon(QString::fromUtf8(":/Icons/qbittorrent32.png"))); actionBugReport->setIcon(QIcon(QString::fromUtf8(":/Icons/newmsg.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"))); connecStatusLblIcon = new QLabel(); connecStatusLblIcon->setFrameShape(QFrame::NoFrame); connecStatusLblIcon->setPixmap(QPixmap(QString::fromUtf8(":/Icons/skin/disconnected.png"))); connecStatusLblIcon->setToolTip(tr("Connection Status:
Offline
No peers found...")); toolBar->addWidget(connecStatusLblIcon); actionDelete_Permanently->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/delete_perm.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,9); 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(SEEDSLEECH, Qt::Horizontal, tr("Seeds/Leechs")); DLListModel->setHeaderData(STATUS, Qt::Horizontal, tr("Status")); DLListModel->setHeaderData(ETA, Qt::Horizontal, tr("ETA")); downloadList->setModel(DLListModel); DLDelegate = new DLListDelegate(); downloadList->setItemDelegate(DLDelegate); // Hide hash column downloadList->hideColumn(HASH); // 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); // DHT (Trackerless) DHTEnabled = false; // Configure BT session according to options configureSession(); s->add_extension(&create_metadata_plugin); // 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(forceExit())); connect(actionOpen, SIGNAL(triggered()), this, SLOT(askForTorrents())); connect(actionDelete_Permanently, SIGNAL(triggered()), this, SLOT(deletePermanently())); 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(actionWebsite, SIGNAL(triggered()), this, SLOT(openqBTHomepage())); connect(actionBugReport, SIGNAL(triggered()), this, SLOT(openqBTBugTracker())); 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); // 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 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(bytesWritten(qint64)), this, SLOT(readParamsOnSocket(qint64))); // 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); } // new qCompleter to the search pattern startSearchHistory(); QCompleter *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(); // 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(){ qDebug("GUI destruction"); delete options; delete checkConnect; delete timerScan; delete searchProcess; delete refresher; delete myTrayIcon; delete myTrayIconMenu; delete DLDelegate; delete DLListModel; delete SearchListModel; delete SearchDelegate; delete previewProcess; delete downloader; delete connecStatusLblIcon; delete s; } void GUI::openqBTHomepage(){ QDesktopServices::openUrl(QUrl("http://www.qbittorrent.org")); } void GUI::openqBTBugTracker(){ QDesktopServices::openUrl(QUrl("http://bugs.qbittorrent.org")); } void GUI::writeSettings() { QSettings settings("qBittorrent", "qBittorrent"); settings.beginGroup("MainWindow"); settings.setValue("size", size()); settings.setValue("pos", pos()); settings.endGroup(); } void GUI::readSettings() { QSettings settings("qBittorrent", "qBittorrent"); settings.beginGroup("MainWindow"); resize(settings.value("size", size()).toSize()); move(settings.value("pos", screenCenter()).toPoint()); settings.endGroup(); } // Update Info Bar information void GUI::setInfoBar(const QString& info, const QString& color){ qDebug("setInfoBar called"); 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(); } } void GUI::acceptConnection(){ clientConnection = tcpServer.nextPendingConnection(); connect(clientConnection, SIGNAL(disconnected()), this, SLOT(readParamsOnSocket())); qDebug("accepted connection from another instance"); } void GUI::readParamsOnSocket(){ if(clientConnection != 0){ QByteArray params = clientConnection->readAll(); if(!params.isEmpty()){ processParams(QString(params.data()).split("\n")); qDebug("Received parameters from another instance"); } } } void GUI::togglePausedState(const QModelIndex& index){ int row = index.row(); QString fileHash = DLListModel->data(DLListModel->index(row, HASH)).toString(); torrent_handle h = handles.value(fileHash); 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 fileHash = DLListModel->data(DLListModel->index(index.row(), HASH)).toString(); torrent_handle h = handles.value(fileHash); 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); QModelIndex 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 fileHash = DLListModel->data(DLListModel->index(index.row(), HASH)).toString(); // Get handle and pause the torrent torrent_handle h = handles.value(fileHash); if(h.is_paused()){ myDLLlistMenu.addAction(actionStart); }else{ myDLLlistMenu.addAction(actionPause); } myDLLlistMenu.addAction(actionDelete); myDLLlistMenu.addAction(actionDelete_Permanently); myDLLlistMenu.addAction(actionTorrent_Properties); if(!options->getPreviewProgram().isEmpty() && isFilePreviewPossible(h) && selectedIndexes.size()<=DLListModel->columnCount()){ myDLLlistMenu.addAction(actionPreview_file); } break; } } // Call menu // XXX: why mapToGlobal() is not enough? myDLLlistMenu.exec(mapToGlobal(pos)+QPoint(22,180)); } // Necessary if we want to close the window // in one time if "close to systray" is enabled void GUI::forceExit(){ hide(); close(); } 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(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); } } 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(bool force){ qDebug("Updating download list"); 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( !force && (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.values()){ try{ torrent_status torrentStatus = h.status(); QString fileHash = QString(misc::toString(h.info_hash()).c_str()); if(!h.is_paused()){ int row = getRowFromHash(fileHash); 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)); } DLListModel->setData(DLListModel->index(row, SEEDSLEECH), QVariant(QString(misc::toString(torrentStatus.num_seeds, true).c_str())+"/"+QString(misc::toString(torrentStatus.num_peers - torrentStatus.num_seeds, true).c_str()))); } }catch(invalid_handle e){ continue; } } qDebug("Updated Download list"); } 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; colcolumnCount(); ++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; colcolumnCount(); ++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 QPoint GUI::screenCenter(){ 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)); return QPoint((desk.width() - this->frameGeometry().width()) / 2, (desk.height() - this->frameGeometry().height()) / 2); } bool GUI::loadFilteredFiles(torrent_handle &h){ bool has_filtered_files = false; torrent_info torrentInfo = h.get_torrent_info(); QString fileHash = QString(misc::toString(torrentInfo.info_hash()).c_str()); QFile pieces_file(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+fileHash+".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& fileHash){ QFile pieces_file(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+fileHash+".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; } // 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{ qDebug("Saving columns width in download list"); QSettings settings("qBittorrent", "qBittorrent"); QStringList width_list; for(int i=0; icolumnCount(); ++i){ width_list << QString(misc::toString(downloadList->columnWidth(i)).c_str()); } settings.setValue("DownloadListColsWidth", width_list.join(" ")); qDebug("Download list columns width saved"); } // Load columns width in a file that were saved previously // (download list) bool GUI::loadColWidthDLList(){ qDebug("Loading columns width for download list"); QSettings settings("qBittorrent", "qBittorrent"); QString line = settings.value("DownloadListColsWidth", QString()).toString(); if(line.isEmpty()) return false; QStringList width_list = line.split(' '); if(width_list.size() != DLListModel->columnCount()) return false; for(int i=0; iheader()->resizeSection(i, width_list.at(i).toInt()); } qDebug("Download list columns width loaded"); 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 aboutdlg = new about(this); } // Called when we close the program void GUI::closeEvent(QCloseEvent *e){ if(options->getGoToSysTrayOnExitingWindow() && !this->isHidden()){ hide(); e->ignore(); return; } 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; } } //TODO: Clean finished torrents on exit // if(options->getClearFinishedOnExit()){ // torrentBackup.remove(fileHash+".torrent"); // torrentBackup.remove(fileHash+".fastresume"); // torrentBackup.remove(fileHash+".paused"); // torrentBackup.remove(fileHash+".incremental"); // torrentBackup.remove(fileHash+".pieces"); // torrentBackup.remove(fileHash+".savepath"); // if(isScanningDir){ // QFile::remove(scan_dir+fileHash+".torrent"); // } // } // save the searchHistory for later uses saveSearchHistory(); // Save DHT entry if(DHTEnabled){ try{ entry dht_state = s->dht_state(); boost::filesystem::ofstream out((const char*)(misc::qBittorrentPath()+QString("dht_state")).toUtf8(), 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 writeSettings(); 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 hiding 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 QString file; foreach(file, files){ if(options->useAdditionDialog()){ torrentAdditionDialog *dialog = new torrentAdditionDialog(this); connect(dialog, SIGNAL(torrentAddition(const QString&, bool, const QString&)), this, SLOT(addTorrent(const QString&, bool, const QString&))); connect(dialog, SIGNAL(setInfoBarGUI(const QString&, const QString&)), this, SLOT(setInfoBar(const QString&, const QString&))); dialog->showLoad(file.trimmed().replace("file://", "")); }else{ addTorrent(file.trimmed().replace("file://", "")); } } } // 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; QSettings settings("qBittorrent", "qBittorrent"); // Open File Open Dialog // Note: it is possible to select more than one file pathsList = QFileDialog::getOpenFileNames(this, tr("Open Torrent Files"), settings.value("MainWindowLastDir", QDir::homePath()).toString(), tr("Torrent Files")+" (*.torrent)"); if(!pathsList.empty()){ for(int i=0; iuseAdditionDialog()){ torrentAdditionDialog *dialog = new torrentAdditionDialog(this); connect(dialog, SIGNAL(torrentAddition(const QString&, bool, const QString&)), this, SLOT(addTorrent(const QString&, bool, const QString&))); connect(dialog, SIGNAL(setInfoBarGUI(const QString&, const QString&)), this, SLOT(setInfoBar(const QString&, const QString&))); dialog->showLoad(pathsList.at(i)); }else{ addTorrent(pathsList.at(i)); } } // Save last dir to remember it QStringList top_dir = pathsList.at(0).split(QDir::separator()); top_dir.removeLast(); settings.setValue("MainWindowLastDir", top_dir.join(QDir::separator())); } } // 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; } } foreach(file, to_add){ if(options->useAdditionDialog()){ torrentAdditionDialog *dialog = new torrentAdditionDialog(this); connect(dialog, SIGNAL(torrentAddition(const QString&, bool, const QString&)), this, SLOT(addTorrent(const QString&, bool, const QString&))); connect(dialog, SIGNAL(setInfoBarGUI(const QString&, const QString&)), this, SLOT(setInfoBar(const QString&, const QString&))); dialog->showLoad(file, true); }else{ addTorrent(file, true); } } } } void GUI::saveFastResumeData() const{ qDebug("Saving fast resume data"); QString 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.values()){ // Pause download (needed before fast resume writing) h.pause(); // Extracting resume data if (h.has_metadata()){ QString fileHash = QString(misc::toString(h.info_hash()).c_str()); if(QFile::exists(torrentBackup.path()+QDir::separator()+fileHash+".torrent")){ // Remove old .fastresume data in case it exists QFile::remove(fileHash + ".fastresume"); // Write fast resume data entry resumeData = h.write_resume_data(); file = fileHash + ".fastresume"; boost::filesystem::ofstream out(fs::path((const char*)torrentBackup.path().toUtf8()) / (const char*)file.toUtf8(), 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 from download list AND from hard drive void GUI::deletePermanently(){ 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 and in hard drive?"), 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 fileHash = DLListModel->data(DLListModel->index(sortedIndex.second.row(), HASH)).toString(); QString savePath; // Delete item from download list DLListModel->removeRow(sortedIndex.first); // Get handle and remove the torrent QHash::iterator it = handles.find(fileHash); if(it != handles.end() && it.key() == fileHash) { torrent_handle h = it.value(); QString fileName = QString(h.name().c_str()); savePath = QString::fromUtf8(h.save_path().string().c_str()); // Remove torrent from handles qDebug(("There are " + misc::toString(handles.size()) + " items in handles").c_str()); handles.erase(it); qDebug(("After removing, there are still " + misc::toString(handles.size()) + " items in handles").c_str()); s->remove_torrent(h); // remove it from scan dir or it will start again if(isScanningDir){ QFile::remove(scan_dir+fileHash+".torrent"); } // Remove it from torrent backup directory torrentBackup.remove(fileHash+".torrent"); torrentBackup.remove(fileHash+".fastresume"); torrentBackup.remove(fileHash+".paused"); torrentBackup.remove(fileHash+".incremental"); torrentBackup.remove(fileHash+".pieces"); torrentBackup.remove(fileHash+".savepath"); // Remove from Hard drive qDebug("Removing this on hard drive: %s", qPrintable(savePath+QDir::separator()+fileName)); // Deleting in a thread to avoid GUI freeze deleteThread *deleter = new deleteThread(savePath+QDir::separator()+fileName); deleters << deleter; int i = 0; while(i < deleters.size()){ deleter = deleters.at(i); if(deleter->isFinished()){ qDebug("Delete thread has finished, deleting it"); deleters.removeAt(i); delete deleter; }else{ ++i; } } // 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"; } } } } } // 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 fileHash = DLListModel->data(DLListModel->index(sortedIndex.second.row(), HASH)).toString(); // Delete item from download list DLListModel->removeRow(sortedIndex.first); // Get handle and remove the torrent QHash::iterator it = handles.find(fileHash); if(it != handles.end() && it.key() == fileHash) { torrent_handle h = it.value(); QString fileName = QString(h.name().c_str()); // Remove torrent from handles handles.erase(it); s->remove_torrent(h); // remove it from scan dir or it will start again if(isScanningDir){ QFile::remove(scan_dir+fileHash+".torrent"); } // Remove it from torrent backup directory torrentBackup.remove(fileHash+".torrent"); torrentBackup.remove(fileHash+".fastresume"); torrentBackup.remove(fileHash+".paused"); torrentBackup.remove(fileHash+".incremental"); torrentBackup.remove(fileHash+".pieces"); torrentBackup.remove(fileHash+".savepath"); // 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 foreach(fileName, filePaths){ addTorrent(fileName); } qDebug("Unfinished torrents resumed"); } // Method used to add torrents to download list void GUI::addTorrent(const QString& path, bool fromScanDir, const QString& from_url){ torrent_handle h; entry resume_data; bool fastResume=false; QDir torrentBackup(misc::qBittorrentPath() + "BT_backup"); QString file, dest_file, scan_dir; // 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 file = path.trimmed().replace("file://", ""); if(file.isEmpty()){ return; } qDebug("Adding %s to download list", (const char*)file.toUtf8()); std::ifstream in((const char*)file.toUtf8(), std::ios_base::binary); in.unsetf(std::ios_base::skipws); try{ // Decode torrent file entry e = bdecode(std::istream_iterator(in), std::istream_iterator()); // Getting torrent file informations torrent_info t(e); QString hash = QString(misc::toString(t.info_hash()).c_str()); if(handles.contains(hash)){ // 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); } return; } // TODO: Remove this in a few releases if(torrentBackup.exists(QString(t.name().c_str())+".torrent")){ QFile::rename(torrentBackup.path()+QDir::separator()+QString(t.name().c_str())+".torrent", torrentBackup.path()+QDir::separator()+hash+".torrent"); QFile::rename(torrentBackup.path()+QDir::separator()+QString(t.name().c_str())+".fastresume", torrentBackup.path()+QDir::separator()+hash+".fastresume"); QFile::rename(torrentBackup.path()+QDir::separator()+QString(t.name().c_str())+".pieces", torrentBackup.path()+QDir::separator()+hash+".pieces"); QFile::rename(torrentBackup.path()+QDir::separator()+QString(t.name().c_str())+".savepath", torrentBackup.path()+QDir::separator()+hash+".savepath"); QFile::rename(torrentBackup.path()+QDir::separator()+QString(t.name().c_str())+".paused", torrentBackup.path()+QDir::separator()+hash+".paused"); QFile::rename(torrentBackup.path()+QDir::separator()+QString(t.name().c_str())+".incremental", torrentBackup.path()+QDir::separator()+hash+".incremental"); file = torrentBackup.path() + QDir::separator() + hash + ".torrent"; } //Getting fast resume data if existing if(torrentBackup.exists(hash+".fastresume")){ try{ std::stringstream strStream; strStream << hash.toStdString() << ".fastresume"; boost::filesystem::ifstream resume_file(fs::path((const char*)torrentBackup.path().toUtf8()) / 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"); } QString savePath = getSavePath(hash); int row = DLListModel->rowCount(); // Adding files to bittorrent session if(hasFilteredFiles(hash)){ h = s->add_torrent(t, fs::path((const char*)savePath.toUtf8()), resume_data, false); qDebug("Full allocation mode"); }else{ h = s->add_torrent(t, fs::path((const char*)savePath.toUtf8()), resume_data, true); qDebug("Compact allocation mode"); } // Is this really useful and appropriate ? //h.set_max_connections(60); h.set_max_uploads(-1); qDebug("Torrent hash is " + hash.toUtf8()); // 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(hash, h); QString newFile = torrentBackup.path() + QDir::separator() + hash + ".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(); } } // 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, SEEDSLEECH), QVariant("0/0")); DLListModel->setData(DLListModel->index(row, ETA), QVariant((qlonglong)-1)); DLListModel->setData(DLListModel->index(row, HASH), QVariant(hash)); // Pause torrent if it was paused last time if(QFile::exists(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+hash+".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()+hash+".paused")){ h.pause(); } // Incremental download if(QFile::exists(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+hash+".incremental")){ qDebug("Incremental download enabled for %s", t.name().c_str()); h.set_sequenced_download_threshold(15); } if(!from_url.isNull()){ // remove temporary file QFile::remove(file); } // Delete from scan dir to avoid trying to download it again if(fromScanDir){ QFile::remove(file); } // 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){ // 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"); } } } QString GUI::getSavePath(QString hash){ QFile savepath_file(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+hash+".savepath"); QByteArray line; QString savePath; if(savepath_file.open(QIODevice::ReadOnly | QIODevice::Text)){ line = savepath_file.readAll(); savepath_file.close(); qDebug("Save path: %s", line.data()); savePath = QString::fromUtf8(line.data()); }else{ savePath = options->getSavePath(); } // Checking if savePath Dir exists // create it if it is not QDir saveDir(savePath); if(!saveDir.exists()){ if(!saveDir.mkpath(saveDir.path())){ std::cerr << "Couldn't create the save directory: " << (const char*)saveDir.path().toUtf8() << "\n"; // TODO: handle this better return QDir::homePath(); } } return savePath; } void GUI::reloadTorrent(const torrent_handle &h, bool compact_mode){ QDir torrentBackup(misc::qBittorrentPath() + "BT_backup"); fs::path saveDir = h.save_path(); QString fileName = QString(h.name().c_str()); QString fileHash = QString(misc::toString(h.info_hash()).c_str()); qDebug("Reloading torrent: %s", (const char*)fileName.toUtf8()); 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, HASH)).toString()==fileHash){ row = i; break; } } Q_ASSERT(row != -1); DLListModel->removeRow(row); // Remove torrent s->remove_torrent(h); handles.remove(fileHash); // 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, saveDir, resumeData, compact_mode); if(compact_mode){ qDebug("Using compact allocation mode"); }else{ qDebug("Using full allocation mode"); } handles.insert(fileHash, 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()+fileHash+".paused")){ new_h.pause(); } // Incremental download if(QFile::exists(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+fileHash+".incremental")){ qDebug("Incremental download enabled for %s", (const char*)fileName.toUtf8()); 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{ if(options->useAdditionDialog()){ torrentAdditionDialog *dialog = new torrentAdditionDialog(this); connect(dialog, SIGNAL(torrentAddition(const QString&, bool, const QString&)), this, SLOT(addTorrent(const QString&, bool, const QString&))); connect(dialog, SIGNAL(setInfoBarGUI(const QString&, const QString&)), this, SLOT(setInfoBar(const QString&, const QString&))); dialog->showLoad(param); }else{ addTorrent(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 fileHash = DLListModel->data(DLListModel->index(row, HASH)).toString(); torrent_handle h = handles.value(fileHash); QStringList errors = trackerErrors.value(fileHash, 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", "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((const char*)(misc::qBittorrentPath()+QString("dht_state")).toUtf8(), 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"); } } if(!options->isPeXDisabled()){ qDebug("Enabling Peer eXchange (PeX)"); s->add_extension(&create_ut_pex_plugin); }else{ qDebug("Peer eXchange (PeX) disabled"); } int dht_port = options->getDHTPort(); if(dht_port >= 1000){ struct dht_settings DHTSettings; DHTSettings.service_port = dht_port; s->set_dht_settings(DHTSettings); qDebug("Set DHT Port to %d", dht_port); } // 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; 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 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 fileHash = DLListModel->data(DLListModel->index(index.row(), HASH)).toString(); // Get handle and pause the torrent torrent_handle h = handles.value(fileHash); if(!h.is_paused()){ h.pause(); // Create .paused file QFile paused_file(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+fileHash+".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("'"+ QString(h.name().c_str()) +"' "+tr("paused.", " paused.")); DLListModel->setData(DLListModel->index(row, NAME), QIcon(":/Icons/skin/paused.png"), Qt::DecorationRole); setRowColor(row, "red"); } } } } // 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 fileHash = DLListModel->data(DLListModel->index(index.row(), HASH)).toString(); // Get handle and pause the torrent torrent_handle h = handles.value(fileHash); if(h.is_paused()){ h.resume(); // Delete .paused file QFile::remove(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+fileHash+".paused"); // Update DL status int row = index.row(); DLListModel->setData(DLListModel->index(row, STATUS), QVariant(tr("Connecting..."))); setInfoBar("'"+ QString(h.name().c_str()) +"' "+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 connecStatusLblIcon->setPixmap(QPixmap(QString::fromUtf8(":/Icons/skin/connected.png"))); connecStatusLblIcon->setToolTip(tr("Connection Status:
Online")); }else{ if(sessionStatus.num_peers){ // Firewalled ? connecStatusLblIcon->setPixmap(QPixmap(QString::fromUtf8(":/Icons/skin/firewalled.png"))); connecStatusLblIcon->setToolTip(tr("Connection Status:
Firewalled?
No incoming connections...")); }else{ // Disconnected connecStatusLblIcon->setPixmap(QPixmap(QString::fromUtf8(":/Icons/skin/disconnected.png"))); connecStatusLblIcon->setToolTip(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()); QString fileHash = QString(misc::toString(p->handle.info_hash()).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(fileHash+".torrent"); QFile::remove(fileHash+".fastresume"); if(isScanningDir){ QFile::remove(scan_dir+fileHash+".torrent"); } } } else if (file_error_alert* p = dynamic_cast(a.get())){ QString fileName = QString(p->handle.get_torrent_info().name().c_str()); QString fileHash = QString(misc::toString(p->handle.info_hash()).c_str()); if(options->getUseOSDAlways() || (options->getUseOSDWhenHiddenOnly() && (isMinimized() || isHidden()))) { myTrayIcon->showMessage(tr("I/O Error"), tr("An error occured when trying to read or write ")+ fileName+"."+tr("The disk is probably full, download has been paused"), QSystemTrayIcon::Critical, TIME_TRAY_BALLOON); // Download will be pausedby libtorrent. Updating GUI information accordingly int row = getRowFromHash(fileHash); 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"); } } 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 fileHash = QString(misc::toString(p->handle.info_hash()).c_str()); QStringList errors = trackerErrors.value(fileHash, QStringList()); errors.append(""+QTime::currentTime().toString("hh:mm:ss")+" - "+QString(a->msg().c_str())+""); trackerErrors.insert(fileHash, 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 * * * *****************************************************/ // 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 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; 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 -- 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){ 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 the filehash int GUI::getRowFromHash(const QString& hash) const{ for(int i=0; irowCount(); ++i){ if(DLListModel->data(DLListModel->index(i, HASH)) == hash){ 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 if(options->useAdditionDialog()){ torrentAdditionDialog *dialog = new torrentAdditionDialog(this); connect(dialog, SIGNAL(torrentAddition(const QString&, bool, const QString&)), this, SLOT(addTorrent(const QString&, bool, const QString&))); connect(dialog, SIGNAL(setInfoBarGUI(const QString&, const QString&)), this, SLOT(setInfoBar(const QString&, const QString&))); dialog->showLoad(file_path, false, url); }else{ addTorrent(file_path, false, url); } } // 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); } }