From c24610ec809419c03f178f2ef93b3b5e65657978 Mon Sep 17 00:00:00 2001 From: "Vladimir Golovnev (Glassez)" Date: Thu, 20 Nov 2014 17:46:14 +0300 Subject: [PATCH 01/10] WebUI: Improve btjson.* indentation. --- src/webui/btjson.cpp | 358 ++++++++++++++++++++++--------------------- src/webui/btjson.h | 17 +- 2 files changed, 192 insertions(+), 183 deletions(-) diff --git a/src/webui/btjson.cpp b/src/webui/btjson.cpp index 5faa1f4c3..6756ac145 100644 --- a/src/webui/btjson.cpp +++ b/src/webui/btjson.cpp @@ -47,32 +47,32 @@ using namespace libtorrent; #if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0) #define CACHED_VARIABLE(VARTYPE, VAR, DUR) \ - static VARTYPE VAR; \ - static QElapsedTimer cacheTimer; \ - static bool initialized = false; \ - if (initialized && !cacheTimer.hasExpired(DUR)) \ + static VARTYPE VAR; \ + static QElapsedTimer cacheTimer; \ + static bool initialized = false; \ + if (initialized && !cacheTimer.hasExpired(DUR)) \ return json::toJson(VAR); \ - initialized = true; \ - cacheTimer.start(); \ - VAR = VARTYPE() + initialized = true; \ + cacheTimer.start(); \ + VAR = VARTYPE() #define CACHED_VARIABLE_FOR_HASH(VARTYPE, VAR, DUR, HASH) \ - static VARTYPE VAR; \ - static QString prev_hash; \ - static QElapsedTimer cacheTimer; \ - if (prev_hash == HASH && !cacheTimer.hasExpired(DUR)) \ + static VARTYPE VAR; \ + static QString prev_hash; \ + static QElapsedTimer cacheTimer; \ + if (prev_hash == HASH && !cacheTimer.hasExpired(DUR)) \ return json::toJson(VAR); \ - prev_hash = HASH; \ - cacheTimer.start(); \ - VAR = VARTYPE() + prev_hash = HASH; \ + cacheTimer.start(); \ + VAR = VARTYPE() #else // We don't support caching for Qt < 4.7 at the moment #define CACHED_VARIABLE(VARTYPE, VAR, DUR) \ - VARTYPE VAR + VARTYPE VAR #define CACHED_VARIABLE_FOR_HASH(VARTYPE, VAR, DUR, HASH) \ - VARTYPE VAR + VARTYPE VAR #endif @@ -134,61 +134,63 @@ static const char KEY_TRANSFER_UPDATA[] = "up_info_data"; static QVariantMap toMap(const QTorrentHandle& h) { - libtorrent::torrent_status status = h.status(torrent_handle::query_accurate_download_counters); - - QVariantMap ret; - ret[KEY_TORRENT_HASH] = h.hash(); - ret[KEY_TORRENT_NAME] = h.name(); - ret[KEY_TORRENT_SIZE] = static_cast(status.total_wanted); - ret[KEY_TORRENT_PROGRESS] = h.progress(status); - ret[KEY_TORRENT_DLSPEED] = status.download_payload_rate; - ret[KEY_TORRENT_UPSPEED] = status.upload_payload_rate; - if (QBtSession::instance()->isQueueingEnabled() && h.queue_position(status) >= 0) - ret[KEY_TORRENT_PRIORITY] = h.queue_position(status); - else - ret[KEY_TORRENT_PRIORITY] = -1; - ret[KEY_TORRENT_SEEDS] = status.num_seeds; - ret[KEY_TORRENT_NUM_COMPLETE] = status.num_complete; - ret[KEY_TORRENT_LEECHS] = status.num_peers - status.num_seeds; - ret[KEY_TORRENT_NUM_INCOMPLETE] = status.num_incomplete; - const qreal ratio = QBtSession::instance()->getRealRatio(status); - ret[KEY_TORRENT_RATIO] = (ratio > 100.) ? -1 : ratio; - qulonglong eta = 0; - QString state; - if (h.is_paused(status)) { - if (h.has_error(status)) - state = "error"; + libtorrent::torrent_status status = h.status(torrent_handle::query_accurate_download_counters); + + QVariantMap ret; + ret[KEY_TORRENT_HASH] = h.hash(); + ret[KEY_TORRENT_NAME] = h.name(); + ret[KEY_TORRENT_SIZE] = static_cast(status.total_wanted); + ret[KEY_TORRENT_PROGRESS] = h.progress(status); + ret[KEY_TORRENT_DLSPEED] = status.download_payload_rate; + ret[KEY_TORRENT_UPSPEED] = status.upload_payload_rate; + if (QBtSession::instance()->isQueueingEnabled() && h.queue_position(status) >= 0) + ret[KEY_TORRENT_PRIORITY] = h.queue_position(status); else - state = h.is_seed(status) ? "pausedUP" : "pausedDL"; - } else { - if (QBtSession::instance()->isQueueingEnabled() && h.is_queued(status)) - state = h.is_seed(status) ? "queuedUP" : "queuedDL"; + ret[KEY_TORRENT_PRIORITY] = -1; + ret[KEY_TORRENT_SEEDS] = status.num_seeds; + ret[KEY_TORRENT_NUM_COMPLETE] = status.num_complete; + ret[KEY_TORRENT_LEECHS] = status.num_peers - status.num_seeds; + ret[KEY_TORRENT_NUM_INCOMPLETE] = status.num_incomplete; + const qreal ratio = QBtSession::instance()->getRealRatio(status); + ret[KEY_TORRENT_RATIO] = (ratio > 100.) ? -1 : ratio; + qulonglong eta = 0; + QString state; + if (h.is_paused(status)) { + if (h.has_error(status)) + state = "error"; + else + state = h.is_seed(status) ? "pausedUP" : "pausedDL"; + } else { - switch (status.state) { - case torrent_status::finished: - case torrent_status::seeding: - state = status.upload_payload_rate > 0 ? "uploading" : "stalledUP"; - break; - case torrent_status::allocating: - case torrent_status::checking_files: - case torrent_status::queued_for_checking: - case torrent_status::checking_resume_data: - state = h.is_seed(status) ? "checkingUP" : "checkingDL"; - break; - case torrent_status::downloading: - case torrent_status::downloading_metadata: - state = status.download_payload_rate > 0 ? "downloading" : "stalledDL"; - eta = QBtSession::instance()->getETA(h.hash(), status); - break; - default: - qWarning("Unrecognized torrent status, should not happen!!! status was %d", h.state()); - } + if (QBtSession::instance()->isQueueingEnabled() && h.is_queued(status)) { + state = h.is_seed(status) ? "queuedUP" : "queuedDL"; + } + else { + switch (status.state) { + case torrent_status::finished: + case torrent_status::seeding: + state = status.upload_payload_rate > 0 ? "uploading" : "stalledUP"; + break; + case torrent_status::allocating: + case torrent_status::checking_files: + case torrent_status::queued_for_checking: + case torrent_status::checking_resume_data: + state = h.is_seed(status) ? "checkingUP" : "checkingDL"; + break; + case torrent_status::downloading: + case torrent_status::downloading_metadata: + state = status.download_payload_rate > 0 ? "downloading" : "stalledDL"; + eta = QBtSession::instance()->getETA(h.hash(), status); + break; + default: + qWarning("Unrecognized torrent status, should not happen!!! status was %d", h.state()); + } + } } - } - ret[KEY_TORRENT_ETA] = eta ? eta : MAX_ETA; - ret[KEY_TORRENT_STATE] = state; + ret[KEY_TORRENT_ETA] = eta ? eta : MAX_ETA; + ret[KEY_TORRENT_STATE] = state; - return ret; + return ret; } /** @@ -213,14 +215,17 @@ static QVariantMap toMap(const QTorrentHandle& h) */ QByteArray btjson::getTorrents() { - CACHED_VARIABLE(QVariantList, torrent_list, CACHE_DURATION_MS); - std::vector torrents = QBtSession::instance()->getTorrents(); - std::vector::const_iterator it = torrents.begin(); - std::vector::const_iterator end = torrents.end(); - for( ; it != end; ++it) { - torrent_list.append(toMap(QTorrentHandle(*it))); - } - return json::toJson(torrent_list); + QVariantList torrent_list; + + std::vector torrents = QBtSession::instance()->getTorrents(); + std::vector::const_iterator it = torrents.begin(); + std::vector::const_iterator end = torrents.end(); + + for( ; it != end; ++it) { + torrent_list.append(toMap(QTorrentHandle(*it))); + } + + return json::toJson(torrent_list); } /** @@ -235,39 +240,40 @@ QByteArray btjson::getTorrents() */ QByteArray btjson::getTrackersForTorrent(const QString& hash) { - CACHED_VARIABLE_FOR_HASH(QVariantList, tracker_list, CACHE_DURATION_MS, hash); - try { - QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash); - QHash trackers_data = QBtSession::instance()->getTrackersInfo(hash); - std::vector vect_trackers = h.trackers(); - std::vector::const_iterator it = vect_trackers.begin(); - std::vector::const_iterator end = vect_trackers.end(); - for (; it != end; ++it) { - QVariantMap tracker_dict; - const QString tracker_url = misc::toQString(it->url); - tracker_dict[KEY_TRACKER_URL] = tracker_url; - const TrackerInfos data = trackers_data.value(tracker_url, TrackerInfos(tracker_url)); - QString status; - if (it->verified) - status = tr("Working"); - else { - if (it->updating && it->fails == 0) - status = tr("Updating..."); - else - status = it->fails > 0 ? tr("Not working") : tr("Not contacted yet"); - } - tracker_dict[KEY_TRACKER_STATUS] = status; - tracker_dict[KEY_TRACKER_PEERS] = static_cast(trackers_data.value(tracker_url, TrackerInfos(tracker_url)).num_peers); - tracker_dict[KEY_TRACKER_MSG] = data.last_message.trimmed(); - - tracker_list.append(tracker_dict); + CACHED_VARIABLE_FOR_HASH(QVariantList, tracker_list, CACHE_DURATION_MS, hash); + try { + QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash); + QHash trackers_data = QBtSession::instance()->getTrackersInfo(hash); + std::vector vect_trackers = h.trackers(); + std::vector::const_iterator it = vect_trackers.begin(); + std::vector::const_iterator end = vect_trackers.end(); + for (; it != end; ++it) { + QVariantMap tracker_dict; + const QString tracker_url = misc::toQString(it->url); + tracker_dict[KEY_TRACKER_URL] = tracker_url; + const TrackerInfos data = trackers_data.value(tracker_url, TrackerInfos(tracker_url)); + QString status; + if (it->verified) + status = tr("Working"); + else { + if (it->updating && it->fails == 0) + status = tr("Updating..."); + else + status = it->fails > 0 ? tr("Not working") : tr("Not contacted yet"); + } + tracker_dict[KEY_TRACKER_STATUS] = status; + tracker_dict[KEY_TRACKER_PEERS] = static_cast(trackers_data.value(tracker_url, TrackerInfos(tracker_url)).num_peers); + tracker_dict[KEY_TRACKER_MSG] = data.last_message.trimmed(); + + tracker_list.append(tracker_dict); + } + } + catch(const std::exception& e) { + qWarning() << Q_FUNC_INFO << "Invalid torrent: " << misc::toQStringU(e.what()); + return QByteArray(); } - } catch(const std::exception& e) { - qWarning() << Q_FUNC_INFO << "Invalid torrent: " << misc::toQStringU(e.what()); - return QByteArray(); - } - return json::toJson(tracker_list); + return json::toJson(tracker_list); } /** @@ -294,41 +300,42 @@ QByteArray btjson::getTrackersForTorrent(const QString& hash) */ QByteArray btjson::getPropertiesForTorrent(const QString& hash) { - CACHED_VARIABLE_FOR_HASH(QVariantMap, data, CACHE_DURATION_MS, hash); - try { - QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash); - - libtorrent::torrent_status status = h.status(torrent_handle::query_accurate_download_counters); - if (!status.has_metadata) - return QByteArray(); - - // Save path - QString save_path = fsutils::toNativePath(TorrentPersistentData::getSavePath(hash)); - if (save_path.isEmpty()) - save_path = fsutils::toNativePath(h.save_path()); - data[KEY_PROP_SAVE_PATH] = save_path; - data[KEY_PROP_CREATION_DATE] = h.creation_date_unix(); - data[KEY_PROP_PIECE_SIZE] = static_cast(h.piece_length()); - data[KEY_PROP_COMMENT] = h.comment(); - data[KEY_PROP_WASTED] = static_cast(status.total_failed_bytes + status.total_redundant_bytes); - data[KEY_PROP_UPLOADED] = static_cast(status.all_time_upload); - data[KEY_PROP_UPLOADED_SESSION] = static_cast(status.total_payload_upload); - data[KEY_PROP_DOWNLOADED] = static_cast(status.all_time_download); - data[KEY_PROP_DOWNLOADED_SESSION] = static_cast(status.total_payload_download); - data[KEY_PROP_UP_LIMIT] = h.upload_limit() <= 0 ? -1 : h.upload_limit(); - data[KEY_PROP_DL_LIMIT] = h.download_limit() <= 0 ? -1 : h.download_limit(); - data[KEY_PROP_TIME_ELAPSED] = status.active_time; - data[KEY_PROP_SEEDING_TIME] = status.seeding_time; - data[KEY_PROP_CONNECT_COUNT] = status.num_connections; - data[KEY_PROP_CONNECT_COUNT_LIMIT] = status.connections_limit; - const qreal ratio = QBtSession::instance()->getRealRatio(status); - data[KEY_PROP_RATIO] = ratio > 100. ? -1 : ratio; - } catch(const std::exception& e) { - qWarning() << Q_FUNC_INFO << "Invalid torrent: " << misc::toQStringU(e.what()); - return QByteArray(); - } + CACHED_VARIABLE_FOR_HASH(QVariantMap, data, CACHE_DURATION_MS, hash); + try { + QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash); + + libtorrent::torrent_status status = h.status(torrent_handle::query_accurate_download_counters); + if (!status.has_metadata) + return QByteArray(); + + // Save path + QString save_path = fsutils::toNativePath(TorrentPersistentData::getSavePath(hash)); + if (save_path.isEmpty()) + save_path = fsutils::toNativePath(h.save_path()); + data[KEY_PROP_SAVE_PATH] = save_path; + data[KEY_PROP_CREATION_DATE] = h.creation_date_unix(); + data[KEY_PROP_PIECE_SIZE] = static_cast(h.piece_length()); + data[KEY_PROP_COMMENT] = h.comment(); + data[KEY_PROP_WASTED] = static_cast(status.total_failed_bytes + status.total_redundant_bytes); + data[KEY_PROP_UPLOADED] = static_cast(status.all_time_upload); + data[KEY_PROP_UPLOADED_SESSION] = static_cast(status.total_payload_upload); + data[KEY_PROP_DOWNLOADED] = static_cast(status.all_time_download); + data[KEY_PROP_DOWNLOADED_SESSION] = static_cast(status.total_payload_download); + data[KEY_PROP_UP_LIMIT] = h.upload_limit() <= 0 ? -1 : h.upload_limit(); + data[KEY_PROP_DL_LIMIT] = h.download_limit() <= 0 ? -1 : h.download_limit(); + data[KEY_PROP_TIME_ELAPSED] = status.active_time; + data[KEY_PROP_SEEDING_TIME] = status.seeding_time; + data[KEY_PROP_CONNECT_COUNT] = status.num_connections; + data[KEY_PROP_CONNECT_COUNT_LIMIT] = status.connections_limit; + const qreal ratio = QBtSession::instance()->getRealRatio(status); + data[KEY_PROP_RATIO] = ratio > 100. ? -1 : ratio; + } + catch(const std::exception& e) { + qWarning() << Q_FUNC_INFO << "Invalid torrent: " << misc::toQStringU(e.what()); + return QByteArray(); + } - return json::toJson(data); + return json::toJson(data); } /** @@ -344,36 +351,37 @@ QByteArray btjson::getPropertiesForTorrent(const QString& hash) */ QByteArray btjson::getFilesForTorrent(const QString& hash) { - CACHED_VARIABLE_FOR_HASH(QVariantList, file_list, CACHE_DURATION_MS, hash); - try { - QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash); - if (!h.has_metadata()) - return QByteArray(); - - const std::vector priorities = h.file_priorities(); - std::vector fp; - h.file_progress(fp); - for (int i = 0; i < h.num_files(); ++i) { - QVariantMap file_dict; - QString fileName = h.filename_at(i); - if (fileName.endsWith(".!qB", Qt::CaseInsensitive)) - fileName.chop(4); - file_dict[KEY_FILE_NAME] = fsutils::toNativePath(fileName); - const size_type size = h.filesize_at(i); - file_dict[KEY_FILE_SIZE] = static_cast(size); - file_dict[KEY_FILE_PROGRESS] = (size > 0) ? (fp[i] / (double) size) : 1.; - file_dict[KEY_FILE_PRIORITY] = priorities[i]; - if (i == 0) - file_dict[KEY_FILE_IS_SEED] = h.is_seed(); - - file_list.append(file_dict); + CACHED_VARIABLE_FOR_HASH(QVariantList, file_list, CACHE_DURATION_MS, hash); + try { + QTorrentHandle h = QBtSession::instance()->getTorrentHandle(hash); + if (!h.has_metadata()) + return QByteArray(); + + const std::vector priorities = h.file_priorities(); + std::vector fp; + h.file_progress(fp); + for (int i = 0; i < h.num_files(); ++i) { + QVariantMap file_dict; + QString fileName = h.filename_at(i); + if (fileName.endsWith(".!qB", Qt::CaseInsensitive)) + fileName.chop(4); + file_dict[KEY_FILE_NAME] = fsutils::toNativePath(fileName); + const size_type size = h.filesize_at(i); + file_dict[KEY_FILE_SIZE] = static_cast(size); + file_dict[KEY_FILE_PROGRESS] = (size > 0) ? (fp[i] / (double) size) : 1.; + file_dict[KEY_FILE_PRIORITY] = priorities[i]; + if (i == 0) + file_dict[KEY_FILE_IS_SEED] = h.is_seed(); + + file_list.append(file_dict); + } + } + catch (const std::exception& e) { + qWarning() << Q_FUNC_INFO << "Invalid torrent: " << misc::toQStringU(e.what()); + return QByteArray(); } - } catch (const std::exception& e) { - qWarning() << Q_FUNC_INFO << "Invalid torrent: " << misc::toQStringU(e.what()); - return QByteArray(); - } - return json::toJson(file_list); + return json::toJson(file_list); } /** @@ -388,11 +396,11 @@ QByteArray btjson::getFilesForTorrent(const QString& hash) */ QByteArray btjson::getTransferInfo() { - CACHED_VARIABLE(QVariantMap, info, CACHE_DURATION_MS); - session_status sessionStatus = QBtSession::instance()->getSessionStatus(); - info[KEY_TRANSFER_DLSPEED] = sessionStatus.payload_download_rate; - info[KEY_TRANSFER_DLDATA] = static_cast(sessionStatus.total_payload_download); - info[KEY_TRANSFER_UPSPEED] = sessionStatus.payload_upload_rate; - info[KEY_TRANSFER_UPDATA] = static_cast(sessionStatus.total_payload_upload); - return json::toJson(info); + CACHED_VARIABLE(QVariantMap, info, CACHE_DURATION_MS); + session_status sessionStatus = QBtSession::instance()->getSessionStatus(); + info[KEY_TRANSFER_DLSPEED] = sessionStatus.payload_download_rate; + info[KEY_TRANSFER_DLDATA] = static_cast(sessionStatus.total_payload_download); + info[KEY_TRANSFER_UPSPEED] = sessionStatus.payload_upload_rate; + info[KEY_TRANSFER_UPDATA] = static_cast(sessionStatus.total_payload_upload); + return json::toJson(info); } diff --git a/src/webui/btjson.h b/src/webui/btjson.h index 93d60aef6..85be78bb4 100644 --- a/src/webui/btjson.h +++ b/src/webui/btjson.h @@ -34,18 +34,19 @@ #include #include -class btjson { - Q_DECLARE_TR_FUNCTIONS(misc) +class btjson +{ + Q_DECLARE_TR_FUNCTIONS(misc) private: - btjson() {} + btjson() {} public: - static QByteArray getTorrents(); - static QByteArray getTrackersForTorrent(const QString& hash); - static QByteArray getPropertiesForTorrent(const QString& hash); - static QByteArray getFilesForTorrent(const QString& hash); - static QByteArray getTransferInfo(); + static QByteArray getTorrents(); + static QByteArray getTrackersForTorrent(const QString& hash); + static QByteArray getPropertiesForTorrent(const QString& hash); + static QByteArray getFilesForTorrent(const QString& hash); + static QByteArray getTransferInfo(); }; // class btjson #endif // BTJSON_H From e887f574d7112e06b7f30b7c05079fdf0abeb8e3 Mon Sep 17 00:00:00 2001 From: "Vladimir Golovnev (Glassez)" Date: Fri, 21 Nov 2014 12:42:02 +0300 Subject: [PATCH 02/10] Implement QTorrentHandle torrentState() and eta(). --- src/qtlibtorrent/qtorrenthandle.cpp | 89 ++++++++++++++++++++++++++++- src/qtlibtorrent/qtorrenthandle.h | 32 +++++++++++ 2 files changed, 120 insertions(+), 1 deletion(-) diff --git a/src/qtlibtorrent/qtorrenthandle.cpp b/src/qtlibtorrent/qtorrenthandle.cpp index d24b17839..e81d0fa99 100644 --- a/src/qtlibtorrent/qtorrenthandle.cpp +++ b/src/qtlibtorrent/qtorrenthandle.cpp @@ -40,6 +40,7 @@ #include "preferences.h" #include "qtorrenthandle.h" #include "torrentpersistentdata.h" +#include "qbtsession.h" #include #include #include @@ -403,7 +404,52 @@ bool QTorrentHandle::has_metadata() const { } void QTorrentHandle::file_progress(std::vector& fp) const { - torrent_handle::file_progress(fp, torrent_handle::piece_granularity); + torrent_handle::file_progress(fp, torrent_handle::piece_granularity); +} + +QTorrentState QTorrentHandle::torrentState() const { + QTorrentState state = QTorrentState::Unknown; + libtorrent::torrent_status s = status(torrent_handle::query_accurate_download_counters); + + if (is_paused(s)) { + if (has_error(s)) + state = QTorrentState::Error; + else + state = is_seed(s) ? QTorrentState::PausedUploading : QTorrentState::PausedDownloading; + } + else { + if (QBtSession::instance()->isQueueingEnabled() && is_queued(s)) { + state = is_seed(s) ? QTorrentState::QueuedUploading : QTorrentState::QueuedDownloading; + } + else { + switch (s.state) { + case torrent_status::finished: + case torrent_status::seeding: + state = s.upload_payload_rate > 0 ? QTorrentState::Uploading : QTorrentState::StalledUploading; + break; + case torrent_status::allocating: + case torrent_status::checking_files: + case torrent_status::queued_for_checking: + case torrent_status::checking_resume_data: + state = is_seed(s) ? QTorrentState::CheckingUploading : QTorrentState::CheckingDownloading; + break; + case torrent_status::downloading: + case torrent_status::downloading_metadata: + state = s.download_payload_rate > 0 ? QTorrentState::Downloading : QTorrentState::StalledDownloading; + break; + default: + qWarning("Unrecognized torrent status, should not happen!!! status was %d", this->state()); + } + } + } + + return state; +} + +qulonglong QTorrentHandle::eta() const +{ + libtorrent::torrent_status s = status(torrent_handle::query_accurate_download_counters); + return QBtSession::instance()->getETA(hash(), s); } // @@ -681,3 +727,44 @@ QString QTorrentHandle::filepath_at(const libtorrent::torrent_info &info, unsign return fsutils::fromNativePath(misc::toQStringU(info.files().file_path(index))); } + + +QTorrentState::QTorrentState(int value) + : m_value(value) +{ +} + +QString QTorrentState::toString() const +{ + switch (m_value) { + case Error: + return "error"; + case Uploading: + return "uploading"; + case PausedUploading: + return "pausedUP"; + case QueuedUploading: + return "queuedUP"; + case StalledUploading: + return "stalledUP"; + case CheckingUploading: + return "checkingUP"; + case Downloading: + return "downloading"; + case PausedDownloading: + return "pausedDL"; + case QueuedDownloading: + return "queuedDL"; + case StalledDownloading: + return "stalledDL"; + case CheckingDownloading: + return "checkingDL"; + default: + return "unknown"; + } +} + +QTorrentState::operator int() const +{ + return m_value; +} diff --git a/src/qtlibtorrent/qtorrenthandle.h b/src/qtlibtorrent/qtorrenthandle.h index f19a836d4..6c25dcc96 100644 --- a/src/qtlibtorrent/qtorrenthandle.h +++ b/src/qtlibtorrent/qtorrenthandle.h @@ -39,6 +39,36 @@ QT_BEGIN_NAMESPACE class QStringList; QT_END_NAMESPACE +class QTorrentState +{ +public: + enum { + Unknown = -1, + + Error, + + Uploading, + PausedUploading, + QueuedUploading, + StalledUploading, + CheckingUploading, + + Downloading, + PausedDownloading, + QueuedDownloading, + StalledDownloading, + CheckingDownloading + }; + + QTorrentState(int value); + + operator int() const; + QString toString() const; + +private: + int m_value; +}; + // A wrapper for torrent_handle in libtorrent // to interact well with Qt types class QTorrentHandle : public libtorrent::torrent_handle { @@ -94,6 +124,8 @@ public: void downloading_pieces(libtorrent::bitfield& bf) const; bool has_metadata() const; void file_progress(std::vector& fp) const; + QTorrentState torrentState() const; + qulonglong eta() const; // // Setters From 5edf0e251fd4b6e6b9713a4fe0f3ad817df2acc7 Mon Sep 17 00:00:00 2001 From: "Vladimir Golovnev (Glassez)" Date: Fri, 21 Nov 2014 12:42:46 +0300 Subject: [PATCH 03/10] WebUI: Implement server-side torrent filtering. --- src/webui/btjson.cpp | 9 ++- src/webui/btjson.h | 4 +- src/webui/qtorrentfilter.cpp | 118 +++++++++++++++++++++++++++++++++++ src/webui/qtorrentfilter.h | 63 +++++++++++++++++++ src/webui/requesthandler.cpp | 3 +- src/webui/webui.pri | 6 +- 6 files changed, 197 insertions(+), 6 deletions(-) create mode 100644 src/webui/qtorrentfilter.cpp create mode 100644 src/webui/qtorrentfilter.h diff --git a/src/webui/btjson.cpp b/src/webui/btjson.cpp index 6756ac145..1452ef67b 100644 --- a/src/webui/btjson.cpp +++ b/src/webui/btjson.cpp @@ -32,6 +32,7 @@ #include "fs_utils.h" #include "qbtsession.h" #include "torrentpersistentdata.h" +#include "qtorrentfilter.h" #include "jsonutils.h" #include @@ -213,7 +214,7 @@ static QVariantMap toMap(const QTorrentHandle& h) * - "eta": Torrent ETA * - "state": Torrent state */ -QByteArray btjson::getTorrents() +QByteArray btjson::getTorrents(QString filter, QString label) { QVariantList torrent_list; @@ -221,8 +222,12 @@ QByteArray btjson::getTorrents() std::vector::const_iterator it = torrents.begin(); std::vector::const_iterator end = torrents.end(); + QTorrentFilter torrentFilter(filter, label); for( ; it != end; ++it) { - torrent_list.append(toMap(QTorrentHandle(*it))); + QTorrentHandle torrent = QTorrentHandle(*it); + + if (torrentFilter.apply(torrent)) + torrent_list.append(toMap(torrent)); } return json::toJson(torrent_list); diff --git a/src/webui/btjson.h b/src/webui/btjson.h index 85be78bb4..2b328d5ff 100644 --- a/src/webui/btjson.h +++ b/src/webui/btjson.h @@ -34,6 +34,8 @@ #include #include +class QTorrentHandle; + class btjson { Q_DECLARE_TR_FUNCTIONS(misc) @@ -42,7 +44,7 @@ private: btjson() {} public: - static QByteArray getTorrents(); + static QByteArray getTorrents(QString filter = "all", QString label = QString()); static QByteArray getTrackersForTorrent(const QString& hash); static QByteArray getPropertiesForTorrent(const QString& hash); static QByteArray getFilesForTorrent(const QString& hash); diff --git a/src/webui/qtorrentfilter.cpp b/src/webui/qtorrentfilter.cpp new file mode 100644 index 000000000..0572bf66a --- /dev/null +++ b/src/webui/qtorrentfilter.cpp @@ -0,0 +1,118 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2014 Vladimir Golovnev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#include "torrentpersistentdata.h" +#include "qtorrentfilter.h" + +QTorrentFilter::QTorrentFilter(QString filter, QString label) + : type_(All) + , label_(label) +{ + if (filter == "downloading") + type_ = Downloading; + else if (filter == "completed") + type_ = Completed; + else if (filter == "paused") + type_ = Paused; + else if (filter == "active") + type_ = Active; + else if (filter == "inactive") + type_ = Inactive; +} + +bool QTorrentFilter::apply(const QTorrentHandle& h) const +{ + if (!torrentHasLabel(h)) + return false; + + switch (type_) { + case Downloading: + return isTorrentDownloading(h); + case Completed: + return isTorrentCompleted(h); + case Paused: + return isTorrentPaused(h); + case Active: + return isTorrentActive(h); + case Inactive: + return isTorrentInactive(h); + default: // All + return true; + } +} + +bool QTorrentFilter::isTorrentDownloading(const QTorrentHandle &h) const +{ + const QTorrentState state = h.torrentState(); + + return state == QTorrentState::Downloading + || state == QTorrentState::StalledDownloading + || state == QTorrentState::CheckingDownloading + || state == QTorrentState::PausedDownloading + || state == QTorrentState::QueuedDownloading; +} + +bool QTorrentFilter::isTorrentCompleted(const QTorrentHandle &h) const +{ + const QTorrentState state = h.torrentState(); + + return state == QTorrentState::Uploading + || state == QTorrentState::StalledUploading + || state == QTorrentState::CheckingUploading + || state == QTorrentState::PausedUploading + || state == QTorrentState::QueuedUploading; +} + +bool QTorrentFilter::isTorrentPaused(const QTorrentHandle &h) const +{ + const QTorrentState state = h.torrentState(); + + return state == QTorrentState::PausedDownloading + || state == QTorrentState::PausedUploading; +} + +bool QTorrentFilter::isTorrentActive(const QTorrentHandle &h) const +{ + const QTorrentState state = h.torrentState(); + + return state == QTorrentState::Downloading + || state == QTorrentState::Uploading; +} + +bool QTorrentFilter::isTorrentInactive(const QTorrentHandle &h) const +{ + return !isTorrentActive(h); +} + +bool QTorrentFilter::torrentHasLabel(const QTorrentHandle &h) const +{ + if (label_.isNull()) + return true; + else + return TorrentPersistentData::getLabel(h.hash()) == label_; +} diff --git a/src/webui/qtorrentfilter.h b/src/webui/qtorrentfilter.h new file mode 100644 index 000000000..052c9ac6a --- /dev/null +++ b/src/webui/qtorrentfilter.h @@ -0,0 +1,63 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2014 Vladimir Golovnev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#ifndef QTORRENTFILTER_H +#define QTORRENTFILTER_H + +#include "qtorrenthandle.h" + +class QTorrentFilter +{ +public: + enum + { + All, + Downloading, + Completed, + Paused, + Active, + Inactive + }; + + // label: pass empty string for "no label" or null string (QString()) for "any label" + QTorrentFilter(QString filter, QString label = QString()); + bool apply(const QTorrentHandle& h) const; + +private: + int type_; + QString label_; + + bool isTorrentDownloading(const QTorrentHandle &h) const; + bool isTorrentCompleted(const QTorrentHandle &h) const; + bool isTorrentPaused(const QTorrentHandle &h) const; + bool isTorrentActive(const QTorrentHandle &h) const; + bool isTorrentInactive(const QTorrentHandle &h) const; + bool torrentHasLabel(const QTorrentHandle &h) const; +}; + +#endif // QTORRENTFILTER_H diff --git a/src/webui/requesthandler.cpp b/src/webui/requesthandler.cpp index 0bcded468..c5104bad3 100644 --- a/src/webui/requesthandler.cpp +++ b/src/webui/requesthandler.cpp @@ -188,7 +188,8 @@ void RequestHandler::action_public_images() void RequestHandler::action_json_torrents() { - print(btjson::getTorrents(), CONTENT_TYPE_JS); + const QStringMap& gets = request().gets; + print(btjson::getTorrents(gets["filter"], gets["label"]), CONTENT_TYPE_JS); } void RequestHandler::action_json_preferences() diff --git a/src/webui/webui.pri b/src/webui/webui.pri index be4989a22..26c009443 100644 --- a/src/webui/webui.pri +++ b/src/webui/webui.pri @@ -11,7 +11,8 @@ HEADERS += $$PWD/httpserver.h \ $$PWD/extra_translations.h \ $$PWD/webapplication.h \ $$PWD/abstractrequesthandler.h \ - $$PWD/requesthandler.h + $$PWD/requesthandler.h \ + $$PWD/qtorrentfilter.h SOURCES += $$PWD/httpserver.cpp \ $$PWD/httpconnection.cpp \ @@ -21,7 +22,8 @@ SOURCES += $$PWD/httpserver.cpp \ $$PWD/prefjson.cpp \ $$PWD/webapplication.cpp \ $$PWD/abstractrequesthandler.cpp \ - $$PWD/requesthandler.cpp + $$PWD/requesthandler.cpp \ + $$PWD/qtorrentfilter.cpp # QJson JSON parser/serializer for using with Qt4 lessThan(QT_MAJOR_VERSION, 5) { From 1a1db877bfde76469a70214975cb866783167623 Mon Sep 17 00:00:00 2001 From: "Vladimir Golovnev (Glassez)" Date: Fri, 21 Nov 2014 17:13:39 +0300 Subject: [PATCH 04/10] Use QTorrentHandle::torrentState() in btjson. --- src/webui/btjson.cpp | 38 ++------------------------------------ 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/src/webui/btjson.cpp b/src/webui/btjson.cpp index 1452ef67b..ac713ce84 100644 --- a/src/webui/btjson.cpp +++ b/src/webui/btjson.cpp @@ -154,42 +154,8 @@ static QVariantMap toMap(const QTorrentHandle& h) ret[KEY_TORRENT_NUM_INCOMPLETE] = status.num_incomplete; const qreal ratio = QBtSession::instance()->getRealRatio(status); ret[KEY_TORRENT_RATIO] = (ratio > 100.) ? -1 : ratio; - qulonglong eta = 0; - QString state; - if (h.is_paused(status)) { - if (h.has_error(status)) - state = "error"; - else - state = h.is_seed(status) ? "pausedUP" : "pausedDL"; - } - else { - if (QBtSession::instance()->isQueueingEnabled() && h.is_queued(status)) { - state = h.is_seed(status) ? "queuedUP" : "queuedDL"; - } - else { - switch (status.state) { - case torrent_status::finished: - case torrent_status::seeding: - state = status.upload_payload_rate > 0 ? "uploading" : "stalledUP"; - break; - case torrent_status::allocating: - case torrent_status::checking_files: - case torrent_status::queued_for_checking: - case torrent_status::checking_resume_data: - state = h.is_seed(status) ? "checkingUP" : "checkingDL"; - break; - case torrent_status::downloading: - case torrent_status::downloading_metadata: - state = status.download_payload_rate > 0 ? "downloading" : "stalledDL"; - eta = QBtSession::instance()->getETA(h.hash(), status); - break; - default: - qWarning("Unrecognized torrent status, should not happen!!! status was %d", h.state()); - } - } - } - ret[KEY_TORRENT_ETA] = eta ? eta : MAX_ETA; - ret[KEY_TORRENT_STATE] = state; + ret[KEY_TORRENT_STATE] = h.torrentState().toString(); + ret[KEY_TORRENT_ETA] = h.eta(); return ret; } From 0488ddafa5559c0eb30507462a0be2accf26ee08 Mon Sep 17 00:00:00 2001 From: "Vladimir Golovnev (Glassez)" Date: Sat, 22 Nov 2014 09:35:56 +0300 Subject: [PATCH 05/10] WebUI: Fix indentation. Fix indentation in client.js and dynamicTable.js. --- src/webui/www/public/scripts/client.js | 473 ++++++------ src/webui/www/public/scripts/dynamicTable.js | 715 ++++++++++--------- 2 files changed, 605 insertions(+), 583 deletions(-) diff --git a/src/webui/www/public/scripts/client.js b/src/webui/www/public/scripts/client.js index b69f979e1..f4feac3bc 100644 --- a/src/webui/www/public/scripts/client.js +++ b/src/webui/www/public/scripts/client.js @@ -23,252 +23,269 @@ */ myTable = new dynamicTable(); -ajaxfn = function(){}; -setSortedColumn = function(index){ - myTable.setSortedColumn(index); +ajaxfn = function () {}; +setSortedColumn = function (index) { + myTable.setSortedColumn(index); }; -window.addEvent('load', function(){ +window.addEvent('load', function () { - var saveColumnSizes = function() { - var filters_width = $('Filters').getSize().x; - var properties_height = $('propertiesPanel').getSize().y; - localStorage.setItem('filters_width', filters_width); - localStorage.setItem('properties_height', properties_height); - } + var saveColumnSizes = function () { + var filters_width = $('Filters').getSize().x; + var properties_height = $('propertiesPanel').getSize().y; + localStorage.setItem('filters_width', filters_width); + localStorage.setItem('properties_height', properties_height); + } - /*MochaUI.Desktop = new MochaUI.Desktop(); - MochaUI.Desktop.desktop.setStyles({ - 'background': '#fff', - 'visibility': 'visible' - });*/ - MochaUI.Desktop.initialize(); + /*MochaUI.Desktop = new MochaUI.Desktop(); + MochaUI.Desktop.desktop.setStyles({ + 'background': '#fff', + 'visibility': 'visible' + });*/ + MochaUI.Desktop.initialize(); - var filt_w = localStorage.getItem('filters_width'); - if($defined(filt_w)) - filt_w = filt_w.toInt(); - else - filt_w = 120; - new MochaUI.Column({ - id: 'filtersColumn', - placement: 'left', - onResize: saveColumnSizes, - width: filt_w, - resizeLimit: [100, 300] - }); - new MochaUI.Column({ - id: 'mainColumn', - placement: 'main', - width: null, - resizeLimit: [100, 300] - }); - MochaUI.Desktop.setDesktopSize(); - new MochaUI.Panel({ - id: 'Filters', - title: 'Panel', - header: false, - padding: { top: 0, right: 0, bottom: 0, left: 0 }, - loadMethod: 'xhr', - contentURL: 'filters.html', - column: 'filtersColumn', - height: 300 - }); - initializeWindows(); - var r=0; - var waiting=false; - var waitingTrInfo = false; + var filt_w = localStorage.getItem('filters_width'); + if ($defined(filt_w)) + filt_w = filt_w.toInt(); + else + filt_w = 120; + new MochaUI.Column({ + id : 'filtersColumn', + placement : 'left', + onResize : saveColumnSizes, + width : filt_w, + resizeLimit : [100, 300] + }); + new MochaUI.Column({ + id : 'mainColumn', + placement : 'main', + width : null, + resizeLimit : [100, 300] + }); + MochaUI.Desktop.setDesktopSize(); + new MochaUI.Panel({ + id : 'Filters', + title : 'Panel', + header : false, + padding : { + top : 0, + right : 0, + bottom : 0, + left : 0 + }, + loadMethod : 'xhr', + contentURL : 'filters.html', + column : 'filtersColumn', + height : 300 + }); + initializeWindows(); + var r = 0; + var waiting = false; + var waitingTrInfo = false; - var stateToImg = function(state){ - if(state == "pausedUP" || state == "pausedDL") { - state = "paused"; - } else { - if(state == "queuedUP" || state == "queuedDL") { - state = "queued"; - } else { - if(state == "checkingUP" || state == "checkingDL") { - state = "checking"; + var stateToImg = function (state) { + if (state == "pausedUP" || state == "pausedDL") { + state = "paused"; + } else { + if (state == "queuedUP" || state == "queuedDL") { + state = "queued"; + } else { + if (state == "checkingUP" || state == "checkingDL") { + state = "checking"; + } + } } - } - } - return 'images/skin/'+state+'.png'; - }; - var loadTransferInfo = function() { - var url = 'json/transferInfo'; - if(!waitingTrInfo) { - waitingTrInfo = true; - var request = new Request.JSON({ - url: url, - noCache: true, - method: 'get', - onFailure: function() { - $('error_div').set('html', '_(qBittorrent client is not reachable)'); - waitingTrInfo=false; - loadTransferInfo.delay(4000); + return 'images/skin/' + state + '.png'; + }; + var loadTransferInfo = function () { + var url = 'json/transferInfo'; + if (!waitingTrInfo) { + waitingTrInfo = true; + var request = new Request.JSON({ + url : url, + noCache : true, + method : 'get', + onFailure : function () { + $('error_div').set('html', '_(qBittorrent client is not reachable)'); + waitingTrInfo = false; + loadTransferInfo.delay(4000); }, - onSuccess: function(info) { - if(info) { - $("DlInfos").set('html', "_(D: %1 - T: %2)".replace("%1", friendlyUnit(info.dl_info_speed, true)) - .replace("%2", friendlyUnit(info.dl_info_data, false))); - $("UpInfos").set('html', "_(U: %1 - T: %2)".replace("%1", friendlyUnit(info.up_info_speed, true)) - .replace("%2", friendlyUnit(info.up_info_data, false))); - if(localStorage.getItem('speed_in_browser_title_bar') == 'true') - document.title = "_(D:%1 U:%2)".replace("%1", friendlyUnit(info.dl_info_speed, true)).replace("%2", friendlyUnit(info.up_info_speed, true)); - else - document.title = "_(qBittorrent web User Interface)"; - waitingTrInfo=false; - loadTransferInfo.delay(3000); - } + onSuccess : function (info) { + if (info) { + $("DlInfos").set('html', "_(D: %1 - T: %2)" + .replace("%1", friendlyUnit(info.dl_info_speed, true)) + .replace("%2", friendlyUnit(info.dl_info_data, false))); + $("UpInfos").set('html', "_(U: %1 - T: %2)" + .replace("%1", friendlyUnit(info.up_info_speed, true)) + .replace("%2", friendlyUnit(info.up_info_data, false))); + if(localStorage.getItem('speed_in_browser_title_bar') == 'true') + document.title = "_(D:%1 U:%2)".replace("%1", friendlyUnit(info.dl_info_speed, true)).replace("%2", friendlyUnit(info.up_info_speed, true)); + else + document.title = "_(qBittorrent web User Interface)"; + waitingTrInfo = false; + loadTransferInfo.delay(3000); + } } - }).send(); - } - }; - $('DlInfos').addEvent('click', globalDownloadLimitFN); - $('UpInfos').addEvent('click', globalUploadLimitFN); + }).send(); + } + }; + $('DlInfos').addEvent('click', globalDownloadLimitFN); + $('UpInfos').addEvent('click', globalUploadLimitFN); - var ajaxfn = function(){ - var queueing_enabled = false; - var url = 'json/torrents'; - if (!waiting){ - waiting=true; - var request = new Request.JSON({ - url: url, - noCache: true, - method: 'get', - onFailure: function() { - $('error_div').set('html', '_(qBittorrent client is not reachable)'); - waiting=false; - ajaxfn.delay(2000); - }, - onSuccess: function(events) { - $('error_div').set('html', ''); - if(events){ - // Add new torrents or update them - torrent_hashes = myTable.getRowIds(); - events_hashes = new Array(); - events.each(function(event){ - events_hashes[events_hashes.length] = event.hash; - var row = new Array(); - var data = new Array(); - row.length = 10; - row[0] = stateToImg(event.state); - row[1] = event.name; - row[2] = event.priority > -1 ? event.priority : null; - data[2] = event.priority; - row[3] = friendlyUnit(event.size, false); - data[3] = event.size; - row[4] = (event.progress*100).round(1); - if(row[4] == 100.0 && event.progress != 1.0) - row[4] = 99.9; - data[4] = event.progress; - row[5] = event.num_seeds; - if (event.num_complete != -1) - row[5] += " (" + event.num_complete + ")"; - data[5] = event.num_seeds; - row[6] = event.num_leechs; - if (event.num_incomplete != -1) - row[6] += " (" + event.num_incomplete + ")"; - data[6] = event.num_leechs; - row[7] = friendlyUnit(event.dlspeed, true); - data[7] = event.dlspeed; - row[8] = friendlyUnit(event.upspeed, true); - data[8] = event.upspeed; - row[9] = friendlyDuration(event.eta); - data[9] = event.eta - if(event.ratio == -1) - row[10] = "∞"; - else - row[10] = (Math.floor(100 * event.ratio) / 100).toFixed(2); //Don't round up - data[10] = event.ratio; - if(row[2] != null) - queueing_enabled = true; - if(!torrent_hashes.contains(event.hash)) { - // New unfinished torrent - torrent_hashes[torrent_hashes.length] = event.hash; - //alert("Inserting row"); - myTable.insertRow(event.hash, row, data, event.state); - } else { - // Update torrent data - myTable.updateRow(event.hash, row, data, event.state); - } - }); - // Remove deleted torrents - torrent_hashes.each(function(hash){ - if(!events_hashes.contains(hash)) { - myTable.removeRow(hash); - } - }); - if(queueing_enabled) { - $('queueingButtons').removeClass('invisible'); - myTable.showPriority(); - } else { - $('queueingButtons').addClass('invisible'); - myTable.hidePriority(); - } - } - waiting=false; - ajaxfn.delay(1500); - } - }).send(); - } - }; - new MochaUI.Panel({ - id: 'transferList', - title: 'Panel', - header: false, - padding: { top: 0, right: 0, bottom: 0, left: 0 }, - loadMethod: 'xhr', - contentURL: 'transferlist.html', - onContentLoaded: function() { - ajaxfn(); - }, - column: 'mainColumn', - onResize: saveColumnSizes, - height: null - }); + var ajaxfn = function () { + var queueing_enabled = false; + var url = 'json/torrents'; + if (!waiting) { + waiting = true; + var request = new Request.JSON({ + url : url, + noCache : true, + method : 'get', + onFailure : function () { + $('error_div').set('html', '_(qBittorrent client is not reachable)'); + waiting = false; + ajaxfn.delay(2000); + }, + onSuccess : function (events) { + $('error_div').set('html', ''); + if (events) { + // Add new torrents or update them + torrent_hashes = myTable.getRowIds(); + events_hashes = new Array(); + events.each(function (event) { + events_hashes[events_hashes.length] = event.hash; + var row = new Array(); + var data = new Array(); + row.length = 10; + row[0] = stateToImg(event.state); + row[1] = event.name; + row[2] = event.priority > -1 ? event.priority : null; + data[2] = event.priority; + row[3] = friendlyUnit(event.size, false); + data[3] = event.size; + row[4] = (event.progress * 100).round(1); + if (row[4] == 100.0 && event.progress != 1.0) + row[4] = 99.9; + data[4] = event.progress; + row[5] = event.num_seeds; + if (event.num_complete != -1) + row[5] += " (" + event.num_complete + ")"; + data[5] = event.num_seeds; + row[6] = event.num_leechs; + if (event.num_incomplete != -1) + row[6] += " (" + event.num_incomplete + ")"; + data[6] = event.num_leechs; + row[7] = friendlyUnit(event.dlspeed, true); + data[7] = event.dlspeed; + row[8] = friendlyUnit(event.upspeed, true); + data[8] = event.upspeed; + row[9] = friendlyDuration(event.eta); + data[9] = event.eta; + if (event.ratio == -1) + row[10] = "∞"; + else + row[10] = (Math.floor(100 * event.ratio) / 100).toFixed(2); //Don't round up + data[10] = event.ratio; + if (row[2] != null) + queueing_enabled = true; + if (!torrent_hashes.contains(event.hash)) { + // New unfinished torrent + torrent_hashes[torrent_hashes.length] = event.hash; + //alert("Inserting row"); + myTable.insertRow(event.hash, row, data, event.state); + } else { + // Update torrent data + myTable.updateRow(event.hash, row, data, event.state); + } + }); + // Remove deleted torrents + torrent_hashes.each(function (hash) { + if (!events_hashes.contains(hash)) { + myTable.removeRow(hash); + } + }); + if (queueing_enabled) { + $('queueingButtons').removeClass('invisible'); + myTable.showPriority(); + } else { + $('queueingButtons').addClass('invisible'); + myTable.hidePriority(); + } + } + waiting = false; + ajaxfn.delay(1500); + } + }).send(); + } + }; + new MochaUI.Panel({ + id : 'transferList', + title : 'Panel', + header : false, + padding : { + top : 0, + right : 0, + bottom : 0, + left : 0 + }, + loadMethod : 'xhr', + contentURL : 'transferlist.html', + onContentLoaded : function () { + ajaxfn(); + }, + column : 'mainColumn', + onResize : saveColumnSizes, + height : null + }); var prop_h = localStorage.getItem('properties_height'); - if($defined(prop_h)) - prop_h = prop_h.toInt(); + if ($defined(prop_h)) + prop_h = prop_h.toInt(); else - prop_h = Window.getSize().y / 2.; + prop_h = Window.getSize().y / 2.; new MochaUI.Panel({ - id: 'propertiesPanel', - title: 'Panel', - header: true, - padding: { top: 0, right: 0, bottom: 0, left: 0 }, - contentURL: 'prop-general.html', - require: { - css: ['css/Tabs.css'] - }, - tabsURL: 'properties.html', - column: 'mainColumn', - height: prop_h - }); - //ajaxfn(); - loadTransferInfo(); + id : 'propertiesPanel', + title : 'Panel', + header : true, + padding : { + top : 0, + right : 0, + bottom : 0, + left : 0 + }, + contentURL : 'prop-general.html', + require : { + css : ['css/Tabs.css'] + }, + tabsURL : 'properties.html', + column : 'mainColumn', + height : prop_h + }); + //ajaxfn(); + loadTransferInfo(); - setFilter = function(f) { - // Visually Select the right filter - $("all_filter").removeClass("selectedFilter"); - $("downloading_filter").removeClass("selectedFilter"); - $("completed_filter").removeClass("selectedFilter"); - $("paused_filter").removeClass("selectedFilter"); - $("active_filter").removeClass("selectedFilter"); - $("inactive_filter").removeClass("selectedFilter"); - $(f+"_filter").addClass("selectedFilter"); - myTable.setFilter(f); - ajaxfn(); - localStorage.setItem('selected_filter', f); - } + setFilter = function (f) { + // Visually Select the right filter + $("all_filter").removeClass("selectedFilter"); + $("downloading_filter").removeClass("selectedFilter"); + $("completed_filter").removeClass("selectedFilter"); + $("paused_filter").removeClass("selectedFilter"); + $("active_filter").removeClass("selectedFilter"); + $("inactive_filter").removeClass("selectedFilter"); + $(f + "_filter").addClass("selectedFilter"); + myTable.setFilter(f); + ajaxfn(); + localStorage.setItem('selected_filter', f); + } }); function closeWindows() { - MochaUI.closeAll(); + MochaUI.closeAll(); } -window.addEvent('keydown', function(event){ - if (event.key == 'a' && event.control) { - event.stop(); - myTable.selectAll(); - } +window.addEvent('keydown', function (event) { + if (event.key == 'a' && event.control) { + event.stop(); + myTable.selectAll(); + } }); diff --git a/src/webui/www/public/scripts/dynamicTable.js b/src/webui/www/public/scripts/dynamicTable.js index 7792c9958..32d9a2027 100644 --- a/src/webui/www/public/scripts/dynamicTable.js +++ b/src/webui/www/public/scripts/dynamicTable.js @@ -8,10 +8,10 @@ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * + * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -23,372 +23,377 @@ /************************************************************** - Script : Dynamic Table - Version : 0.5 - Authors : Ishan Arora & Christophe Dumez - Desc : Programable sortable table - Licence : Open Source MIT Licence + Script : Dynamic Table + Version : 0.5 + Authors : Ishan Arora & Christophe Dumez + Desc : Programable sortable table + Licence : Open Source MIT Licence -**************************************************************/ + **************************************************************/ -var dynamicTable = new Class ({ +var dynamicTable = new Class({ - initialize: function(){ - }, - - setup: function(table, progressIndex, context_menu){ - this.table = $(table); - this.rows = new Hash(); - this.cur = new Array(); - this.priority_hidden = false; - this.progressIndex = progressIndex; - this.filter = localStorage.getItem('selected_filter'); - if(!$defined(this.filter)) { - this.filter = 'all'; - } - this.context_menu = context_menu; - this.table.sortedIndex = 1; // Default is NAME - this.table.reverseSort = false; - }, - - sortfunction: function(tr1, tr2) { - var i = tr2.getParent().sortedIndex; - var reverseSort = tr2.getParent().reverseSort; - switch(i) { - case 1: // Name - if(!reverseSort) - return tr1.getElements('td')[i].get('html').localeCompare(tr2.getElements('td')[i].get('html')); - else - return tr2.getElements('td')[i].get('html').localeCompare(tr1.getElements('td')[i].get('html')); - case 2: // Prio - case 3: // Size - case 4: // Progress - case 5: // Seeds - case 6: // Peers - case 7: // Up Speed - case 8: // Down Speed - case 9: // ETA - default: // Ratio - if(!reverseSort) - return (tr1.getElements('td')[i].get('data-raw') - tr2.getElements('td')[i].get('data-raw')); - else - return (tr2.getElements('td')[i].get('data-raw') - tr1.getElements('td')[i].get('data-raw')); - } - }, - - updateSort: function() { - var trs = this.table.getChildren('tr'); - trs.sort(this.sortfunction); - this.table.adopt(trs); - }, - - setSortedColumn: function(index) { - if(index != this.table.sortedIndex) { - this.table.sortedIndex = index; - this.table.reverseSort = false; - } else { - // Toggle sort order - this.table.reverseSort = !this.table.reverseSort; - } - this.updateSort(); - this.altRow(); - }, - - getCurrentTorrentHash: function() { - if(this.cur.length > 0) - return this.cur[0]; - return ''; - }, + initialize : function () {}, - altRow: function() - { - var trs = this.table.getElements('tr'); - trs.each(function(el,i){ - if(i % 2){ - el.addClass('alt'); - }else{ - el.removeClass('alt'); - } - }.bind(this)); - }, + setup : function (table, progressIndex, context_menu) { + this.table = $(table); + this.rows = new Hash(); + this.cur = new Array(); + this.priority_hidden = false; + this.progressIndex = progressIndex; + this.filter = localStorage.getItem('selected_filter'); + if (!$defined(this.filter)) { + this.filter = 'all'; + } + this.context_menu = context_menu; + this.table.sortedIndex = 1; // Default is NAME + this.table.reverseSort = false; + }, - hidePriority: function(){ - if(this.priority_hidden) return; - $('prioHeader').addClass('invisible'); - var trs = this.table.getElements('tr'); - trs.each(function(tr,i){ - var tds = tr.getElements('td'); - tds[2].addClass('invisible'); - }.bind(this)); - this.priority_hidden = true; - }, - - setFilter: function(f) { - this.filter = f; - }, + sortfunction : function (tr1, tr2) { + var i = tr2.getParent().sortedIndex; + var reverseSort = tr2.getParent().reverseSort; + switch (i) { + case 1: // Name + if (!reverseSort) + return tr1.getElements('td')[i].get('html').localeCompare(tr2.getElements('td')[i].get('html')); + else + return tr2.getElements('td')[i].get('html').localeCompare(tr1.getElements('td')[i].get('html')); + case 2: // Prio + case 3: // Size + case 4: // Progress + case 5: // Seeds + case 6: // Peers + case 7: // Up Speed + case 8: // Down Speed + case 9: // ETA + default: // Ratio + if (!reverseSort) + return (tr1.getElements('td')[i].get('data-raw') - tr2.getElements('td')[i].get('data-raw')); + else + return (tr2.getElements('td')[i].get('data-raw') - tr1.getElements('td')[i].get('data-raw')); + } + }, + + updateSort : function () { + var trs = this.table.getChildren('tr'); + trs.sort(this.sortfunction); + this.table.adopt(trs); + }, + + setSortedColumn : function (index) { + if (index != this.table.sortedIndex) { + this.table.sortedIndex = index; + this.table.reverseSort = false; + } else { + // Toggle sort order + this.table.reverseSort = !this.table.reverseSort; + } + this.updateSort(); + this.altRow(); + }, + + getCurrentTorrentHash : function () { + if (this.cur.length > 0) + return this.cur[0]; + return ''; + }, + + altRow : function () { + var trs = this.table.getElements('tr'); + trs.each(function (el, i) { + if (i % 2) { + el.addClass('alt'); + } else { + el.removeClass('alt'); + } + }.bind(this)); + }, + + hidePriority : function () { + if (this.priority_hidden) + return; + $('prioHeader').addClass('invisible'); + var trs = this.table.getElements('tr'); + trs.each(function (tr, i) { + var tds = tr.getElements('td'); + tds[2].addClass('invisible'); + }.bind(this)); + this.priority_hidden = true; + }, - showPriority: function(){ - if(!this.priority_hidden) return; - $('prioHeader').removeClass('invisible'); - var trs = this.table.getElements('tr'); - trs.each(function(tr,i){ - var tds = tr.getElements('td'); - tds[2].removeClass('invisible'); + setFilter : function (f) { + this.filter = f; + }, + + showPriority : function () { + if (!this.priority_hidden) + return; + $('prioHeader').removeClass('invisible'); + var trs = this.table.getElements('tr'); + trs.each(function (tr, i) { + var tds = tr.getElements('td'); + tds[2].removeClass('invisible'); + }.bind(this)); + this.priority_hidden = false; + }, + + applyFilterOnRow : function (tr, status) { + switch (this.filter) { + case 'all': + tr.removeClass("invisible"); + break; + case 'downloading': + if (status == "downloading" || status == "stalledDL" || status == "checkingDL" || status == "pausedDL" || status == "queuedDL") { + tr.removeClass("invisible"); + } else { + tr.addClass("invisible"); + } + break; + case 'completed': + if (status == "uploading" || status == "stalledUP" || status == "checkingUP" || status == "pausedUP" || status == "queuedUP") { + tr.removeClass("invisible"); + } else { + tr.addClass("invisible"); + } + break; + case 'paused': + if (status == "pausedDL" || status == "pausedUP") { + tr.removeClass("invisible"); + } else { + tr.addClass("invisible"); + } + break; + case 'active': + if (status == "downloading" || status == "uploading") { + tr.removeClass("invisible"); + } else { + tr.addClass("invisible"); + } + break; + case 'inactive': + if (status != "downloading" && status != "uploading") { + tr.removeClass("invisible"); + } else { + tr.addClass("invisible"); + } + } + return !tr.hasClass('invisible'); + }, + + insertRow : function (id, row, data, status) { + if (this.rows.has(id)) { + return; + } + var tr = new Element('tr'); + tr.addClass("menu-target"); + this.rows.set(id, tr); + for (var i = 0; i < row.length; i++) { + var td = new Element('td'); + if (i == this.progressIndex) { + td.adopt(new ProgressBar(row[i].toFloat(), { + 'id' : 'pb_' + id, + 'width' : 80 + })); + if (typeof data[i] != 'undefined') + td.set('data-raw', data[i]) + } else { + if (i == 0) { + td.adopt(new Element('img', { + 'src' : row[i], + 'class' : 'statusIcon' + })); + } else { + if (i == 2) { + // Priority + if (this.priority_hidden) + td.addClass('invisible'); + } + td.set('html', row[i]); + if (typeof data[i] != 'undefined') + td.set('data-raw', data[i]) + } + } + td.injectInside(tr); + }; + + tr.addEvent('mouseover', function (e) { + tr.addClass('over'); + }.bind(this)); + tr.addEvent('mouseout', function (e) { + tr.removeClass('over'); + }.bind(this)); + tr.addEvent('contextmenu', function (e) { + if (!this.cur.contains(id)) { + // Remove selected style from previous ones + for (i = 0; i < this.cur.length; i++) { + if (this.rows.has(this.cur[i])) { + var temptr = this.rows.get(this.cur[i]); + temptr.removeClass('selected'); + } + } + this.cur.empty(); + this.cur[this.cur.length] = id; + temptr = this.rows.get(id); + temptr.addClass("selected"); + } + return true; + }.bind(this)); + tr.addEvent('click', function (e) { + e.stop(); + if (e.control) { + // CTRL key was pressed + if (this.cur.contains(id)) { + // remove it + this.cur.erase(id); + // Remove selected style + if (this.rows.has(id)) { + temptr = this.rows.get(id); + temptr.removeClass('selected'); + } + } else { + this.cur[this.cur.length] = id; + // Add selected style + if (this.rows.has(id)) { + temptr = this.rows.get(id); + temptr.addClass('selected'); + } + } + } else { + if (e.shift && this.cur.length == 1) { + // Shift key was pressed + var first_id = this.cur[0]; + var first_tr = this.rows.get(first_id); + var last_id = id; + var last_tr = this.rows.get(last_id); + var all_trs = this.table.getChildren('tr'); + var index_first_tr = all_trs.indexOf(first_tr); + var index_last_tr = all_trs.indexOf(last_tr); + var trs_to_select = all_trs.filter(function (item, index) { + if (index_first_tr < index_last_tr) + return (index > index_first_tr) && (index <= index_last_tr); + else + return (index < index_first_tr) && (index >= index_last_tr); + }); + trs_to_select.each(function (item, index) { + // Add to selection + this.cur[this.cur.length] = this.getRowId(item); + // Select it visually + item.addClass('selected'); }.bind(this)); - this.priority_hidden = false; + } else { + // Simple selection + // Remove selected style from previous ones + for (i = 0; i < this.cur.length; i++) { + if (this.rows.has(this.cur[i])) { + var temptr = this.rows.get(this.cur[i]); + temptr.removeClass('selected'); + } + } + this.cur.empty(); + // Add selected style to new one + if (this.rows.has(id)) { + temptr = this.rows.get(id); + temptr.addClass('selected'); + } + this.cur[0] = id; + // TODO: Warn Properties panel + } + } + return false; + }.bind(this)); + // Apply filter + this.applyFilterOnRow(tr, status); + // Insert + var trs = this.table.getChildren('tr'); + var i = 0; + while (i < trs.length && this.sortfunction(tr, trs[i]) > 0) { + i++; + } + if (i == trs.length) { + tr.inject(this.table); + } else { + tr.inject(trs[i], 'before'); + } + //tr.injectInside(this.table); + this.altRow(); + // Update context menu + this.context_menu.addTarget(tr); }, - - applyFilterOnRow: function(tr, status) { - switch(this.filter) { - case 'all': - tr.removeClass("invisible"); - break; - case 'downloading': - if(status == "downloading" || status == "stalledDL" || status == "checkingDL" || status == "pausedDL" || status == "queuedDL") { - tr.removeClass("invisible"); - } else { - tr.addClass("invisible"); - } - break; - case 'completed': - if(status == "uploading" || status == "stalledUP" || status == "checkingUP" || status == "pausedUP" || status == "queuedUP") { - tr.removeClass("invisible"); - } else { - tr.addClass("invisible"); - } - break; - case 'paused': - if(status == "pausedDL" || status == "pausedUP") { - tr.removeClass("invisible"); - } else { - tr.addClass("invisible"); - } - break; - case 'active': - if(status == "downloading" || status == "uploading") { - tr.removeClass("invisible"); - } else { - tr.addClass("invisible"); - } - break; - case 'inactive': - if(status != "downloading" && status != "uploading") { - tr.removeClass("invisible"); - } else { - tr.addClass("invisible"); - } - } - return !tr.hasClass('invisible'); - }, - insertRow: function(id, row, data, status){ - if(this.rows.has(id)) { - return; - } - var tr = new Element('tr'); - tr.addClass("menu-target"); - this.rows.set(id, tr); - for(var i=0; i index_first_tr) && (index <= index_last_tr); - else - return (index < index_first_tr) && (index >= index_last_tr); - }); - trs_to_select.each(function(item, index){ - // Add to selection - this.cur[this.cur.length] = this.getRowId(item); - // Select it visually - item.addClass('selected'); - }.bind(this)); - } else { - // Simple selection - // Remove selected style from previous ones - for(i=0; i 0) { - i++; - } - if(i==trs.length) { - tr.inject(this.table); - } else { - tr.inject(trs[i], 'before'); - } - //tr.injectInside(this.table); - this.altRow(); - // Update context menu - this.context_menu.addTarget(tr); - }, - - selectAll: function() { - this.cur.empty(); - this.rows.each(function(tr, id){ - this.cur[this.cur.length] = id; - if(!tr.hasClass('selected')) { - tr.addClass('selected'); - } - }, this); - }, + removeRow : function (id) { + if (this.cur.contains(id)) { + this.cur.erase(id); + } + if (this.rows.has(id)) { + var tr = this.rows.get(id); + tr.dispose(); + this.altRow(); + this.rows.erase(id); + return true; + } + return false; + }, - updateRow: function(id, row, data, status){ - if(!this.rows.has(id)) { - return false; - } - var tr = this.rows.get(id); - // Apply filter - if(this.applyFilterOnRow(tr, status)) { - var tds = tr.getElements('td'); - for(var i=0; i Date: Sat, 22 Nov 2014 10:23:51 +0300 Subject: [PATCH 06/10] WebUI: Drop client-side filtering. --- src/webui/www/public/filters.html | 6 +- src/webui/www/public/scripts/client.js | 8 +- src/webui/www/public/scripts/dynamicTable.js | 92 +++----------------- 3 files changed, 22 insertions(+), 84 deletions(-) diff --git a/src/webui/www/public/filters.html b/src/webui/www/public/filters.html index e0791fe83..c18cae28a 100644 --- a/src/webui/www/public/filters.html +++ b/src/webui/www/public/filters.html @@ -9,9 +9,9 @@ diff --git a/src/webui/www/public/scripts/client.js b/src/webui/www/public/scripts/client.js index f4feac3bc..065b562e5 100644 --- a/src/webui/www/public/scripts/client.js +++ b/src/webui/www/public/scripts/client.js @@ -134,7 +134,8 @@ window.addEvent('load', function () { var ajaxfn = function () { var queueing_enabled = false; - var url = 'json/torrents'; + var url = new URI('json/torrents'); + url.setData({'filter': filter}); if (!waiting) { waiting = true; var request = new Request.JSON({ @@ -272,9 +273,10 @@ window.addEvent('load', function () { $("active_filter").removeClass("selectedFilter"); $("inactive_filter").removeClass("selectedFilter"); $(f + "_filter").addClass("selectedFilter"); - myTable.setFilter(f); - ajaxfn(); + filter = f; localStorage.setItem('selected_filter', f); + // Reload torrents + ajaxfn(); } }); diff --git a/src/webui/www/public/scripts/dynamicTable.js b/src/webui/www/public/scripts/dynamicTable.js index 32d9a2027..caa06f433 100644 --- a/src/webui/www/public/scripts/dynamicTable.js +++ b/src/webui/www/public/scripts/dynamicTable.js @@ -41,10 +41,6 @@ var dynamicTable = new Class({ this.cur = new Array(); this.priority_hidden = false; this.progressIndex = progressIndex; - this.filter = localStorage.getItem('selected_filter'); - if (!$defined(this.filter)) { - this.filter = 'all'; - } this.context_menu = context_menu; this.table.sortedIndex = 1; // Default is NAME this.table.reverseSort = false; @@ -122,10 +118,6 @@ var dynamicTable = new Class({ this.priority_hidden = true; }, - setFilter : function (f) { - this.filter = f; - }, - showPriority : function () { if (!this.priority_hidden) return; @@ -138,49 +130,6 @@ var dynamicTable = new Class({ this.priority_hidden = false; }, - applyFilterOnRow : function (tr, status) { - switch (this.filter) { - case 'all': - tr.removeClass("invisible"); - break; - case 'downloading': - if (status == "downloading" || status == "stalledDL" || status == "checkingDL" || status == "pausedDL" || status == "queuedDL") { - tr.removeClass("invisible"); - } else { - tr.addClass("invisible"); - } - break; - case 'completed': - if (status == "uploading" || status == "stalledUP" || status == "checkingUP" || status == "pausedUP" || status == "queuedUP") { - tr.removeClass("invisible"); - } else { - tr.addClass("invisible"); - } - break; - case 'paused': - if (status == "pausedDL" || status == "pausedUP") { - tr.removeClass("invisible"); - } else { - tr.addClass("invisible"); - } - break; - case 'active': - if (status == "downloading" || status == "uploading") { - tr.removeClass("invisible"); - } else { - tr.addClass("invisible"); - } - break; - case 'inactive': - if (status != "downloading" && status != "uploading") { - tr.removeClass("invisible"); - } else { - tr.addClass("invisible"); - } - } - return !tr.hasClass('invisible'); - }, - insertRow : function (id, row, data, status) { if (this.rows.has(id)) { return; @@ -302,8 +251,7 @@ var dynamicTable = new Class({ } return false; }.bind(this)); - // Apply filter - this.applyFilterOnRow(tr, status); + // Insert var trs = this.table.getChildren('tr'); var i = 0; @@ -336,34 +284,22 @@ var dynamicTable = new Class({ return false; } var tr = this.rows.get(id); - // Apply filter - if (this.applyFilterOnRow(tr, status)) { - var tds = tr.getElements('td'); - for (var i = 0; i < row.length; i++) { - if (i == 1) - continue; // Do not refresh name - if (i == this.progressIndex) { - $('pb_' + id).setValue(row[i]); + var tds = tr.getElements('td'); + for (var i = 0; i < row.length; i++) { + if (i == 1) + continue; // Do not refresh name + if (i == this.progressIndex) { + $('pb_' + id).setValue(row[i]); + } else { + if (i == 0) { + tds[i].getChildren('img')[0].set('src', row[i]); } else { - if (i == 0) { - tds[i].getChildren('img')[0].set('src', row[i]); - } else { - tds[i].set('html', row[i]); - } + tds[i].set('html', row[i]); } - if (typeof data[i] != 'undefined') - tds[i].set('data-raw', data[i]) - }; - } else { - // Row was hidden, check if it was selected - // and unselect it if it was - if (this.cur.contains(id)) { - // Remove from selection - this.cur.erase(id); - // Remove selected style - tr.removeClass('selected'); } - } + if (typeof data[i] != 'undefined') + tds[i].set('data-raw', data[i]) + }; return true; }, From 8ff03d162d2f2e0ab395ce5a534c3a490864f1d6 Mon Sep 17 00:00:00 2001 From: "Vladimir Golovnev (Glassez)" Date: Sat, 22 Nov 2014 14:51:14 +0300 Subject: [PATCH 07/10] WebUI: Fix transferlist.html indentation. --- src/webui/www/public/transferlist.html | 90 +++++++++++++------------- 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/src/webui/www/public/transferlist.html b/src/webui/www/public/transferlist.html index 8df936728..fff6a57fc 100644 --- a/src/webui/www/public/transferlist.html +++ b/src/webui/www/public/transferlist.html @@ -20,48 +20,50 @@ From 59ff08c107c05cff9339acb63f059be0644070b5 Mon Sep 17 00:00:00 2001 From: "Vladimir Golovnev (Glassez)" Date: Mon, 24 Nov 2014 21:37:16 +0300 Subject: [PATCH 08/10] WebUI: Implement server-side sorting. --- src/webui/btjson.cpp | 58 +++++++++++++++++++++++++++++++++++- src/webui/btjson.h | 3 +- src/webui/requesthandler.cpp | 5 +++- 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/src/webui/btjson.cpp b/src/webui/btjson.cpp index ac713ce84..332ed556b 100644 --- a/src/webui/btjson.cpp +++ b/src/webui/btjson.cpp @@ -37,6 +37,9 @@ #include #include +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) +#include +#endif #if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0) #include #endif @@ -133,6 +136,58 @@ static const char KEY_TRANSFER_DLDATA[] = "dl_info_data"; static const char KEY_TRANSFER_UPSPEED[] = "up_info_speed"; static const char KEY_TRANSFER_UPDATA[] = "up_info_data"; +class QTorrentCompare +{ +public: + QTorrentCompare(QString key, bool greaterThan = false) + : key_(key) + , greaterThan_(greaterThan) +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) + , type_(QVariant::Invalid) +#endif + { + } + + bool operator()(QVariant torrent1, QVariant torrent2) + { +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) + if (type_ == QVariant::Invalid) + type_ = torrent1.toMap().value(key_).type(); + + switch (type_) { + case QVariant::Int: + return greaterThan_ ? torrent1.toMap().value(key_).toInt() > torrent2.toMap().value(key_).toInt() + : torrent1.toMap().value(key_).toInt() < torrent2.toMap().value(key_).toInt(); + case QVariant::LongLong: + return greaterThan_ ? torrent1.toMap().value(key_).toLongLong() > torrent2.toMap().value(key_).toLongLong() + : torrent1.toMap().value(key_).toLongLong() < torrent2.toMap().value(key_).toLongLong(); + case QVariant::ULongLong: + return greaterThan_ ? torrent1.toMap().value(key_).toULongLong() > torrent2.toMap().value(key_).toULongLong() + : torrent1.toMap().value(key_).toULongLong() < torrent2.toMap().value(key_).toULongLong(); + case QMetaType::Float: + return greaterThan_ ? torrent1.toMap().value(key_).toFloat() > torrent2.toMap().value(key_).toFloat() + : torrent1.toMap().value(key_).toFloat() < torrent2.toMap().value(key_).toFloat(); + case QVariant::Double: + return greaterThan_ ? torrent1.toMap().value(key_).toDouble() > torrent2.toMap().value(key_).toDouble() + : torrent1.toMap().value(key_).toDouble() < torrent2.toMap().value(key_).toDouble(); + default: + return greaterThan_ ? torrent1.toMap().value(key_).toString() > torrent2.toMap().value(key_).toString() + : torrent1.toMap().value(key_).toString() < torrent2.toMap().value(key_).toString(); + } +#else + return greaterThan_ ? torrent1.toMap().value(key_) > torrent2.toMap().value(key_) + : torrent1.toMap().value(key_) < torrent2.toMap().value(key_); +#endif + } + +private: + QString key_; + bool greaterThan_; +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) + QVariant::Type type_; +#endif +}; + static QVariantMap toMap(const QTorrentHandle& h) { libtorrent::torrent_status status = h.status(torrent_handle::query_accurate_download_counters); @@ -180,7 +235,7 @@ static QVariantMap toMap(const QTorrentHandle& h) * - "eta": Torrent ETA * - "state": Torrent state */ -QByteArray btjson::getTorrents(QString filter, QString label) +QByteArray btjson::getTorrents(QString filter, QString label, QString sortedColumn, bool reverse) { QVariantList torrent_list; @@ -196,6 +251,7 @@ QByteArray btjson::getTorrents(QString filter, QString label) torrent_list.append(toMap(torrent)); } + std::sort(torrent_list.begin(), torrent_list.end(), QTorrentCompare(sortedColumn, reverse)); return json::toJson(torrent_list); } diff --git a/src/webui/btjson.h b/src/webui/btjson.h index 2b328d5ff..0d1a63c56 100644 --- a/src/webui/btjson.h +++ b/src/webui/btjson.h @@ -44,7 +44,8 @@ private: btjson() {} public: - static QByteArray getTorrents(QString filter = "all", QString label = QString()); + static QByteArray getTorrents(QString filter = "all", QString label = QString(), + QString sortedColumn = "name", bool reverse = false); static QByteArray getTrackersForTorrent(const QString& hash); static QByteArray getPropertiesForTorrent(const QString& hash); static QByteArray getFilesForTorrent(const QString& hash); diff --git a/src/webui/requesthandler.cpp b/src/webui/requesthandler.cpp index c5104bad3..844fab554 100644 --- a/src/webui/requesthandler.cpp +++ b/src/webui/requesthandler.cpp @@ -189,7 +189,10 @@ void RequestHandler::action_public_images() void RequestHandler::action_json_torrents() { const QStringMap& gets = request().gets; - print(btjson::getTorrents(gets["filter"], gets["label"]), CONTENT_TYPE_JS); + + print(btjson::getTorrents( + gets["filter"], gets["label"], gets["sort"], gets["reverse"] == "true" + ), CONTENT_TYPE_JS); } void RequestHandler::action_json_preferences() From e279dcf904507c90625d021da0ef6126577a4585 Mon Sep 17 00:00:00 2001 From: "Vladimir Golovnev (Glassez)" Date: Tue, 25 Nov 2014 09:27:22 +0300 Subject: [PATCH 09/10] WebUI: Implement limit/offset. --- src/webui/btjson.cpp | 18 ++++++++++++++++-- src/webui/btjson.h | 2 +- src/webui/requesthandler.cpp | 10 +++++++++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/webui/btjson.cpp b/src/webui/btjson.cpp index 332ed556b..4ea1483de 100644 --- a/src/webui/btjson.cpp +++ b/src/webui/btjson.cpp @@ -235,7 +235,8 @@ static QVariantMap toMap(const QTorrentHandle& h) * - "eta": Torrent ETA * - "state": Torrent state */ -QByteArray btjson::getTorrents(QString filter, QString label, QString sortedColumn, bool reverse) +QByteArray btjson::getTorrents(QString filter, QString label, + QString sortedColumn, bool reverse, int limit, int offset) { QVariantList torrent_list; @@ -252,7 +253,20 @@ QByteArray btjson::getTorrents(QString filter, QString label, QString sortedColu } std::sort(torrent_list.begin(), torrent_list.end(), QTorrentCompare(sortedColumn, reverse)); - return json::toJson(torrent_list); + int size = torrent_list.size(); + // normalize offset + if (offset < 0) + offset = size - offset; + if ((offset >= size) || (offset < 0)) + offset = 0; + // normalize limit + if (limit <= 0) + limit = -1; // unlimited + + if ((limit > 0) || (offset > 0)) + return json::toJson(torrent_list.mid(offset, limit)); + else + return json::toJson(torrent_list); } /** diff --git a/src/webui/btjson.h b/src/webui/btjson.h index 0d1a63c56..e8bf66896 100644 --- a/src/webui/btjson.h +++ b/src/webui/btjson.h @@ -45,7 +45,7 @@ private: public: static QByteArray getTorrents(QString filter = "all", QString label = QString(), - QString sortedColumn = "name", bool reverse = false); + QString sortedColumn = "name", bool reverse = false, int limit = 0, int offset = 0); static QByteArray getTrackersForTorrent(const QString& hash); static QByteArray getPropertiesForTorrent(const QString& hash); static QByteArray getFilesForTorrent(const QString& hash); diff --git a/src/webui/requesthandler.cpp b/src/webui/requesthandler.cpp index 844fab554..092bd11e3 100644 --- a/src/webui/requesthandler.cpp +++ b/src/webui/requesthandler.cpp @@ -186,12 +186,20 @@ void RequestHandler::action_public_images() printFile(path); } +// GET params: +// - filter (string): all, downloading, completed, paused, active, inactive +// - label (string): torrent label for filtering by it (empty string means "unlabeled"; no "label" param presented means "any label") +// - sort (string): name of column for sorting by its value +// - reverse (bool): enable reverse sorting +// - limit (int): set limit number of torrents returned (if greater than 0, otherwise - unlimited) +// - offset (int): set offset (if less than 0 - offset from end) void RequestHandler::action_json_torrents() { const QStringMap& gets = request().gets; print(btjson::getTorrents( - gets["filter"], gets["label"], gets["sort"], gets["reverse"] == "true" + gets["filter"], gets["label"], gets["sort"], gets["reverse"] == "true", + gets["limit"].toInt(), gets["offset"].toInt() ), CONTENT_TYPE_JS); } From 0e87b96beb4fb951a67440c494c15336f6f2c6a5 Mon Sep 17 00:00:00 2001 From: "Vladimir Golovnev (Glassez)" Date: Wed, 26 Nov 2014 12:45:09 +0300 Subject: [PATCH 10/10] WebUI: Drop client-side sorting. --- src/webui/www/public/scripts/client.js | 23 +++++-- src/webui/www/public/scripts/dynamicTable.js | 67 ++++++-------------- src/webui/www/public/transferlist.html | 20 +++--- 3 files changed, 48 insertions(+), 62 deletions(-) diff --git a/src/webui/www/public/scripts/client.js b/src/webui/www/public/scripts/client.js index 065b562e5..cdacceba6 100644 --- a/src/webui/www/public/scripts/client.js +++ b/src/webui/www/public/scripts/client.js @@ -24,9 +24,6 @@ myTable = new dynamicTable(); ajaxfn = function () {}; -setSortedColumn = function (index) { - myTable.setSortedColumn(index); -}; window.addEvent('load', function () { @@ -135,7 +132,9 @@ window.addEvent('load', function () { var ajaxfn = function () { var queueing_enabled = false; var url = new URI('json/torrents'); - url.setData({'filter': filter}); + url.setData('filter', filter); + url.setData('sort', myTable.table.sortedColumn); + url.setData('reverse', myTable.table.reverseSort); if (!waiting) { waiting = true; var request = new Request.JSON({ @@ -153,6 +152,7 @@ window.addEvent('load', function () { // Add new torrents or update them torrent_hashes = myTable.getRowIds(); events_hashes = new Array(); + pos = 0; events.each(function (event) { events_hashes[events_hashes.length] = event.hash; var row = new Array(); @@ -193,11 +193,13 @@ window.addEvent('load', function () { // New unfinished torrent torrent_hashes[torrent_hashes.length] = event.hash; //alert("Inserting row"); - myTable.insertRow(event.hash, row, data, event.state); + myTable.insertRow(event.hash, row, data, event.state, pos); } else { // Update torrent data - myTable.updateRow(event.hash, row, data, event.state); + myTable.updateRow(event.hash, row, data, event.state, pos); } + + pos++; }); // Remove deleted torrents torrent_hashes.each(function (hash) { @@ -212,6 +214,8 @@ window.addEvent('load', function () { $('queueingButtons').addClass('invisible'); myTable.hidePriority(); } + + myTable.altRow(); } waiting = false; ajaxfn.delay(1500); @@ -219,6 +223,13 @@ window.addEvent('load', function () { }).send(); } }; + + setSortedColumn = function (column) { + myTable.setSortedColumn(column); + // reload torrents + ajaxfn(); + }; + new MochaUI.Panel({ id : 'transferList', title : 'Panel', diff --git a/src/webui/www/public/scripts/dynamicTable.js b/src/webui/www/public/scripts/dynamicTable.js index caa06f433..4ae397fd2 100644 --- a/src/webui/www/public/scripts/dynamicTable.js +++ b/src/webui/www/public/scripts/dynamicTable.js @@ -42,51 +42,18 @@ var dynamicTable = new Class({ this.priority_hidden = false; this.progressIndex = progressIndex; this.context_menu = context_menu; - this.table.sortedIndex = 1; // Default is NAME + this.table.sortedColumn = 'name'; // Default is NAME this.table.reverseSort = false; }, - sortfunction : function (tr1, tr2) { - var i = tr2.getParent().sortedIndex; - var reverseSort = tr2.getParent().reverseSort; - switch (i) { - case 1: // Name - if (!reverseSort) - return tr1.getElements('td')[i].get('html').localeCompare(tr2.getElements('td')[i].get('html')); - else - return tr2.getElements('td')[i].get('html').localeCompare(tr1.getElements('td')[i].get('html')); - case 2: // Prio - case 3: // Size - case 4: // Progress - case 5: // Seeds - case 6: // Peers - case 7: // Up Speed - case 8: // Down Speed - case 9: // ETA - default: // Ratio - if (!reverseSort) - return (tr1.getElements('td')[i].get('data-raw') - tr2.getElements('td')[i].get('data-raw')); - else - return (tr2.getElements('td')[i].get('data-raw') - tr1.getElements('td')[i].get('data-raw')); - } - }, - - updateSort : function () { - var trs = this.table.getChildren('tr'); - trs.sort(this.sortfunction); - this.table.adopt(trs); - }, - - setSortedColumn : function (index) { - if (index != this.table.sortedIndex) { - this.table.sortedIndex = index; + setSortedColumn : function (column) { + if (column != this.table.sortedColumn) { + this.table.sortedColumn = column; this.table.reverseSort = false; } else { // Toggle sort order this.table.reverseSort = !this.table.reverseSort; } - this.updateSort(); - this.altRow(); }, getCurrentTorrentHash : function () { @@ -130,7 +97,7 @@ var dynamicTable = new Class({ this.priority_hidden = false; }, - insertRow : function (id, row, data, status) { + insertRow : function (id, row, data, status, pos) { if (this.rows.has(id)) { return; } @@ -254,17 +221,12 @@ var dynamicTable = new Class({ // Insert var trs = this.table.getChildren('tr'); - var i = 0; - while (i < trs.length && this.sortfunction(tr, trs[i]) > 0) { - i++; - } - if (i == trs.length) { + if (pos >= trs.length) { tr.inject(this.table); } else { - tr.inject(trs[i], 'before'); + tr.inject(trs[pos], 'before'); } //tr.injectInside(this.table); - this.altRow(); // Update context menu this.context_menu.addTarget(tr); }, @@ -279,10 +241,11 @@ var dynamicTable = new Class({ }, this); }, - updateRow : function (id, row, data, status) { + updateRow : function (id, row, data, status, newpos) { if (!this.rows.has(id)) { return false; } + var tr = this.rows.get(id); var tds = tr.getElements('td'); for (var i = 0; i < row.length; i++) { @@ -300,6 +263,18 @@ var dynamicTable = new Class({ if (typeof data[i] != 'undefined') tds[i].set('data-raw', data[i]) }; + + // Prevent freezing of the backlight. + tr.removeClass('over'); + + // Move to 'newpos' + var trs = this.table.getChildren('tr'); + if (newpos >= trs.length) { + tr.inject(this.table); + } else { + tr.inject(trs[newpos], 'before'); + } + return true; }, diff --git a/src/webui/www/public/transferlist.html b/src/webui/www/public/transferlist.html index fff6a57fc..8cfa98680 100644 --- a/src/webui/www/public/transferlist.html +++ b/src/webui/www/public/transferlist.html @@ -2,16 +2,16 @@ - _(Name) - # - _(Size) - _(Done) - _(Seeds) - _(Peers) - _(Down Speed) - _(Up Speed) - _(ETA) - _(Ratio) + _(Name) + # + _(Size) + _(Done) + _(Seeds) + _(Peers) + _(Down Speed) + _(Up Speed) + _(ETA) + _(Ratio)