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) { + 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; + } + save_resume_data_alert const* rd = dynamic_cast(a); + if (!rd) { + continue; + } + // Saving fast resume data was successful --num_resume_data; - s->pop_alert(); + 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 - 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; + s->remove_torrent(rd->handle); + } catch(libtorrent::invalid_handle&) {} + + delete a; } - // 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); - s->pop_alert(); - } catch(libtorrent::invalid_handle&) {} } } @@ -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()); - } + } + // 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(); + } + // 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..."); } - 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(); + 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; } - #endif // DISABLE_GUI + 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 (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 (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 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 { - // 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); - } - } + // Single-file torrent + // Renaming a file corresponds to changing the save path + emit savePathChanged(h); } } - 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); + } + 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); } - 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 (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 (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 (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); + + 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_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 (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 (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 (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 (tracker_error_alert* p = dynamic_cast(a.get())) { - // Level: fatal + } + else if (torrent_paused_alert* p = dynamic_cast(a)) { + if (p->handle.is_valid()) { 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); - } + if (!HiddenData::hasData(h.hash())) { + if (!h.has_error()) + h.save_resume_data(); + emit pausedTorrent(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()); + } + 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); - 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 + data.last_message = misc::toQStringU(p->msg); 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 { + emit trackerAuthenticationRequired(h); } } - 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 (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 (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 (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 (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 (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.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 (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.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 (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.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"; + } + 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"; + 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(); - } + 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"; + } + 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"; + 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()) { - // 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); + 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.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(); } - - a = s->pop_alert(); + 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 \