From eb46326d23a97275bb1cb6c5385ad60e4671e077 Mon Sep 17 00:00:00 2001 From: Ivan Sorokin Date: Sat, 17 May 2014 13:04:33 +0400 Subject: [PATCH] use set_alert_dispatch instead of timer to get an alerts from libtorrent libtorrent allows setting a custom dispatch handler that is invoked in libtorrent thread when new alerts are incoming. QAlertDispatcher is a class that allows to translate these alerts to UI thread. The concept is very simple: 1. On initialization QAlertDispatcher constructor calls set_alert_dispatch() passing QAlertDispatcher::dispatch as argument. 2. On deinitialization destructor calls set_alert_dispatch() passing a empty function. (line 25) libtorrent handles thos and switches back to queuing alerts in queue. 3. QAlertDispatcher::dispatch() adds alert to queue and notifies UI thread that new alerts are incoming. Enqueuing is done in function enqueueToMainThread(). The invariant of class is the following: if alert queue is not empty, in message loop of UI thread contains a queued invocation of deliverSignal(). 4. When message loop is pumped UI thread execute deliverSignal() function. It emit appropriate signal and if queue is still not empty (for example if slot doesn't grab alerts) rewind enqueuing to main thread. This is a idea. But here is some details. 1. When QAlertDispatcher is destoyed, libtorrent still can call QAlertDispatcher::dispatch a few times after destruction. This is handled by passing a "tag". A tag is a object that references QAlertDispatch. Tag could be invalidated. So on destruction QAlertDispatcher invalidates a tag and then unsubscribes from alerts. When QAlertDispatcher::dispatch is called with invalid tag it simply discard an alert. Therefore we could drop a few alerts during unsubscription. So we unsubscribe only at exit when missing some alerts is not a problem. 2. Another problem is in QBtSession::saveFastResumeData(). It pumps alert queue synchronously. My first attempt was to destroy QAlertDispatcher and then pump libtorrent queue. But as I was afraid of losing alerts I supported synchronous querying of alerts in QAlertDispatcher. (QAlertDispatcher::getPendingAlerts) Conflicts: src/qtlibtorrent/qbtsession.cpp --- src/qtlibtorrent/alertdispatcher.cpp | 93 +++ src/qtlibtorrent/alertdispatcher.h | 43 ++ src/qtlibtorrent/qbtsession.cpp | 973 ++++++++++++++------------- src/qtlibtorrent/qbtsession.h | 4 +- src/qtlibtorrent/qtlibtorrent.pri | 6 +- 5 files changed, 632 insertions(+), 487 deletions(-) create mode 100644 src/qtlibtorrent/alertdispatcher.cpp create mode 100644 src/qtlibtorrent/alertdispatcher.h diff --git a/src/qtlibtorrent/alertdispatcher.cpp b/src/qtlibtorrent/alertdispatcher.cpp new file mode 100644 index 000000000..345d4e163 --- /dev/null +++ b/src/qtlibtorrent/alertdispatcher.cpp @@ -0,0 +1,93 @@ +#include "alertdispatcher.h" + +#include +#include +#include + +QAlertDispatcher::QAlertDispatcher(libtorrent::session *session, QObject* parent) + : QObject(parent) + , session(session) + , current_tag(new QAtomicPointer(this)) + , event_posted(false) { + session->set_alert_dispatch(boost::bind(&QAlertDispatcher::dispatch, current_tag, _1)); +} + +QAlertDispatcher::~QAlertDispatcher() { + // When QAlertDispatcher is destoyed, libtorrent still can call + // QAlertDispatcher::dispatch a few times after destruction. This is + // handled by passing a "tag". A tag is a object that references QAlertDispatch. + // Tag could be invalidated. So on destruction QAlertDispatcher invalidates a tag + // and then unsubscribes from alerts. When QAlertDispatcher::dispatch is called + // with invalid tag it simply discard an alert. + + { + QMutexLocker lock(&(alerts_mutex)); + *current_tag = 0; + current_tag.clear(); + } + + typedef boost::function)> dispatch_function_t; + session->set_alert_dispatch(dispatch_function_t()); +} + +void QAlertDispatcher::getPendingAlertsNoWait(std::deque& out) { + Q_ASSERT(out.empty()); + + QMutexLocker lock(&(alerts_mutex)); + std::swap(alerts, out); + event_posted = false; +} + +void QAlertDispatcher::getPendingAlerts(std::deque& out) { + assert(out.empty()); + + QMutexLocker lock(&(alerts_mutex)); + + while (alerts.empty()) + alerts_condvar.wait(&(alerts_mutex)); + + std::swap(alerts, out); + event_posted = false; +} + +void QAlertDispatcher::dispatch(QSharedPointer > tag, + std::auto_ptr alert_ptr) { + QAlertDispatcher* that = *tag; + if (!that) + return; + + QMutexLocker lock(&(that->alerts_mutex)); + + that = *tag; + if (!that) + return; + + bool was_empty = that->alerts.empty(); + + that->alerts.push_back(alert_ptr.get()); + alert_ptr.release(); + + if (was_empty) + that->alerts_condvar.wakeAll(); + + that->enqueueToMainThread(); + + Q_ASSERT(that->current_tag == tag); +} + +void QAlertDispatcher::enqueueToMainThread() { + if (!event_posted) { + event_posted = true; + QMetaObject::invokeMethod(this, "deliverSignal", Qt::QueuedConnection); + } +} + +void QAlertDispatcher::deliverSignal() { + emit alertsReceived(); + + QMutexLocker lock(&(alerts_mutex)); + event_posted = false; + + if (!alerts.empty()) + enqueueToMainThread(); +} diff --git a/src/qtlibtorrent/alertdispatcher.h b/src/qtlibtorrent/alertdispatcher.h new file mode 100644 index 000000000..55e937621 --- /dev/null +++ b/src/qtlibtorrent/alertdispatcher.h @@ -0,0 +1,43 @@ +#ifndef ALERTDISPATCHER_H +#define ALERTDISPATCHER_H + +#include +#include +#include +#include +#include +#include + +class QAlertDispatcher : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(QAlertDispatcher) + +public: + QAlertDispatcher(libtorrent::session *session, QObject* parent); + ~QAlertDispatcher(); + + void getPendingAlertsNoWait(std::deque&); + void getPendingAlerts(std::deque&); + +signals: + void alertsReceived(); + +private: + static void dispatch(QSharedPointer >, + std::auto_ptr); + void enqueueToMainThread(); + +private slots: + void deliverSignal(); + +private: + libtorrent::session *session; + QMutex alerts_mutex; + QWaitCondition alerts_condvar; + std::deque alerts; + QSharedPointer > current_tag; + bool event_posted; +}; + +#endif // ALERTDISPATCHER_H diff --git a/src/qtlibtorrent/qbtsession.cpp b/src/qtlibtorrent/qbtsession.cpp index 0d00f228b..57f83eefb 100755 --- a/src/qtlibtorrent/qbtsession.cpp +++ b/src/qtlibtorrent/qbtsession.cpp @@ -114,6 +114,7 @@ QBtSession::QBtSession() , m_upnp(0), m_natpmp(0) #endif , m_dynDNSUpdater(0) + , m_alertDispatcher(0) { BigRatioTimer = new QTimer(this); BigRatioTimer->setInterval(10000); @@ -147,9 +148,8 @@ QBtSession::QBtSession() PeXEnabled = false; } s->add_extension(&create_smart_ban_plugin); - timerAlerts = new QTimer(this); - connect(timerAlerts, SIGNAL(timeout()), SLOT(readAlerts())); - timerAlerts->start(1000); + m_alertDispatcher = new QAlertDispatcher(s, this); + connect(m_alertDispatcher, SIGNAL(alertsReceived()), SLOT(readAlerts())); appendLabelToSavePath = pref.appendTorrentLabel(); appendqBExtension = pref.useIncompleteFilesExtension(); connect(m_scanFolders, SIGNAL(torrentsAdded(QStringList&)), SLOT(addTorrentsFromScanFolder(QStringList&))); @@ -179,7 +179,6 @@ QBtSession::~QBtSession() { // Delete our objects if (m_tracker) delete m_tracker; - delete timerAlerts; if (BigRatioTimer) delete BigRatioTimer; if (filterParser) @@ -190,6 +189,7 @@ QBtSession::~QBtSession() { // HTTP Server if (httpServer) delete httpServer; + delete m_alertDispatcher; qDebug("Deleting the session"); delete s; qDebug("BTSession destructor OUT"); @@ -1608,7 +1608,6 @@ void QBtSession::saveFastResumeData() { qDebug("Saving fast resume data..."); // Stop listening for alerts resumeDataTimer.stop(); - timerAlerts->stop(); int num_resume_data = 0; // Pause session s->pause(); @@ -1633,52 +1632,52 @@ void QBtSession::saveFastResumeData() { } catch(libtorrent::invalid_handle&) {} } while (num_resume_data > 0) { - alert const* a = s->wait_for_alert(seconds(30)); - if (a == 0) { - std::cerr << " aborting with " << num_resume_data << " outstanding " - "torrents to save resume data for" << std::endl; - break; - } - // Saving fastresume data can fail - save_resume_data_failed_alert const* rda = dynamic_cast(a); - if (rda) { - --num_resume_data; - s->pop_alert(); - try { - // Remove torrent from session - if (rda->handle.is_valid()) - s->remove_torrent(rda->handle); - }catch(libtorrent::libtorrent_exception) {} - continue; - } - save_resume_data_alert const* rd = dynamic_cast(a); - if (!rd) { - s->pop_alert(); - continue; - } - // Saving fast resume data was successful - --num_resume_data; - if (!rd->resume_data) continue; - QDir torrentBackup(fsutils::BTBackupLocation()); - const QTorrentHandle h(rd->handle); - if (!h.is_valid()) continue; - try { - // Remove old fastresume file if it exists - backupPersistentData(h.hash(), rd->resume_data); - vector out; - bencode(back_inserter(out), *rd->resume_data); - const QString filepath = torrentBackup.absoluteFilePath(h.hash()+".fastresume"); - QFile resume_file(filepath); - if (resume_file.exists()) - fsutils::forceRemove(filepath); - if (!out.empty() && resume_file.open(QIODevice::WriteOnly)) { - resume_file.write(&out[0], out.size()); - resume_file.close(); + std::deque alerts; + m_alertDispatcher->getPendingAlerts(alerts); + + for (std::deque::const_iterator i = alerts.begin(), end = alerts.end(); i != end; ++i) + { + alert const* a = *i; + // Saving fastresume data can fail + save_resume_data_failed_alert const* rda = dynamic_cast(a); + if (rda) { + --num_resume_data; + try { + // Remove torrent from session + if (rda->handle.is_valid()) + s->remove_torrent(rda->handle); + }catch(libtorrent::libtorrent_exception) {} + continue; } - // Remove torrent from session - s->remove_torrent(rd->handle); - s->pop_alert(); - } catch(libtorrent::invalid_handle&) {} + save_resume_data_alert const* rd = dynamic_cast(a); + if (!rd) { + continue; + } + // Saving fast resume data was successful + --num_resume_data; + if (!rd->resume_data) continue; + QDir torrentBackup(fsutils::BTBackupLocation()); + const QTorrentHandle h(rd->handle); + if (!h.is_valid()) continue; + try { + // Remove old fastresume file if it exists + backupPersistentData(h.hash(), rd->resume_data); + vector out; + bencode(back_inserter(out), *rd->resume_data); + const QString filepath = torrentBackup.absoluteFilePath(h.hash()+".fastresume"); + QFile resume_file(filepath); + if (resume_file.exists()) + fsutils::forceRemove(filepath); + if (!out.empty() && resume_file.open(QIODevice::WriteOnly)) { + resume_file.write(&out[0], out.size()); + resume_file.close(); + } + // Remove torrent from session + s->remove_torrent(rd->handle); + } catch(libtorrent::invalid_handle&) {} + + delete a; + } } } @@ -2117,454 +2116,460 @@ void QBtSession::sendNotificationEmail(const QTorrentHandle &h) { // Read alerts sent by the Bittorrent session void QBtSession::readAlerts() { - // look at session alerts and display some infos - std::auto_ptr a = s->pop_alert(); - while (a.get()) { - try { - if (torrent_finished_alert* p = dynamic_cast(a.get())) { - QTorrentHandle h(p->handle); - if (h.is_valid()) { - const QString hash = h.hash(); - qDebug("Got a torrent finished alert for %s", qPrintable(h.name())); - // Remove .!qB extension if necessary - if (appendqBExtension) - appendqBextensionToTorrent(h, false); - const bool was_already_seeded = TorrentPersistentData::isSeed(hash); - qDebug("Was already seeded: %d", was_already_seeded); - if (!was_already_seeded) { - h.save_resume_data(); - qDebug("Checking if the torrent contains torrent files to download"); - // Check if there are torrent files inside - for (int i=0; i t = new torrent_info(fsutils::toNativePath(torrent_fullpath).toUtf8().constData()); - if (t->is_valid()) { - qDebug("emitting recursiveTorrentDownloadPossible()"); - emit recursiveTorrentDownloadPossible(h); - break; - } - } catch(std::exception&) { - qDebug("Caught error loading torrent"); - addConsoleMessage(tr("Unable to decode %1 torrent file.").arg(fsutils::toNativePath(torrent_fullpath)), QString::fromUtf8("red")); + typedef std::deque alerts_t; + alerts_t alerts; + m_alertDispatcher->getPendingAlertsNoWait(alerts); + + for (alerts_t::const_iterator i = alerts.begin(), end = alerts.end(); i != end; ++i) { + handleAlert(*i); + delete *i; + } +} + +void QBtSession::handleAlert(libtorrent::alert* a) { + try { + if (torrent_finished_alert* p = dynamic_cast(a)) { + QTorrentHandle h(p->handle); + if (h.is_valid()) { + const QString hash = h.hash(); + qDebug("Got a torrent finished alert for %s", qPrintable(h.name())); + // Remove .!qB extension if necessary + if (appendqBExtension) + appendqBextensionToTorrent(h, false); + + const bool was_already_seeded = TorrentPersistentData::isSeed(hash); + qDebug("Was already seeded: %d", was_already_seeded); + if (!was_already_seeded) { + h.save_resume_data(); + qDebug("Checking if the torrent contains torrent files to download"); + // Check if there are torrent files inside + for (int i=0; i t = new torrent_info(fsutils::toNativePath(torrent_fullpath).toUtf8().constData()); + if (t->is_valid()) { + qDebug("emitting recursiveTorrentDownloadPossible()"); + emit recursiveTorrentDownloadPossible(h); + break; } + } catch(std::exception&) { + qDebug("Caught error loading torrent"); + addConsoleMessage(tr("Unable to decode %1 torrent file.").arg(fsutils::toNativePath(torrent_fullpath)), QString::fromUtf8("red")); } } - // Move to download directory if necessary - if (!defaultTempPath.isEmpty()) { - // Check if directory is different - const QDir current_dir(h.save_path()); - const QDir save_dir(getSavePath(hash)); - if (current_dir != save_dir) { - qDebug("Moving torrent from the temp folder"); - h.move_storage(save_dir.absolutePath()); - } - } - // Remember finished state - qDebug("Saving seed status"); - TorrentPersistentData::saveSeedStatus(h); - // Recheck if the user asked to - Preferences pref; - if (pref.recheckTorrentsOnCompletion()) { - h.force_recheck(); - } - qDebug("Emitting finishedTorrent() signal"); - emit finishedTorrent(h); - qDebug("Received finished alert for %s", qPrintable(h.name())); - #ifndef DISABLE_GUI - bool will_shutdown = (pref.shutdownWhenDownloadsComplete() || - pref.shutdownqBTWhenDownloadsComplete() || - pref.suspendWhenDownloadsComplete()) - && !hasDownloadingTorrents(); - #else - bool will_shutdown = false; - #endif - // AutoRun program - if (pref.isAutoRunEnabled()) - autoRunExternalProgram(h); - // Move .torrent file to another folder - if (pref.isFinishedTorrentExportEnabled()) - exportTorrentFile(h, FinishedTorrentExportFolder); - // Mail notification - if (pref.isMailNotificationEnabled()) - sendNotificationEmail(h); - #ifndef DISABLE_GUI - // Auto-Shutdown - if (will_shutdown) { - bool suspend = pref.suspendWhenDownloadsComplete(); - bool shutdown = pref.shutdownWhenDownloadsComplete(); - // Confirm shutdown - QString confirm_msg; - if (suspend) { - confirm_msg = tr("The computer will now go to sleep mode unless you cancel within the next 15 seconds..."); - } else if (shutdown) { - confirm_msg = tr("The computer will now be switched off unless you cancel within the next 15 seconds..."); - } else { - confirm_msg = tr("qBittorrent will now exit unless you cancel within the next 15 seconds..."); - } - if (!ShutdownConfirmDlg::askForConfirmation(confirm_msg)) - return; - // Actually shut down - if (suspend || shutdown) { - qDebug("Preparing for auto-shutdown because all downloads are complete!"); - // Disabling it for next time - pref.setShutdownWhenDownloadsComplete(false); - pref.setSuspendWhenDownloadsComplete(false); - // Make sure preferences are synced before exiting - if (suspend) - m_shutdownAct = SUSPEND_COMPUTER; - else - m_shutdownAct = SHUTDOWN_COMPUTER; - } - qDebug("Exiting the application"); - qApp->exit(); - return; - } - #endif // DISABLE_GUI } - } - } - else if (save_resume_data_alert* p = dynamic_cast(a.get())) { - const QDir torrentBackup(fsutils::BTBackupLocation()); - const QTorrentHandle h(p->handle); - if (h.is_valid() && p->resume_data) { - const QString filepath = torrentBackup.absoluteFilePath(h.hash()+".fastresume"); - QFile resume_file(filepath); - if (resume_file.exists()) - fsutils::forceRemove(filepath); - qDebug("Saving fastresume data in %s", qPrintable(filepath)); - backupPersistentData(h.hash(), p->resume_data); - vector out; - bencode(back_inserter(out), *p->resume_data); - if (!out.empty() && resume_file.open(QIODevice::WriteOnly)) { - resume_file.write(&out[0], out.size()); - resume_file.close(); - } - } - } - else if (file_renamed_alert* p = dynamic_cast(a.get())) { - QTorrentHandle h(p->handle); - if (h.is_valid()) { - if (h.num_files() > 1) { - // Check if folders were renamed - QStringList old_path_parts = h.orig_filepath_at(p->index).split("/"); - old_path_parts.removeLast(); - QString old_path = old_path_parts.join("/"); - QStringList new_path_parts = fsutils::fromNativePath(misc::toQStringU(p->name)).split("/"); - new_path_parts.removeLast(); - if (!new_path_parts.isEmpty() && old_path != new_path_parts.join("/")) { - qDebug("Old_path(%s) != new_path(%s)", qPrintable(old_path), qPrintable(new_path_parts.join("/"))); - old_path = h.save_path()+"/"+old_path; - qDebug("Detected folder renaming, attempt to delete old folder: %s", qPrintable(old_path)); - QDir().rmpath(old_path); - } - } else { - // Single-file torrent - // Renaming a file corresponds to changing the save path - emit savePathChanged(h); - } - } - } - else if (torrent_deleted_alert* p = dynamic_cast(a.get())) { - qDebug("A torrent was deleted from the hard disk, attempting to remove the root folder too..."); - QString hash = misc::toQString(p->info_hash); - if (!hash.isEmpty()) { - if (savePathsToRemove.contains(hash)) { - const QString dirpath = savePathsToRemove.take(hash); - qDebug() << "Removing save path: " << dirpath << "..."; - bool ok = fsutils::smartRemoveEmptyFolderTree(dirpath); - Q_UNUSED(ok); - qDebug() << "Folder was removed: " << ok; - } - } else { - // Fallback - qDebug() << "hash is empty, use fallback to remove save path"; - foreach (const QString& key, savePathsToRemove.keys()) { - // Attempt to delete - if (QDir().rmdir(savePathsToRemove[key])) { - savePathsToRemove.remove(key); - } - } - } - } - else if (storage_moved_alert* p = dynamic_cast(a.get())) { - QTorrentHandle h(p->handle); - if (h.is_valid()) { - // Attempt to remove old folder if empty - const QString old_save_path = fsutils::fromNativePath(TorrentPersistentData::getPreviousPath(h.hash())); - const QString new_save_path = fsutils::fromNativePath(misc::toQStringU(p->path.c_str())); - qDebug("Torrent moved from %s to %s", qPrintable(old_save_path), qPrintable(new_save_path)); - QDir old_save_dir(old_save_path); - if (old_save_dir != QDir(defaultSavePath) && old_save_dir != QDir(defaultTempPath)) { - qDebug("Attempting to remove %s", qPrintable(old_save_path)); - QDir().rmpath(old_save_path); - } - if (defaultTempPath.isEmpty() || !new_save_path.startsWith(defaultTempPath)) { - qDebug("Storage has been moved, updating save path to %s", qPrintable(new_save_path)); - TorrentPersistentData::saveSavePath(h.hash(), new_save_path); - } - emit savePathChanged(h); - //h.force_recheck(); - } - } - else if (metadata_received_alert* p = dynamic_cast(a.get())) { - QTorrentHandle h(p->handle); - Preferences pref; - if (h.is_valid()) { - QString hash(h.hash()); - if (HiddenData::hasData(hash)) { - HiddenData::gotMetadata(hash); - if (pref.isQueueingSystemEnabled()) { - //Internally decrease the queue limits to ensure that that other queued items aren't started - libtorrent::session_settings sessionSettings(s->settings()); - int max_downloading = pref.getMaxActiveDownloads(); - int max_active = pref.getMaxActiveTorrents(); - if (max_downloading > -1) - sessionSettings.active_downloads = max_downloading + HiddenData::getDownloadingSize(); - else - sessionSettings.active_downloads = max_downloading; - if (max_active > -1) - sessionSettings.active_limit = max_active + HiddenData::getDownloadingSize(); - else - sessionSettings.active_limit = max_active; - s->set_settings(sessionSettings); - } - h.pause(); - } - qDebug("Received metadata for %s", qPrintable(h.hash())); - // Save metadata - const QDir torrentBackup(fsutils::BTBackupLocation()); - if (!QFile::exists(torrentBackup.absoluteFilePath(h.hash()+QString(".torrent")))) - h.save_torrent_file(torrentBackup.absoluteFilePath(h.hash()+QString(".torrent"))); - // Copy the torrent file to the export folder - if (m_torrentExportEnabled) - exportTorrentFile(h); - // Append .!qB to incomplete files - if (appendqBExtension) - appendqBextensionToTorrent(h, true); - - if (!HiddenData::hasData(hash)) - emit metadataReceived(h); - else - emit metadataReceivedHidden(h); - - if (h.is_paused() && !HiddenData::hasData(hash)) { - // XXX: Unfortunately libtorrent-rasterbar does not send a torrent_paused_alert - // and the torrent can be paused when metadata is received - emit pausedTorrent(h); - } - - } - } - else if (file_error_alert* p = dynamic_cast(a.get())) { - QTorrentHandle h(p->handle); - if (h.is_valid()) { - h.pause(); - std::cerr << "File Error: " << p->message().c_str() << std::endl; - addConsoleMessage(tr("An I/O error occurred, '%1' paused.").arg(h.name())); - addConsoleMessage(tr("Reason: %1").arg(misc::toQStringU(p->message()))); - if (h.is_valid()) { - emit fullDiskError(h, misc::toQStringU(p->message())); - //h.pause(); - emit pausedTorrent(h); - } - } - } - else if (file_completed_alert* p = dynamic_cast(a.get())) { - QTorrentHandle h(p->handle); - qDebug("A file completed download in torrent %s", qPrintable(h.name())); - if (appendqBExtension) { - qDebug("appendqBTExtension is true"); - QString name = h.filepath_at(p->index); - if (name.endsWith(".!qB")) { - const QString old_name = name; - name.chop(4); - qDebug("Renaming %s to %s", qPrintable(old_name), qPrintable(name)); - h.rename_file(p->index, name); - } - } - } - else if (torrent_paused_alert* p = dynamic_cast(a.get())) { - if (p->handle.is_valid()) { - QTorrentHandle h(p->handle); - if (!HiddenData::hasData(h.hash())) { - if (!h.has_error()) - h.save_resume_data(); - emit pausedTorrent(h); - } - } - } - else if (tracker_error_alert* p = dynamic_cast(a.get())) { - // Level: fatal - QTorrentHandle h(p->handle); - if (h.is_valid()) { - // Authentication - if (p->status_code != 401) { - qDebug("Received a tracker error for %s: %s", p->url.c_str(), p->msg.c_str()); - const QString tracker_url = misc::toQString(p->url); - QHash trackers_data = trackersInfos.value(h.hash(), QHash()); - TrackerInfos data = trackers_data.value(tracker_url, TrackerInfos(tracker_url)); - data.last_message = misc::toQStringU(p->msg); - trackers_data.insert(tracker_url, data); - trackersInfos[h.hash()] = trackers_data; - } else { - emit trackerAuthenticationRequired(h); - } - } - } - else if (tracker_reply_alert* p = dynamic_cast(a.get())) { - const QTorrentHandle h(p->handle); - if (h.is_valid()) { - qDebug("Received a tracker reply from %s (Num_peers=%d)", p->url.c_str(), p->num_peers); - // Connection was successful now. Remove possible old errors - QHash trackers_data = trackersInfos.value(h.hash(), QHash()); - const QString tracker_url = misc::toQString(p->url); - TrackerInfos data = trackers_data.value(tracker_url, TrackerInfos(tracker_url)); - data.last_message = ""; // Reset error/warning message - data.num_peers = p->num_peers; - trackers_data.insert(tracker_url, data); - trackersInfos[h.hash()] = trackers_data; - } - } - else if (tracker_warning_alert* p = dynamic_cast(a.get())) { - const QTorrentHandle h(p->handle); - if (h.is_valid()) { - // Connection was successful now but there is a warning message - QHash trackers_data = trackersInfos.value(h.hash(), QHash()); - const QString tracker_url = misc::toQString(p->url); - TrackerInfos data = trackers_data.value(tracker_url, TrackerInfos(tracker_url)); - data.last_message = misc::toQStringU(p->msg); // Store warning message - trackers_data.insert(tracker_url, data); - trackersInfos[h.hash()] = trackers_data; - qDebug("Received a tracker warning from %s: %s", p->url.c_str(), p->msg.c_str()); - } - } - else if (portmap_error_alert* p = dynamic_cast(a.get())) { - addConsoleMessage(tr("UPnP/NAT-PMP: Port mapping failure, message: %1").arg(misc::toQStringU(p->message())), "red"); - //emit UPnPError(QString(p->msg().c_str())); - } - else if (portmap_alert* p = dynamic_cast(a.get())) { - qDebug("UPnP Success, msg: %s", p->message().c_str()); - addConsoleMessage(tr("UPnP/NAT-PMP: Port mapping successful, message: %1").arg(misc::toQStringU(p->message())), "blue"); - //emit UPnPSuccess(QString(p->msg().c_str())); - } - else if (peer_blocked_alert* p = dynamic_cast(a.get())) { - boost::system::error_code ec; - string ip = p->ip.to_string(ec); - if (!ec) { - addPeerBanMessage(QString::fromLatin1(ip.c_str()), true); - //emit peerBlocked(QString::fromLatin1(ip.c_str())); - } - } - else if (peer_ban_alert* p = dynamic_cast(a.get())) { - boost::system::error_code ec; - string ip = p->ip.address().to_string(ec); - if (!ec) { - addPeerBanMessage(QString::fromLatin1(ip.c_str()), false); - //emit peerBlocked(QString::fromLatin1(ip.c_str())); - } - } - else if (fastresume_rejected_alert* p = dynamic_cast(a.get())) { - QTorrentHandle h(p->handle); - if (h.is_valid()) { - qDebug("/!\\ Fast resume failed for %s, reason: %s", qPrintable(h.name()), p->message().c_str()); - if (p->error.value() == 134 && TorrentPersistentData::isSeed(h.hash()) && h.has_missing_files()) { - const QString hash = h.hash(); - // Mismatching file size (files were probably moved - addConsoleMessage(tr("File sizes mismatch for torrent %1, pausing it.").arg(h.name())); - TorrentPersistentData::setErrorState(hash, true); - pauseTorrent(hash); - } else { - addConsoleMessage(tr("Fast resume data was rejected for torrent %1, checking again...").arg(h.name()), QString::fromUtf8("red")); - addConsoleMessage(tr("Reason: %1").arg(misc::toQStringU(p->message()))); - } - } - } - else if (url_seed_alert* p = dynamic_cast(a.get())) { - addConsoleMessage(tr("Url seed lookup failed for url: %1, message: %2").arg(misc::toQString(p->url)).arg(misc::toQStringU(p->message())), QString::fromUtf8("red")); - //emit urlSeedProblem(QString::fromUtf8(p->url.c_str()), QString::fromUtf8(p->msg().c_str())); - } - else if (listen_succeeded_alert *p = dynamic_cast(a.get())) { - boost::system::error_code ec; - QString proto = "TCP"; -#if LIBTORRENT_VERSION_NUM >= 10000 - if (p->sock_type == listen_succeeded_alert::udp) - proto = "UDP"; - else if (p->sock_type == listen_succeeded_alert::tcp) - proto = "TCP"; - else if (p->sock_type == listen_succeeded_alert::tcp_ssl) - proto = "TCP_SSL"; -#endif - qDebug() << "Successfully listening on " << proto << p->endpoint.address().to_string(ec).c_str() << "/" << p->endpoint.port(); - addConsoleMessage(tr("qBittorrent is successfully listening on interface %1 port: %2/%3", "e.g: qBittorrent is successfully listening on interface 192.168.0.1 port: TCP/6881").arg(p->endpoint.address().to_string(ec).c_str()).arg(proto).arg(QString::number(p->endpoint.port())), "blue"); - // Force reannounce on all torrents because some trackers blacklist some ports - std::vector torrents = s->get_torrents(); - - std::vector::iterator it = torrents.begin(); - std::vector::iterator itend = torrents.end(); - for ( ; it != itend; ++it) { - it->force_reannounce(); - } - } - else if (listen_failed_alert *p = dynamic_cast(a.get())) { - boost::system::error_code ec; - QString proto = "TCP"; -#if LIBTORRENT_VERSION_NUM >= 10000 - if (p->sock_type == listen_failed_alert::udp) - proto = "UDP"; - else if (p->sock_type == listen_failed_alert::tcp) - proto = "TCP"; - else if (p->sock_type == listen_failed_alert::tcp_ssl) - proto = "TCP_SSL"; - else if (p->sock_type == listen_failed_alert::i2p) - proto = "I2P"; - else if (p->sock_type == listen_failed_alert::socks5) - proto = "SOCKS5"; -#endif - qDebug() << "Failed listening on " << proto << p->endpoint.address().to_string(ec).c_str() << "/" << p->endpoint.port(); - addConsoleMessage(tr("qBittorrent failed listening on interface %1 port: %2/%3. Reason: %4", "e.g: qBittorrent failed listening on interface 192.168.0.1 port: TCP/6881. Reason: already in use").arg(p->endpoint.address().to_string(ec).c_str()).arg(proto).arg(QString::number(p->endpoint.port())).arg(misc::toQStringU(p->error.message())), "red"); - } - else if (torrent_checked_alert* p = dynamic_cast(a.get())) { - QTorrentHandle h(p->handle); - if (h.is_valid()) { - const QString hash = h.hash(); - qDebug("%s have just finished checking", qPrintable(hash)); - // Save seed status - TorrentPersistentData::saveSeedStatus(h); - // Move to temp directory if necessary - if (!h.is_seed() && !defaultTempPath.isEmpty()) { + // Move to download directory if necessary + if (!defaultTempPath.isEmpty()) { // Check if directory is different const QDir current_dir(h.save_path()); - const QDir save_dir(getSavePath(h.hash())); - if (current_dir == save_dir) { - qDebug("Moving the torrent to the temp directory..."); - QString torrent_tmp_path = defaultTempPath; - h.move_storage(torrent_tmp_path); + const QDir save_dir(getSavePath(hash)); + if (current_dir != save_dir) { + qDebug("Moving torrent from the temp folder"); + h.move_storage(save_dir.absolutePath()); } } - emit torrentFinishedChecking(h); - if (torrentsToPausedAfterChecking.contains(hash)) { - torrentsToPausedAfterChecking.removeOne(hash); - h.pause(); - emit pausedTorrent(h); + // Remember finished state + qDebug("Saving seed status"); + TorrentPersistentData::saveSeedStatus(h); + // Recheck if the user asked to + Preferences pref; + if (pref.recheckTorrentsOnCompletion()) { + h.force_recheck(); + } + qDebug("Emitting finishedTorrent() signal"); + emit finishedTorrent(h); + qDebug("Received finished alert for %s", qPrintable(h.name())); +#ifndef DISABLE_GUI + bool will_shutdown = (pref.shutdownWhenDownloadsComplete() || + pref.shutdownqBTWhenDownloadsComplete() || + pref.suspendWhenDownloadsComplete()) + && !hasDownloadingTorrents(); +#else + bool will_shutdown = false; +#endif + // AutoRun program + if (pref.isAutoRunEnabled()) + autoRunExternalProgram(h); + // Move .torrent file to another folder + if (pref.isFinishedTorrentExportEnabled()) + exportTorrentFile(h, FinishedTorrentExportFolder); + // Mail notification + if (pref.isMailNotificationEnabled()) + sendNotificationEmail(h); +#ifndef DISABLE_GUI + // Auto-Shutdown + if (will_shutdown) { + bool suspend = pref.suspendWhenDownloadsComplete(); + bool shutdown = pref.shutdownWhenDownloadsComplete(); + // Confirm shutdown + QString confirm_msg; + if (suspend) { + confirm_msg = tr("The computer will now go to sleep mode unless you cancel within the next 15 seconds..."); + } else if (shutdown) { + confirm_msg = tr("The computer will now be switched off unless you cancel within the next 15 seconds..."); + } else { + confirm_msg = tr("qBittorrent will now exit unless you cancel within the next 15 seconds..."); + } + if (!ShutdownConfirmDlg::askForConfirmation(confirm_msg)) + return; + // Actually shut down + if (suspend || shutdown) { + qDebug("Preparing for auto-shutdown because all downloads are complete!"); + // Disabling it for next time + pref.setShutdownWhenDownloadsComplete(false); + pref.setSuspendWhenDownloadsComplete(false); + // Make sure preferences are synced before exiting + if (suspend) + m_shutdownAct = SUSPEND_COMPUTER; + else + m_shutdownAct = SHUTDOWN_COMPUTER; + } + qDebug("Exiting the application"); + qApp->exit(); + return; + } +#endif // DISABLE_GUI + } + } + } + else if (save_resume_data_alert* p = dynamic_cast(a)) { + const QDir torrentBackup(fsutils::BTBackupLocation()); + const QTorrentHandle h(p->handle); + if (h.is_valid() && p->resume_data) { + const QString filepath = torrentBackup.absoluteFilePath(h.hash()+".fastresume"); + QFile resume_file(filepath); + if (resume_file.exists()) + fsutils::forceRemove(filepath); + qDebug("Saving fastresume data in %s", qPrintable(filepath)); + backupPersistentData(h.hash(), p->resume_data); + vector out; + bencode(back_inserter(out), *p->resume_data); + if (!out.empty() && resume_file.open(QIODevice::WriteOnly)) { + resume_file.write(&out[0], out.size()); + resume_file.close(); + } + } + } + else if (file_renamed_alert* p = dynamic_cast(a)) { + QTorrentHandle h(p->handle); + if (h.is_valid()) { + if (h.num_files() > 1) { + // Check if folders were renamed + QStringList old_path_parts = h.orig_filepath_at(p->index).split("/"); + old_path_parts.removeLast(); + QString old_path = old_path_parts.join("/"); + QStringList new_path_parts = fsutils::fromNativePath(misc::toQStringU(p->name)).split("/"); + new_path_parts.removeLast(); + if (!new_path_parts.isEmpty() && old_path != new_path_parts.join("/")) { + qDebug("Old_path(%s) != new_path(%s)", qPrintable(old_path), qPrintable(new_path_parts.join("/"))); + old_path = h.save_path()+"/"+old_path; + qDebug("Detected folder renaming, attempt to delete old folder: %s", qPrintable(old_path)); + QDir().rmpath(old_path); + } + } else { + // Single-file torrent + // Renaming a file corresponds to changing the save path + emit savePathChanged(h); + } + } + } + else if (torrent_deleted_alert* p = dynamic_cast(a)) { + qDebug("A torrent was deleted from the hard disk, attempting to remove the root folder too..."); + QString hash = misc::toQString(p->info_hash); + if (!hash.isEmpty()) { + if (savePathsToRemove.contains(hash)) { + const QString dirpath = savePathsToRemove.take(hash); + qDebug() << "Removing save path: " << dirpath << "..."; + bool ok = fsutils::smartRemoveEmptyFolderTree(dirpath); + Q_UNUSED(ok); + qDebug() << "Folder was removed: " << ok; + } + } else { + // Fallback + qDebug() << "hash is empty, use fallback to remove save path"; + foreach (const QString& key, savePathsToRemove.keys()) { + // Attempt to delete + if (QDir().rmdir(savePathsToRemove[key])) { + savePathsToRemove.remove(key); } } } - else if (external_ip_alert *p = dynamic_cast(a.get())) { - boost::system::error_code ec; - addConsoleMessage(tr("External IP: %1", "e.g. External IP: 192.168.0.1").arg(p->external_address.to_string(ec).c_str()), "blue"); - } - } catch (const std::exception& e) { - qWarning() << "Caught exception in readAlerts(): " << e.what(); } + else if (storage_moved_alert* p = dynamic_cast(a)) { + QTorrentHandle h(p->handle); + if (h.is_valid()) { + // Attempt to remove old folder if empty + const QString old_save_path = fsutils::fromNativePath(TorrentPersistentData::getPreviousPath(h.hash())); + const QString new_save_path = fsutils::fromNativePath(misc::toQStringU(p->path.c_str())); + qDebug("Torrent moved from %s to %s", qPrintable(old_save_path), qPrintable(new_save_path)); + QDir old_save_dir(old_save_path); + if (old_save_dir != QDir(defaultSavePath) && old_save_dir != QDir(defaultTempPath)) { + qDebug("Attempting to remove %s", qPrintable(old_save_path)); + QDir().rmpath(old_save_path); + } + if (defaultTempPath.isEmpty() || !new_save_path.startsWith(defaultTempPath)) { + qDebug("Storage has been moved, updating save path to %s", qPrintable(new_save_path)); + TorrentPersistentData::saveSavePath(h.hash(), new_save_path); + } + emit savePathChanged(h); + //h.force_recheck(); + } + } + else if (metadata_received_alert* p = dynamic_cast(a)) { + QTorrentHandle h(p->handle); + Preferences pref; + if (h.is_valid()) { + QString hash(h.hash()); + if (HiddenData::hasData(hash)) { + HiddenData::gotMetadata(hash); + if (pref.isQueueingSystemEnabled()) { + //Internally decrease the queue limits to ensure that that other queued items aren't started + libtorrent::session_settings sessionSettings(s->settings()); + int max_downloading = pref.getMaxActiveDownloads(); + int max_active = pref.getMaxActiveTorrents(); + if (max_downloading > -1) + sessionSettings.active_downloads = max_downloading + HiddenData::getDownloadingSize(); + else + sessionSettings.active_downloads = max_downloading; + if (max_active > -1) + sessionSettings.active_limit = max_active + HiddenData::getDownloadingSize(); + else + sessionSettings.active_limit = max_active; + s->set_settings(sessionSettings); + } + h.pause(); + } + qDebug("Received metadata for %s", qPrintable(h.hash())); + // Save metadata + const QDir torrentBackup(fsutils::BTBackupLocation()); + if (!QFile::exists(torrentBackup.absoluteFilePath(h.hash()+QString(".torrent")))) + h.save_torrent_file(torrentBackup.absoluteFilePath(h.hash()+QString(".torrent"))); + // Copy the torrent file to the export folder + if (m_torrentExportEnabled) + exportTorrentFile(h); + // Append .!qB to incomplete files + if (appendqBExtension) + appendqBextensionToTorrent(h, true); - a = s->pop_alert(); + if (!HiddenData::hasData(hash)) + emit metadataReceived(h); + else + emit metadataReceivedHidden(h); + + if (h.is_paused() && !HiddenData::hasData(hash)) { + // XXX: Unfortunately libtorrent-rasterbar does not send a torrent_paused_alert + // and the torrent can be paused when metadata is received + emit pausedTorrent(h); + } + + } + } + else if (file_error_alert* p = dynamic_cast(a)) { + QTorrentHandle h(p->handle); + if (h.is_valid()) { + h.pause(); + std::cerr << "File Error: " << p->message().c_str() << std::endl; + addConsoleMessage(tr("An I/O error occurred, '%1' paused.").arg(h.name())); + addConsoleMessage(tr("Reason: %1").arg(misc::toQStringU(p->message()))); + if (h.is_valid()) { + emit fullDiskError(h, misc::toQStringU(p->message())); + //h.pause(); + emit pausedTorrent(h); + } + } + } + else if (file_completed_alert* p = dynamic_cast(a)) { + QTorrentHandle h(p->handle); + qDebug("A file completed download in torrent %s", qPrintable(h.name())); + if (appendqBExtension) { + qDebug("appendqBTExtension is true"); + QString name = h.filepath_at(p->index); + if (name.endsWith(".!qB")) { + const QString old_name = name; + name.chop(4); + qDebug("Renaming %s to %s", qPrintable(old_name), qPrintable(name)); + h.rename_file(p->index, name); + } + } + } + else if (torrent_paused_alert* p = dynamic_cast(a)) { + if (p->handle.is_valid()) { + QTorrentHandle h(p->handle); + if (!HiddenData::hasData(h.hash())) { + if (!h.has_error()) + h.save_resume_data(); + emit pausedTorrent(h); + } + } + } + else if (tracker_error_alert* p = dynamic_cast(a)) { + // Level: fatal + QTorrentHandle h(p->handle); + if (h.is_valid()) { + // Authentication + if (p->status_code != 401) { + qDebug("Received a tracker error for %s: %s", p->url.c_str(), p->msg.c_str()); + const QString tracker_url = misc::toQString(p->url); + QHash trackers_data = trackersInfos.value(h.hash(), QHash()); + TrackerInfos data = trackers_data.value(tracker_url, TrackerInfos(tracker_url)); + data.last_message = misc::toQStringU(p->msg); + trackers_data.insert(tracker_url, data); + trackersInfos[h.hash()] = trackers_data; + } else { + emit trackerAuthenticationRequired(h); + } + } + } + else if (tracker_reply_alert* p = dynamic_cast(a)) { + const QTorrentHandle h(p->handle); + if (h.is_valid()) { + qDebug("Received a tracker reply from %s (Num_peers=%d)", p->url.c_str(), p->num_peers); + // Connection was successful now. Remove possible old errors + QHash trackers_data = trackersInfos.value(h.hash(), QHash()); + const QString tracker_url = misc::toQString(p->url); + TrackerInfos data = trackers_data.value(tracker_url, TrackerInfos(tracker_url)); + data.last_message = ""; // Reset error/warning message + data.num_peers = p->num_peers; + trackers_data.insert(tracker_url, data); + trackersInfos[h.hash()] = trackers_data; + } + } + else if (tracker_warning_alert* p = dynamic_cast(a)) { + const QTorrentHandle h(p->handle); + if (h.is_valid()) { + // Connection was successful now but there is a warning message + QHash trackers_data = trackersInfos.value(h.hash(), QHash()); + const QString tracker_url = misc::toQString(p->url); + TrackerInfos data = trackers_data.value(tracker_url, TrackerInfos(tracker_url)); + data.last_message = misc::toQStringU(p->msg); // Store warning message + trackers_data.insert(tracker_url, data); + trackersInfos[h.hash()] = trackers_data; + qDebug("Received a tracker warning from %s: %s", p->url.c_str(), p->msg.c_str()); + } + } + else if (portmap_error_alert* p = dynamic_cast(a)) { + addConsoleMessage(tr("UPnP/NAT-PMP: Port mapping failure, message: %1").arg(misc::toQStringU(p->message())), "red"); + //emit UPnPError(QString(p->msg().c_str())); + } + else if (portmap_alert* p = dynamic_cast(a)) { + qDebug("UPnP Success, msg: %s", p->message().c_str()); + addConsoleMessage(tr("UPnP/NAT-PMP: Port mapping successful, message: %1").arg(misc::toQStringU(p->message())), "blue"); + //emit UPnPSuccess(QString(p->msg().c_str())); + } + else if (peer_blocked_alert* p = dynamic_cast(a)) { + boost::system::error_code ec; + string ip = p->ip.to_string(ec); + if (!ec) { + addPeerBanMessage(QString::fromLatin1(ip.c_str()), true); + //emit peerBlocked(QString::fromLatin1(ip.c_str())); + } + } + else if (peer_ban_alert* p = dynamic_cast(a)) { + boost::system::error_code ec; + string ip = p->ip.address().to_string(ec); + if (!ec) { + addPeerBanMessage(QString::fromLatin1(ip.c_str()), false); + //emit peerBlocked(QString::fromLatin1(ip.c_str())); + } + } + else if (fastresume_rejected_alert* p = dynamic_cast(a)) { + QTorrentHandle h(p->handle); + if (h.is_valid()) { + qDebug("/!\\ Fast resume failed for %s, reason: %s", qPrintable(h.name()), p->message().c_str()); + if (p->error.value() == 134 && TorrentPersistentData::isSeed(h.hash()) && h.has_missing_files()) { + const QString hash = h.hash(); + // Mismatching file size (files were probably moved + addConsoleMessage(tr("File sizes mismatch for torrent %1, pausing it.").arg(h.name())); + TorrentPersistentData::setErrorState(hash, true); + pauseTorrent(hash); + } else { + addConsoleMessage(tr("Fast resume data was rejected for torrent %1, checking again...").arg(h.name()), QString::fromUtf8("red")); + addConsoleMessage(tr("Reason: %1").arg(misc::toQStringU(p->message()))); + } + } + } + else if (url_seed_alert* p = dynamic_cast(a)) { + addConsoleMessage(tr("Url seed lookup failed for url: %1, message: %2").arg(misc::toQString(p->url)).arg(misc::toQStringU(p->message())), QString::fromUtf8("red")); + //emit urlSeedProblem(QString::fromUtf8(p->url.c_str()), QString::fromUtf8(p->msg().c_str())); + } + else if (listen_succeeded_alert *p = dynamic_cast(a)) { + boost::system::error_code ec; + QString proto = "TCP"; +#if LIBTORRENT_VERSION_NUM >= 10000 + if (p->sock_type == listen_succeeded_alert::udp) + proto = "UDP"; + else if (p->sock_type == listen_succeeded_alert::tcp) + proto = "TCP"; + else if (p->sock_type == listen_succeeded_alert::tcp_ssl) + proto = "TCP_SSL"; +#endif + qDebug() << "Successfully listening on " << proto << p->endpoint.address().to_string(ec).c_str() << "/" << p->endpoint.port(); + addConsoleMessage(tr("qBittorrent is successfully listening on interface %1 port: %2/%3", "e.g: qBittorrent is successfully listening on interface 192.168.0.1 port: TCP/6881").arg(p->endpoint.address().to_string(ec).c_str()).arg(proto).arg(QString::number(p->endpoint.port())), "blue"); + // Force reannounce on all torrents because some trackers blacklist some ports + std::vector torrents = s->get_torrents(); + + std::vector::iterator it = torrents.begin(); + std::vector::iterator itend = torrents.end(); + for ( ; it != itend; ++it) { + it->force_reannounce(); + } + } + else if (listen_failed_alert *p = dynamic_cast(a)) { + boost::system::error_code ec; + QString proto = "TCP"; +#if LIBTORRENT_VERSION_NUM >= 10000 + if (p->sock_type == listen_failed_alert::udp) + proto = "UDP"; + else if (p->sock_type == listen_failed_alert::tcp) + proto = "TCP"; + else if (p->sock_type == listen_failed_alert::tcp_ssl) + proto = "TCP_SSL"; + else if (p->sock_type == listen_failed_alert::i2p) + proto = "I2P"; + else if (p->sock_type == listen_failed_alert::socks5) + proto = "SOCKS5"; +#endif + qDebug() << "Failed listening on " << proto << p->endpoint.address().to_string(ec).c_str() << "/" << p->endpoint.port(); + addConsoleMessage(tr("qBittorrent failed listening on interface %1 port: %2/%3. Reason: %4", "e.g: qBittorrent failed listening on interface 192.168.0.1 port: TCP/6881. Reason: already in use").arg(p->endpoint.address().to_string(ec).c_str()).arg(proto).arg(QString::number(p->endpoint.port())).arg(misc::toQStringU(p->error.message())), "red"); + } + else if (torrent_checked_alert* p = dynamic_cast(a)) { + QTorrentHandle h(p->handle); + if (h.is_valid()) { + const QString hash = h.hash(); + qDebug("%s have just finished checking", qPrintable(hash)); + // Save seed status + TorrentPersistentData::saveSeedStatus(h); + // Move to temp directory if necessary + if (!h.is_seed() && !defaultTempPath.isEmpty()) { + // Check if directory is different + const QDir current_dir(h.save_path()); + const QDir save_dir(getSavePath(h.hash())); + if (current_dir == save_dir) { + qDebug("Moving the torrent to the temp directory..."); + QString torrent_tmp_path = defaultTempPath; + h.move_storage(torrent_tmp_path); + } + } + emit torrentFinishedChecking(h); + if (torrentsToPausedAfterChecking.contains(hash)) { + torrentsToPausedAfterChecking.removeOne(hash); + h.pause(); + emit pausedTorrent(h); + } + } + } + else if (external_ip_alert *p = dynamic_cast(a)) { + boost::system::error_code ec; + addConsoleMessage(tr("External IP: %1", "e.g. External IP: 192.168.0.1").arg(p->external_address.to_string(ec).c_str()), "blue"); + } + } catch (const std::exception& e) { + qWarning() << "Caught exception in readAlerts(): " << e.what(); } } diff --git a/src/qtlibtorrent/qbtsession.h b/src/qtlibtorrent/qbtsession.h index 3a35b30e0..a794ad1da 100755 --- a/src/qtlibtorrent/qbtsession.h +++ b/src/qtlibtorrent/qbtsession.h @@ -51,6 +51,7 @@ #include "qtracker.h" #include "qtorrenthandle.h" #include "trackerinfos.h" +#include "alertdispatcher.h" #define MAX_SAMPLES 20 @@ -190,6 +191,7 @@ private: void updateRatioTimer(); void recoverPersistentData(const QString &hash, const std::vector &buf); void backupPersistentData(const QString &hash, boost::shared_ptr data); + void handleAlert(libtorrent::alert* a); private slots: void addTorrentsFromScanFolder(QStringList&); @@ -234,7 +236,6 @@ signals: private: // Bittorrent libtorrent::session *s; - QPointer timerAlerts; QPointer bd_scheduler; QMap > savepathLabel_fromurl; // Use QMap for compatibility with Qt < 4.7: qHash(QUrl) QHash > trackersInfos; @@ -287,6 +288,7 @@ private: #endif // DynDNS DNSUpdater *m_dynDNSUpdater; + QAlertDispatcher* m_alertDispatcher; }; #endif diff --git a/src/qtlibtorrent/qtlibtorrent.pri b/src/qtlibtorrent/qtlibtorrent.pri index 5ded68dcd..4af0adf18 100644 --- a/src/qtlibtorrent/qtlibtorrent.pri +++ b/src/qtlibtorrent/qtlibtorrent.pri @@ -5,11 +5,13 @@ HEADERS += $$PWD/qbtsession.h \ $$PWD/bandwidthscheduler.h \ $$PWD/trackerinfos.h \ $$PWD/torrentspeedmonitor.h \ - $$PWD/filterparserthread.h + $$PWD/filterparserthread.h \ + $$PWD/alertdispatcher.h SOURCES += $$PWD/qbtsession.cpp \ $$PWD/qtorrenthandle.cpp \ - $$PWD/torrentspeedmonitor.cpp + $$PWD/torrentspeedmonitor.cpp \ + $$PWD/alertdispatcher.cpp !contains(DEFINES, DISABLE_GUI) { HEADERS += $$PWD/torrentmodel.h \