From 4ba2c3d385aeddc1a90ef96f54218a9175688167 Mon Sep 17 00:00:00 2001 From: Christophe Dumez Date: Sat, 24 Nov 2007 22:01:31 +0000 Subject: [PATCH] - FEATURE: Greatly improved ETA calculation algorithm (use GASA) --- Changelog | 2 +- TODO | 22 ++------ src/bittorrent.cpp | 127 ++++++++++++++++++++++----------------------- src/bittorrent.h | 10 ++-- src/misc.h | 2 +- 5 files changed, 72 insertions(+), 91 deletions(-) diff --git a/Changelog b/Changelog index 3014bb8e6..27041a6e0 100644 --- a/Changelog +++ b/Changelog @@ -42,12 +42,12 @@ - FEATURE: Added an option to display current transfer speeds in title bar - FEATURE: Torrent content is now displayed as a tree - FEATURE: Better media file preview (player detected automatically) + - FEATURE: Greatly improved ETA calculation algorithm (use GASA) - I18N: Added Hungarian translation - I18N: Added Brazilian translation - BUGFIX: Progress of paused torrents is now correct on restart - BUGFIX: clear the results of a search stops searching - BUGFIX: Progress column gets sorted on restart it is was during last execution - - BUGFIX: Made ETA more reliable using stats instead of instant values - BUGFIX: Remove torrent from hard drive used to delete parent folder if empty - BUGFIX: Fixed a crash when filtering all the files in a torrent - BUGFIX: Reload torrent only when necessary (properties) diff --git a/TODO b/TODO index f17008a3c..a67ccccb2 100644 --- a/TODO +++ b/TODO @@ -29,6 +29,7 @@ - Allow user to organize the downloads into categories/folders? // in v1.1.0 +<<<<<<< .courant - Tabs support in search - Allow to hide columns? - Allow to scan multiple directories? (useful?) @@ -50,22 +51,5 @@ -> in download list -> in seeding list -// V1.0.0 TODO -- Choose a better ETA calculation algorithm (and avoid overflows) - -rc8->rc9 changelog: -- FEATURE: Better media file preview (player detected automatically) -- BUGFIX: Remember properties window size and position -- BUGFIX: Added HTTP and SOCKS5 proxy support in downloads from urls, RSS -- BUGFIX: Added HTTP proxy support in search engine (no SOCKS yet) -- BUGFIX: Do no pause torrents before saving fastresume data anymore (no longer needed) -- BUGFIX: Save fast resume data regularly (every 60 seconds) to avoid downloading from scratch if qBittorrent crashes -- BUGFIX: Do not save fastresume data for checking torrents anymore -- BUGFIX: Saving trackers file only when necessary -- BUGFIX: Fixed possible segfault when unfiltering files in torrent addition dialog -- BUGFIX: Fixed possible overflow in ETA calculation -- BUGFIX: title bar is now reset when "Display speed in title" is disabled -- BUGFIX: Fixed HTTP_PW and SOCKS5_PW in proxy combobox -- BUGFIX: Fixed proxy auth disable problem when disabling proxy -- BUGFIX: Fixed proxy layout in program preferences -- BUGFIX: Fixed everlasting libtorrent session destruction on exit +rc9->rc10 changelog: +- FEATURE: Greatly improved ETA calculation algorithm (use GASA) diff --git a/src/bittorrent.cpp b/src/bittorrent.cpp index 7d84e02d8..d96c8001c 100644 --- a/src/bittorrent.cpp +++ b/src/bittorrent.cpp @@ -38,8 +38,6 @@ #include #include -#define ETAS_MAX_VALUES 3 -#define ETA_REFRESH_INTERVAL 10000 #define MAX_TRACKER_ERRORS 2 // Main constructor @@ -55,9 +53,6 @@ bittorrent::bittorrent() : timerScan(0), DHTEnabled(false), preAllocateAll(false timerAlerts = new QTimer(); connect(timerAlerts, SIGNAL(timeout()), this, SLOT(readAlerts())); timerAlerts->start(3000); - ETARefresher = new QTimer(); - connect(ETARefresher, SIGNAL(timeout()), this, SLOT(updateETAs())); - ETARefresher->start(ETA_REFRESH_INTERVAL); fastResumeSaver = new QTimer(); connect(fastResumeSaver, SIGNAL(timeout()), this, SLOT(saveFastResumeAndRatioData())); fastResumeSaver->start(60000); @@ -67,6 +62,7 @@ bittorrent::bittorrent() : timerScan(0), DHTEnabled(false), preAllocateAll(false connect(downloader, SIGNAL(downloadFailure(QString, QString)), this, SLOT(handleDownloadFailure(QString, QString))); // File deleter (thread) deleter = new deleteThread(this); + BigRatioTimer = 0; qDebug("* BTSession constructed"); } @@ -78,7 +74,8 @@ bittorrent::~bittorrent() { delete deleter; delete fastResumeSaver; delete timerAlerts; - delete ETARefresher; + if(BigRatioTimer != 0) + delete BigRatioTimer; delete downloader; // Delete BT session delete s; @@ -143,57 +140,34 @@ void bittorrent::startTorrentsInPause(bool b) { addInPause = b; } -void bittorrent::updateETAs() { - QString hash; - foreach(hash, unfinishedTorrents) { - QTorrentHandle h = getTorrentHandle(hash); - if(h.is_valid()) { - if(h.is_paused()) continue; - QString hash = h.hash(); - QList listEtas = ETAstats.value(hash, QList()); - // XXX: We can still get an overflow if remaining file size is approximately - // 8.38*10^5 TiB (let's assume this can't happen) - if(h.download_payload_rate() > 0.1) { - if(listEtas.size() == ETAS_MAX_VALUES) { - listEtas.removeFirst(); - } - listEtas << (qlonglong)((h.actual_size()-h.total_wanted_done())/(double)h.download_payload_rate()); - ETAstats[hash] = listEtas; - qlonglong moy = 0; - qlonglong val; - unsigned int nbETAs = listEtas.size(); - Q_ASSERT(nbETAs); - foreach(val, listEtas) { - moy += (qlonglong)((double)val/(double)nbETAs); - } - if(moy < 0) { - if(ETAstats.contains(hash)) { - ETAstats.remove(hash); - } - if(ETAs.contains(hash)) { - ETAs.remove(hash); - } - } else { - ETAs[hash] = moy; - } +// Calculate the ETA using GASA +// GASA: global Average Speed Algorithm +qlonglong bittorrent::getETA(QString hash) const { + QTorrentHandle h = getTorrentHandle(hash); + if(!h.is_valid()) return -1; + switch(h.state()) { + case torrent_status::downloading: + case torrent_status::connecting_to_tracker: { + if(!TorrentsStartTime.contains(hash)) return -1; + int timeElapsed = TorrentsStartTime.value(hash).secsTo(QDateTime::currentDateTime()); + double avg_speed; + if(timeElapsed) { + size_type data_origin = TorrentsStartData.value(hash, 0); + avg_speed = (double)(h.total_payload_download()-data_origin) / (double)timeElapsed; } else { - // Speed is too low, we don't want an overflow. - if(ETAstats.contains(hash)) { - ETAstats.remove(hash); - } - if(ETAs.contains(hash)) { - ETAs.remove(hash); - } + return -1; } + if(avg_speed) { + return (qlonglong) floor((double) (h.actual_size() - h.total_wanted_done()) / avg_speed); + } else { + return -1; + } + } + default: + return -1; } - // Delete big ratios - if(max_ratio != -1) - deleteBigRatios(); -} - -long bittorrent::getETA(QString hash) const{ - return ETAs.value(hash, -1); + } // Return the torrent handle, given its hash @@ -240,9 +214,9 @@ void bittorrent::deleteTorrent(QString hash, bool permanent) { foreach(file, files) { torrentBackup.remove(file); } - // Remove it from ETAs hash tables - ETAstats.remove(hash); - ETAs.remove(hash); + // Remove it from TorrentsStartTime hash table + TorrentsStartTime.remove(hash); + TorrentsStartData.remove(hash); // Remove tracker errors trackersErrors.remove(hash); // Remove it from ratio table @@ -290,6 +264,9 @@ void bittorrent::setUnfinishedTorrent(QString hash) { } if(!unfinishedTorrents.contains(hash)) { unfinishedTorrents << hash; + QTorrentHandle h = getTorrentHandle(hash); + TorrentsStartData[hash] = h.total_payload_download(); + TorrentsStartTime[hash] = QDateTime::currentDateTime(); } } @@ -307,9 +284,9 @@ void bittorrent::setFinishedTorrent(QString hash) { if(index != -1) { unfinishedTorrents.removeAt(index); } - // Remove it from ETAs hash tables - ETAstats.remove(hash); - ETAs.remove(hash); + // Remove it from TorrentsStartTime hash table + TorrentsStartTime.remove(hash); + TorrentsStartData.remove(hash); // Save fast resume data saveFastResumeAndRatioData(hash); } @@ -337,9 +314,9 @@ bool bittorrent::pauseTorrent(QString hash) { paused_file.open(QIODevice::WriteOnly | QIODevice::Text); paused_file.close(); } - // Remove it from ETAs hash tables - ETAstats.remove(hash); - ETAs.remove(hash); + // Remove it from TorrentsStartTime hash table + TorrentsStartTime.remove(hash); + TorrentsStartData.remove(hash); return change; } @@ -348,6 +325,9 @@ bool bittorrent::resumeTorrent(QString hash) { bool success = false; QTorrentHandle h = getTorrentHandle(hash); if(h.is_valid() && h.is_paused()) { + // Save Addition DateTime + TorrentsStartData[hash] = h.total_payload_download(); + TorrentsStartTime[hash] = QDateTime::currentDateTime(); h.resume(); success = true; } @@ -1018,9 +998,22 @@ void bittorrent::setGlobalRatio(float ratio) { // be automatically deleted void bittorrent::setDeleteRatio(float ratio) { if(ratio != -1 && ratio < 1.) ratio = 1.; - max_ratio = ratio; - qDebug("* Set deleteRatio to %.1f", max_ratio); - deleteBigRatios(); + if(max_ratio == -1 && ratio != -1) { + Q_ASSERT(!BigRatioTimer); + BigRatioTimer = new QTimer(this); + connect(BigRatioTimer, SIGNAL(timeout()), this, SLOT(deleteBigRatios())); + BigRatioTimer->start(5000); + } else { + if(max_ratio != -1 && ratio == -1) { + delete BigRatioTimer; + BigRatioTimer = 0; + } + } + if(max_ratio != ratio) { + max_ratio = ratio; + qDebug("* Set deleteRatio to %.1f", max_ratio); + deleteBigRatios(); + } } bool bittorrent::loadTrackerFile(QString hash) { @@ -1175,6 +1168,10 @@ void bittorrent::readAlerts() { // Pause torrent pauseTorrent(hash); qDebug("%s was paused after checking", hash.toUtf8().data()); + } else { + // Save Addition DateTime + TorrentsStartTime[hash] = QDateTime::currentDateTime(); + TorrentsStartData[hash] = h.total_payload_download(); } emit torrentFinishedChecking(hash); } diff --git a/src/bittorrent.h b/src/bittorrent.h index 9b0060424..01896a421 100644 --- a/src/bittorrent.h +++ b/src/bittorrent.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -45,14 +46,14 @@ class bittorrent : public QObject{ QTimer *timerScan; QTimer *timerAlerts; QTimer *fastResumeSaver; + QTimer *BigRatioTimer; bool DHTEnabled; downloadThread *downloader; QString defaultSavePath; QStringList torrentsToPauseAfterChecking; - QHash > ETAstats; - QHash ETAs; + QHash TorrentsStartTime; + QHash TorrentsStartData; QHash > ratioData; - QTimer *ETARefresher; QHash > > trackersErrors; deleteThread *deleter; QStringList finishedTorrents; @@ -82,7 +83,7 @@ class bittorrent : public QObject{ session_status getSessionStatus() const; int getListenPort() const; QStringList getTorrentsToPauseAfterChecking() const; - long getETA(QString hash) const; + qlonglong getETA(QString hash) const; float getRealRatio(QString hash) const; session* getSession() const; QList > getTrackersErrors(QString hash) const; @@ -108,7 +109,6 @@ class bittorrent : public QObject{ void enableIPFilter(ip_filter filter); void disableIPFilter(); void resumeUnfinishedTorrents(); - void updateETAs(); void saveTorrentSpeedLimits(QString hash); void loadTorrentSpeedLimits(QString hash); void saveDownloadUploadForTorrent(QString hash); diff --git a/src/misc.h b/src/misc.h index efe3c8c7e..a8a29dc79 100644 --- a/src/misc.h +++ b/src/misc.h @@ -298,7 +298,7 @@ class misc : public QObject{ // Take a number of seconds and return an user-friendly // time duration like "1d 2h 10m". - static QString userFriendlyDuration(const long int seconds) { + static QString userFriendlyDuration(qlonglong seconds) { if(seconds < 0) { return tr("Unknown"); }