From aedf579d772210aa0f4c951eb3462fadda3af97c Mon Sep 17 00:00:00 2001 From: Gabriele Date: Sat, 8 Nov 2014 16:13:00 +0100 Subject: [PATCH 1/4] WebUI: make API locale independet Sizes are now given in bytes. Dates are Unix timestamps and converted to ISO 8601 in the web UI. Numbers are not converted to strings. -1 is returned for undefined values. Some keys have been splitted: Torrent list (json/torrents) * num_seeds: Torrent seeds connected to * num_complete: Torrent seeds in the swarm * num_leechs: Torrent leechers connected to * num_incomplete: Torrent leechers in the swarm Torrent generic properties (propertiesGeneral/hash) * total_uploaded: Total data uploaded * total_uploaded_session: Total data uploaded this session * total_downloaded: Total data dowloaded * total_downloaded_session: Total data downloaded this session * time_elapsed: Torrent elapsed time * seeding_time: Torrent elapsed time while complete * nb_connections: Torrent connection count * nb_connections_limit: Torrent connection count limit Global transfer info (json/transferInfo) * dl_info_speed: Global downalod rate * dl_info_data: Data downloaded this session * up_info_speed: Global upload rate * up_info_data: Data uploaded this session Closes #1524. --- src/qtlibtorrent/qtorrenthandle.cpp | 9 +++ src/qtlibtorrent/qtorrenthandle.h | 1 + src/webui/btjson.cpp | 100 ++++++++++++++----------- src/webui/webui.qrc | 1 + src/webui/www/private/index.html | 1 + src/webui/www/public/prop-files.html | 2 +- src/webui/www/public/prop-general.html | 33 +++++--- src/webui/www/public/scripts/client.js | 31 +++++--- src/webui/www/public/scripts/misc.js | 78 +++++++++++++++++++ 9 files changed, 192 insertions(+), 64 deletions(-) create mode 100644 src/webui/www/public/scripts/misc.js diff --git a/src/qtlibtorrent/qtorrenthandle.cpp b/src/qtlibtorrent/qtorrenthandle.cpp index cedeb76ac..288d823d2 100644 --- a/src/qtlibtorrent/qtorrenthandle.cpp +++ b/src/qtlibtorrent/qtorrenthandle.cpp @@ -103,6 +103,15 @@ QString QTorrentHandle::creation_date() const { return t ? misc::toQString(*t) : ""; } +qlonglong QTorrentHandle::creation_date_unix() const { +#if LIBTORRENT_VERSION_NUM < 10000 + boost::optional t = torrent_handle::get_torrent_info().creation_date(); +#else + boost::optional t = torrent_handle::torrent_file()->creation_date(); +#endif + return t ? *t : -1; +} + QString QTorrentHandle::current_tracker() const { return misc::toQString(status(0x0).current_tracker); } diff --git a/src/qtlibtorrent/qtorrenthandle.h b/src/qtlibtorrent/qtorrenthandle.h index a41be30c2..ca7278cc0 100644 --- a/src/qtlibtorrent/qtorrenthandle.h +++ b/src/qtlibtorrent/qtorrenthandle.h @@ -84,6 +84,7 @@ public: bool is_checking() const; bool is_sequential_download() const; QString creation_date() const; + qlonglong creation_date_unix() const; bool priv() const; bool first_last_piece_first() const; QString root_path() const; diff --git a/src/webui/btjson.cpp b/src/webui/btjson.cpp index 4272e1650..5faa1f4c3 100644 --- a/src/webui/btjson.cpp +++ b/src/webui/btjson.cpp @@ -29,7 +29,6 @@ */ #include "btjson.h" -#include "misc.h" #include "fs_utils.h" #include "qbtsession.h" #include "torrentpersistentdata.h" @@ -89,7 +88,9 @@ static const char KEY_TORRENT_DLSPEED[] = "dlspeed"; static const char KEY_TORRENT_UPSPEED[] = "upspeed"; static const char KEY_TORRENT_PRIORITY[] = "priority"; static const char KEY_TORRENT_SEEDS[] = "num_seeds"; +static const char KEY_TORRENT_NUM_COMPLETE[] = "num_complete"; static const char KEY_TORRENT_LEECHS[] = "num_leechs"; +static const char KEY_TORRENT_NUM_INCOMPLETE[] = "num_incomplete"; static const char KEY_TORRENT_RATIO[] = "ratio"; static const char KEY_TORRENT_ETA[] = "eta"; static const char KEY_TORRENT_STATE[] = "state"; @@ -107,11 +108,15 @@ static const char KEY_PROP_PIECE_SIZE[] = "piece_size"; static const char KEY_PROP_COMMENT[] = "comment"; static const char KEY_PROP_WASTED[] = "total_wasted"; static const char KEY_PROP_UPLOADED[] = "total_uploaded"; +static const char KEY_PROP_UPLOADED_SESSION[] = "total_uploaded_session"; static const char KEY_PROP_DOWNLOADED[] = "total_downloaded"; +static const char KEY_PROP_DOWNLOADED_SESSION[] = "total_downloaded_session"; static const char KEY_PROP_UP_LIMIT[] = "up_limit"; static const char KEY_PROP_DL_LIMIT[] = "dl_limit"; static const char KEY_PROP_TIME_ELAPSED[] = "time_elapsed"; +static const char KEY_PROP_SEEDING_TIME[] = "seeding_time"; static const char KEY_PROP_CONNECT_COUNT[] = "nb_connections"; +static const char KEY_PROP_CONNECT_COUNT_LIMIT[] = "nb_connections_limit"; static const char KEY_PROP_RATIO[] = "share_ratio"; // File keys @@ -122,8 +127,10 @@ static const char KEY_FILE_PRIORITY[] = "priority"; static const char KEY_FILE_IS_SEED[] = "is_seed"; // TransferInfo keys -static const char KEY_TRANSFER_DLSPEED[] = "dl_info"; -static const char KEY_TRANSFER_UPSPEED[] = "up_info"; +static const char KEY_TRANSFER_DLSPEED[] = "dl_info_speed"; +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"; static QVariantMap toMap(const QTorrentHandle& h) { @@ -132,25 +139,21 @@ static QVariantMap toMap(const QTorrentHandle& h) QVariantMap ret; ret[KEY_TORRENT_HASH] = h.hash(); ret[KEY_TORRENT_NAME] = h.name(); - ret[KEY_TORRENT_SIZE] = misc::friendlyUnit(status.total_wanted, false, true); // FIXME: Should pass as Number, not formatted String (for sorting). - ret[KEY_TORRENT_PROGRESS] = (double)h.progress(status); - ret[KEY_TORRENT_DLSPEED] = misc::friendlyUnit(status.download_payload_rate, true, true); // FIXME: Should be passed as a Number - ret[KEY_TORRENT_UPSPEED] = misc::friendlyUnit(status.upload_payload_rate, true, true); // FIXME: Should be passed as a Number + 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] = QString::number(h.queue_position(status)); + ret[KEY_TORRENT_PRIORITY] = h.queue_position(status); else - ret[KEY_TORRENT_PRIORITY] = "*"; - QString seeds = QString::number(status.num_seeds); - if (status.num_complete > 0) - seeds += " ("+QString::number(status.num_complete)+")"; - ret[KEY_TORRENT_SEEDS] = seeds; - QString leechs = QString::number(status.num_peers - status.num_seeds); - if (status.num_incomplete > 0) - leechs += " ("+QString::number(status.num_incomplete)+")"; - ret[KEY_TORRENT_LEECHS] = leechs; + 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.) ? QString::fromUtf8("∞") : misc::accurateDoubleToString(ratio, 1, false); - QString eta; + ret[KEY_TORRENT_RATIO] = (ratio > 100.) ? -1 : ratio; + qulonglong eta = 0; QString state; if (h.is_paused(status)) { if (h.has_error(status)) @@ -175,14 +178,14 @@ static QVariantMap toMap(const QTorrentHandle& h) case torrent_status::downloading: case torrent_status::downloading_metadata: state = status.download_payload_rate > 0 ? "downloading" : "stalledDL"; - eta = misc::userFriendlyDuration(QBtSession::instance()->getETA(h.hash(), status)); + 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.isEmpty() ? QString::fromUtf8("∞") : eta; + ret[KEY_TORRENT_ETA] = eta ? eta : MAX_ETA; ret[KEY_TORRENT_STATE] = state; return ret; @@ -199,9 +202,11 @@ static QVariantMap toMap(const QTorrentHandle& h) * - "progress: Torrent progress * - "dlspeed": Torrent download speed * - "upspeed": Torrent upload speed - * - "priority": Torrent priority ('*' if queuing is disabled) - * - "num_seeds": Torrent seed count - * - "num_leechs": Torrent leecher count + * - "priority": Torrent priority (-1 if queuing is disabled) + * - "num_seeds": Torrent seeds connected to + * - "num_complete": Torrent seeds in the swarm + * - "num_leechs": Torrent leechers connected to + * - "num_incomplete": Torrent leechers in the swarm * - "ratio": Torrent share ratio * - "eta": Torrent ETA * - "state": Torrent state @@ -252,7 +257,7 @@ QByteArray btjson::getTrackersForTorrent(const QString& hash) status = it->fails > 0 ? tr("Not working") : tr("Not contacted yet"); } tracker_dict[KEY_TRACKER_STATUS] = status; - tracker_dict[KEY_TRACKER_PEERS] = QString::number(trackers_data.value(tracker_url, TrackerInfos(tracker_url)).num_peers); + 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); @@ -276,11 +281,15 @@ QByteArray btjson::getTrackersForTorrent(const QString& hash) * - "comment": Torrent comment * - "total_wasted": Total data wasted for torrent * - "total_uploaded": Total data uploaded for torrent + * - "total_uploaded_session": Total data uploaded this session * - "total_downloaded": Total data uploaded for torrent + * - "total_downloaded_session": Total data downloaded this session * - "up_limit": Torrent upload limit * - "dl_limit": Torrent download limit * - "time_elapsed": Torrent elapsed time + * - "seeding_time": Torrent elapsed time while complete * - "nb_connections": Torrent connection count + * - "nb_connections_limit": Torrent connection count limit * - "share_ratio": Torrent share ratio */ QByteArray btjson::getPropertiesForTorrent(const QString& hash) @@ -298,21 +307,22 @@ QByteArray btjson::getPropertiesForTorrent(const QString& 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(); - data[KEY_PROP_PIECE_SIZE] = misc::friendlyUnit(h.piece_length(), false, true); + 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] = misc::friendlyUnit(status.total_failed_bytes + status.total_redundant_bytes, false, true); - data[KEY_PROP_UPLOADED] = QString(misc::friendlyUnit(status.all_time_upload, false, true) + " (" + misc::friendlyUnit(status.total_payload_upload, false, true) + " " + tr("this session") + ")"); - data[KEY_PROP_DOWNLOADED] = QString(misc::friendlyUnit(status.all_time_download, false, true) + " (" + misc::friendlyUnit(status.total_payload_download, false, true) + " " + tr("this session") + ")"); - data[KEY_PROP_UP_LIMIT] = h.upload_limit() <= 0 ? QString::fromUtf8("∞") : misc::friendlyUnit(h.upload_limit(), true, true); - data[KEY_PROP_DL_LIMIT] = h.download_limit() <= 0 ? QString::fromUtf8("∞") : misc::friendlyUnit(h.download_limit(), true, true); - QString elapsed_txt = misc::userFriendlyDuration(status.active_time); - if (h.is_seed(status)) - elapsed_txt += " ("+tr("Seeded for %1", "e.g. Seeded for 3m10s").arg(misc::userFriendlyDuration(status.seeding_time))+")"; - data[KEY_PROP_TIME_ELAPSED] = elapsed_txt; - data[KEY_PROP_CONNECT_COUNT] = QString(QString::number(status.num_connections) + " (" + tr("%1 max", "e.g. 10 max").arg(QString::number(status.connections_limit)) + ")"); + 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. ? QString::fromUtf8("∞") : misc::accurateDoubleToString(ratio, 1, false); + 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(); @@ -350,7 +360,7 @@ QByteArray btjson::getFilesForTorrent(const QString& hash) fileName.chop(4); file_dict[KEY_FILE_NAME] = fsutils::toNativePath(fileName); const size_type size = h.filesize_at(i); - file_dict[KEY_FILE_SIZE] = misc::friendlyUnit(size, false, true); + 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) @@ -371,14 +381,18 @@ QByteArray btjson::getFilesForTorrent(const QString& hash) * * The return value is a JSON-formatted dictionary. * The dictionary keys are: - * - "dl_info": Global download info - * - "up_info": Global upload info + * - "dl_info_speed": Global download rate + * - "dl_info_data": Data downloaded this session + * - "up_info_speed": Global upload rate + * - "up_info_data": Data uploaded this session */ QByteArray btjson::getTransferInfo() { CACHED_VARIABLE(QVariantMap, info, CACHE_DURATION_MS); session_status sessionStatus = QBtSession::instance()->getSessionStatus(); - info[KEY_TRANSFER_DLSPEED] = tr("D: %1/s - T: %2", "Download speed: x KiB/s - Transferred: x MiB").arg(misc::friendlyUnit(sessionStatus.payload_download_rate, true, true)).arg(misc::friendlyUnit(sessionStatus.total_payload_download, false, true)); - info[KEY_TRANSFER_UPSPEED] = tr("U: %1/s - T: %2", "Upload speed: x KiB/s - Transferred: x MiB").arg(misc::friendlyUnit(sessionStatus.payload_upload_rate, true, true)).arg(misc::friendlyUnit(sessionStatus.total_payload_upload, false, true)); + 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/webui.qrc b/src/webui/webui.qrc index 035301d86..f0b7a13b8 100644 --- a/src/webui/webui.qrc +++ b/src/webui/webui.qrc @@ -13,6 +13,7 @@ www/public/scripts/download.js www/public/scripts/dynamicTable.js www/public/scripts/excanvas-compressed.js + www/public/scripts/misc.js www/public/scripts/mocha.js www/public/scripts/mocha-init.js www/public/scripts/mocha-yc.js diff --git a/src/webui/www/private/index.html b/src/webui/www/private/index.html index 80f10c32e..c8e246073 100644 --- a/src/webui/www/private/index.html +++ b/src/webui/www/private/index.html @@ -18,6 +18,7 @@ + diff --git a/src/webui/www/public/prop-files.html b/src/webui/www/public/prop-files.html index 9fdd9b2ad..a5ce18d9b 100644 --- a/src/webui/www/public/prop-files.html +++ b/src/webui/www/public/prop-files.html @@ -303,7 +303,7 @@ var createPriorityCombo = function(id, selected_prio) { row.length = 4; row[0] = file.priority; row[1] = file.name; - row[2] = file.size; + row[2] = friendlyUnit(file.size, false); row[3] = (file.progress*100).round(1); if(row[3] == 100.0 && file.progress < 1.0) row[3] = 99.9 diff --git a/src/webui/www/public/prop-general.html b/src/webui/www/public/prop-general.html index a8283ff79..029312dbe 100644 --- a/src/webui/www/public/prop-general.html +++ b/src/webui/www/public/prop-general.html @@ -80,19 +80,34 @@ dynamic information: total_downloaded, total_uploaded, total_wasted, up_limit, d onSuccess: function(data) { $('error_div').set('html', ''); if(data){ + var temp; // Update Torrent data $('save_path').set('html', data.save_path); - $('creation_date').set('html', data.creation_date); - $('piece_size').set('html', data.piece_size); + temp = data.creation_date; + var timestamp = "_(Unknown)"; + if (temp != -1) + timestamp = new Date(data.creation_date*1000).toISOString(); + $('creation_date').set('html', timestamp); + $('piece_size').set('html', friendlyUnit(data.piece_size)); $('comment').set('html', data.comment); - $('total_uploaded').set('html', data.total_uploaded); - $('total_downloaded').set('html', data.total_downloaded); + $('total_uploaded').set('html', friendlyUnit(data.total_uploaded) + + " (" + friendlyUnit(data.total_uploaded_session) + + " (" + "_(this session)" + ")"); + $('total_downloaded').set('html', friendlyUnit(data.total_downloaded) + + " (" + friendlyUnit(data.total_downloaded_session) + + " (" + "_(this session)" + ")"); $('total_wasted').set('html', data.total_wasted); - $('up_limit').set('html', data.up_limit); - $('dl_limit').set('html', data.dl_limit); - $('time_elapsed').set('html', data.time_elapsed); - $('nb_connections').set('html', data.nb_connections); - $('share_ratio').set('html', data.share_ratio); + temp = data.up_limit; + $('up_limit').set('html', temp == -1 ? "∞" : temp); + temp = data.dl_limit; + $('dl_limit').set('html', temp == -1 ? "∞" : temp); + temp = friendlyDuration(status.active_time); + if (status.is_seed) + temp += " (" + "_(Seeded for %1)".replace("%1", status.seeding_time) + ")"; + $('time_elapsed').set('html', temp); + temp = data.nb_connections + " (" + "_(%1 max)".replace("%1", status.nb_connections_limit) + ")"; + $('nb_connections').set('html', temp); + $('share_ratio').set('html', data.share_ratio.toFixed(2)); } else { clearData(); } diff --git a/src/webui/www/public/scripts/client.js b/src/webui/www/public/scripts/client.js index 85b100a96..d01b89546 100644 --- a/src/webui/www/public/scripts/client.js +++ b/src/webui/www/public/scripts/client.js @@ -108,8 +108,10 @@ window.addEvent('load', function(){ }, onSuccess: function(info) { if(info) { - $("DlInfos").set('html', info.dl_info); - $("UpInfos").set('html', info.up_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))); waitingTrInfo=false; loadTransferInfo.delay(3000); } @@ -146,18 +148,25 @@ window.addEvent('load', function(){ row.length = 10; row[0] = stateToImg(event.state); row[1] = event.name; - row[2] = event.priority - row[3] = event.size; + row[2] = event.priority > -1 ? event.priority : null; + row[3] = friendlyUnit(event.size, false); row[4] = (event.progress*100).round(1); if(row[4] == 100.0 && event.progress != 1.0) row[4] = 99.9; - row[5] = event.num_seeds; - row[6] = event.num_leechs; - row[7] = event.dlspeed; - row[8] = event.upspeed; - row[9] = event.eta; - row[10] = event.ratio; - if(row[2] != "*") + row[5] = event.num_seeds; + if (event.num_complete != -1) + row[5] += " (" + event.num_complete + ")"; + row[6] = event.num_leechs; + if (event.num_incomplete != -1) + row[6] += " (" + event.num_incomplete + ")"; + row[7] = friendlyUnit(event.dlspeed, true); + row[8] = friendlyUnit(event.upspeed, true); + row[9] = friendlyDuration(event.eta); + if(event.ratio == -1) + row[10] = "∞"; + else + row[10] = (Math.floor(100 * event.ratio) / 100).toFixed(2); //Don't round up + if(row[2] != null) queueing_enabled = true; if(!torrent_hashes.contains(event.hash)) { // New unfinished torrent diff --git a/src/webui/www/public/scripts/misc.js b/src/webui/www/public/scripts/misc.js new file mode 100644 index 000000000..b547a621a --- /dev/null +++ b/src/webui/www/public/scripts/misc.js @@ -0,0 +1,78 @@ +/* + * JS counterpart of the function in src/misc.cpp + */ +function friendlyUnit(value, isSpeed) { + units = [ + "_(B)", + "_(KiB)", + "_(MiB)", + "_(GiB)", + "_(TiB)", + ]; + + if (value < 0) + return "_(Unknown)"; + var i = 0; + while (value >= 1024. && i++ < 6) + value /= 1024.; + var ret; + if (i == 0) + ret = value.toFixed(2) + " " + units[0]; + else + ret = value.toFixed(2) + " " + units[i]; + if (isSpeed) + ret += "_(/s)"; + return ret; +} + +/* + * JS counterpart of the function in src/misc.cpp + */ +function friendlyDuration(seconds) { + var MAX_ETA = 8640000; + if (seconds < 0 || seconds >= MAX_ETA) + return "∞"; + if (seconds == 0) + return "0"; + if (seconds < 60) + return "< " + "_(%1m)".replace("%1", "1"); //translation of "< 1m" not working + var minutes = seconds / 60; + if (minutes < 60) + return "_(%1m)".replace("%1", parseInt(minutes)); + var hours = minutes / 60; + minutes = minutes - hours*60; + if (hours < 24) + return "_(%1h %2m)".replace("%1", parseInt(hours)).replace("%2", parseInt(minutes)) + var days = hours / 24; + hours = hours - days * 24; + if (days < 100) + return "_(%1d %2h)".replace("%1", parseInt(days)).replace("%2", parseInt(hours)) + return "∞"; +} + +/* + * From: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString + */ +if (!Date.prototype.toISOString) { + (function() { + + function pad(number) { + if (number < 10) { + return '0' + number; + } + return number; + } + + Date.prototype.toISOString = function() { + return this.getUTCFullYear() + + '-' + pad(this.getUTCMonth() + 1) + + '-' + pad(this.getUTCDate()) + + 'T' + pad(this.getUTCHours()) + + ':' + pad(this.getUTCMinutes()) + + ':' + pad(this.getUTCSeconds()) + + '.' + (this.getUTCMilliseconds() / 1000).toFixed(3).slice(2, 5) + + 'Z'; + }; + + }()); +} \ No newline at end of file From ec592f817574f3b6e0594d999c9ecfac539e3745 Mon Sep 17 00:00:00 2001 From: Gabriele Date: Sat, 8 Nov 2014 16:13:00 +0100 Subject: [PATCH 2/4] Remove unneeded parameters from helper functions for WebUI --- src/misc.cpp | 13 ++++--------- src/misc.h | 4 ++-- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/misc.cpp b/src/misc.cpp index 1504d89db..ff78595c9 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -289,9 +289,8 @@ int misc::pythonVersion() { // use Binary prefix standards from IEC 60027-2 // see http://en.wikipedia.org/wiki/Kilobyte // value must be given in bytes -// FIXME: Remove the 'webui' variable, when the webui API is reworked // to send numbers instead of strings with suffixes -QString misc::friendlyUnit(qreal val, bool is_speed, bool webui) { +QString misc::friendlyUnit(qreal val, bool is_speed) { if (val < 0) return QCoreApplication::translate("misc", "Unknown", "Unknown (size)"); int i = 0; @@ -301,7 +300,7 @@ QString misc::friendlyUnit(qreal val, bool is_speed, bool webui) { if (i == 0) ret = QString::number((long)val) + " " + QCoreApplication::translate("misc", units[0].source, units[0].comment); else - ret = accurateDoubleToString(val, 1, !webui) + " " + QCoreApplication::translate("misc", units[i].source, units[i].comment); + ret = accurateDoubleToString(val, 1) + " " + QCoreApplication::translate("misc", units[i].source, units[i].comment); if (is_speed) ret += QCoreApplication::translate("misc", "/s", "per second"); return ret; @@ -630,9 +629,8 @@ bool misc::naturalSort(QString left, QString right, bool &result) { // uses less } #endif -// FIXME: Remove the 'localized' variable, when the webui API is reworked // to send numbers instead of strings with suffixes -QString misc::accurateDoubleToString(const double &n, const int &precision, bool localized) { +QString misc::accurateDoubleToString(const double &n, const int &precision) { /* HACK because QString rounds up. Eg QString::number(0.999*100.0, 'f' ,1) == 99.9 ** but QString::number(0.9999*100.0, 'f' ,1) == 100.0 The problem manifests when ** the number has more digits after the decimal than we want AND the digit after @@ -640,10 +638,7 @@ QString misc::accurateDoubleToString(const double &n, const int &precision, bool ** precision we add an extra 0 behind 1 in the below algorithm. */ double prec = std::pow(10.0, precision); - if (localized) - return QLocale::system().toString(std::floor(n*prec)/prec, 'f', precision); - else - return QString::number(std::floor(n*prec)/prec, 'f', precision); + return QLocale::system().toString(std::floor(n*prec)/prec, 'f', precision); } // Implements constant-time comparison to protect against timing attacks diff --git a/src/misc.h b/src/misc.h index ccc33b0e8..8409e84c9 100644 --- a/src/misc.h +++ b/src/misc.h @@ -83,7 +83,7 @@ namespace misc // use Binary prefix standards from IEC 60027-2 // see http://en.wikipedia.org/wiki/Kilobyte // value must be given in bytes - QString friendlyUnit(qreal val, bool is_speed = false, bool webui=false); + QString friendlyUnit(qreal val, bool is_speed = false); bool isPreviewable(const QString& extension); QString magnetUriToName(const QString& magnet_uri); QString magnetUriToHash(const QString& magnet_uri); @@ -100,7 +100,7 @@ namespace misc QList boolListfromStringList(const QStringList &l); QString toQString(time_t t); - QString accurateDoubleToString(const double &n, const int &precision, bool localized=true); + QString accurateDoubleToString(const double &n, const int &precision); #ifndef DISABLE_GUI bool naturalSort(QString left, QString right, bool& result); From ed830705170077274b693ed0b947fa022205a527 Mon Sep 17 00:00:00 2001 From: Gabriele Date: Sun, 9 Nov 2014 20:00:00 +0100 Subject: [PATCH 3/4] Use raw data to sort columns Store the raw data retrieved in a new data-raw attribute and use these to sort the columns. In addition, make the ETA column sortable. --- src/webui/www/public/scripts/client.js | 14 ++++- src/webui/www/public/scripts/dynamicTable.js | 62 ++++---------------- src/webui/www/public/transferlist.html | 2 +- 3 files changed, 26 insertions(+), 52 deletions(-) diff --git a/src/webui/www/public/scripts/client.js b/src/webui/www/public/scripts/client.js index d01b89546..0e9bfe33a 100644 --- a/src/webui/www/public/scripts/client.js +++ b/src/webui/www/public/scripts/client.js @@ -145,37 +145,47 @@ window.addEvent('load', function(){ 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, event.state); + myTable.insertRow(event.hash, row, data, event.state); } else { // Update torrent data - myTable.updateRow(event.hash, row, event.state); + myTable.updateRow(event.hash, row, data, event.state); } }); // Remove deleted torrents diff --git a/src/webui/www/public/scripts/dynamicTable.js b/src/webui/www/public/scripts/dynamicTable.js index 46b4eb38b..fcb5a6579 100644 --- a/src/webui/www/public/scripts/dynamicTable.js +++ b/src/webui/www/public/scripts/dynamicTable.js @@ -66,60 +66,18 @@ var dynamicTable = new Class ({ return -1; } case 2: // Prio - var prio1 = tr1.getElements('td')[i].get('html'); - if(prio1 == '*') prio1 = '-1'; - var prio2 = tr2.getElements('td')[i].get('html'); - if(prio2 == '*') prio2 = '-1'; - if(!reverseSort) - return (prio1.toInt() - prio2.toInt()); - else - return (prio2.toInt() - prio1.toInt()); case 3: // Size - case 7: // Up Speed - case 8: // Down Speed - var sizeStrToFloat = function(mystr) { - var val1 = mystr.split(' '); - var val1num = val1[0].toFloat() - var unit = val1[1]; - switch(unit) { - case '_(TiB)': - return val1num*1099511627776; - case '_(GiB)': - return val1num*1073741824; - case '_(MiB)': - return val1num*1048576; - case '_(KiB)': - return val1num*1024; - default: - return val1num; - } - }; - if(!reverseSort) - return (sizeStrToFloat(tr1.getElements('td')[i].get('html')) - sizeStrToFloat(tr2.getElements('td')[i].get('html'))); - else - return (sizeStrToFloat(tr2.getElements('td')[i].get('html')) - sizeStrToFloat(tr1.getElements('td')[i].get('html'))); case 4: // Progress - if(!reverseSort) - return (tr1.getElements('td')[i].getChildren()[0].getValue() - tr2.getElements('td')[i].getChildren()[0].getValue()); - else - return (tr2.getElements('td')[i].getChildren()[0].getValue() - tr1.getElements('td')[i].getChildren()[0].getValue()); case 5: // Seeds case 6: // Peers - if(!reverseSort) - return (tr1.getElements('td')[i].get('html').split(' ')[0].toInt() - tr2.getElements('td')[i].get('html').split(' ')[0].toInt()); - else - return (tr2.getElements('td')[i].get('html').split(' ')[0].toInt() - tr1.getElements('td')[i].get('html').split(' ')[0].toInt()); + case 7: // Up Speed + case 8: // Down Speed + case 9: // ETA default: // Ratio - var ratio1 = tr1.getElements('td')[i].get('html'); - if(ratio1 == '∞') - ratio1 = '101.0'; - var ratio2 = tr2.getElements('td')[i].get('html'); - if(ratio2 == '∞') - ratio2 = '101.0'; if(!reverseSort) - return (ratio1.toFloat() - ratio2.toFloat()); + return (tr1.getElements('td')[i].get('data-raw') - tr2.getElements('td')[i].get('data-raw')); else - return (ratio2.toFloat() - ratio1.toFloat()); + return (tr2.getElements('td')[i].get('data-raw') - tr1.getElements('td')[i].get('data-raw')); } }, @@ -229,7 +187,7 @@ var dynamicTable = new Class ({ return !tr.hasClass('invisible'); }, - insertRow: function(id, row, status){ + insertRow: function(id, row, data, status){ if(this.rows.has(id)) { return; } @@ -241,6 +199,8 @@ var dynamicTable = new Class ({ 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'})); @@ -251,6 +211,8 @@ var dynamicTable = new Class ({ td.addClass('invisible'); } td.set('html', row[i]); + if (typeof data[i] != 'undefined') + td.set('data-raw', data[i]) } } td.injectInside(tr); @@ -370,7 +332,7 @@ var dynamicTable = new Class ({ }, this); }, - updateRow: function(id, row, status){ + updateRow: function(id, row, data, status){ if(!this.rows.has(id)) { return false; } @@ -389,6 +351,8 @@ var dynamicTable = new Class ({ 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 diff --git a/src/webui/www/public/transferlist.html b/src/webui/www/public/transferlist.html index e2e7243f4..8df936728 100644 --- a/src/webui/www/public/transferlist.html +++ b/src/webui/www/public/transferlist.html @@ -10,7 +10,7 @@ _(Peers) _(Down Speed) _(Up Speed) - _(ETA) + _(ETA) _(Ratio) From b4acb2ef521cf78250717bc72c899410374009fb Mon Sep 17 00:00:00 2001 From: Gabriele Date: Sun, 9 Nov 2014 20:00:00 +0100 Subject: [PATCH 4/4] Use localeCompare to sort torrent list by name --- src/webui/www/public/scripts/dynamicTable.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/webui/www/public/scripts/dynamicTable.js b/src/webui/www/public/scripts/dynamicTable.js index fcb5a6579..72d2ae78a 100644 --- a/src/webui/www/public/scripts/dynamicTable.js +++ b/src/webui/www/public/scripts/dynamicTable.js @@ -56,15 +56,10 @@ var dynamicTable = new Class ({ var reverseSort = tr2.getParent().reverseSort; switch(i) { case 1: // Name - if(!reverseSort) { - if(tr1.getElements('td')[i].get('html') > tr2.getElements('td')[i].get('html')) - return 1; - return -1; - } else { - if(tr1.getElements('td')[i].get('html') < tr2.getElements('td')[i].get('html')) - return 1; - return -1; - } + 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