--- /dev/null
+{
+ "indent_size": 4,
+ "indent_char": " ",
+ "indent_level": 0,
+ "indent_with_tabs": false,
+ "preserve_newlines": true,
+ "max_preserve_newlines": 2,
+ "jslint_happy": true
+}
isMobileDevice = RegExp("(iPhone|iPod|Android)").test(navigator.userAgent),
scroll_timeout;
-if (!Array.indexOf){
- Array.prototype.indexOf = function(obj){
- var i, len;
- for (i=0, len=this.length; i<len; i++)
- if (this[i]==obj)
- return i;
- return -1;
- }
-}
+if (!Array.indexOf) {
+ Array.prototype.indexOf = function (obj) {
+ var i, len;
+ for (i = 0, len = this.length; i < len; i++) {
+ if (this[i] == obj) {
+ return i;
+ };
+ };
+ return -1;
+ };
+};
// http://forum.jquery.com/topic/combining-ui-dialog-and-tabs
$.fn.tabbedDialog = function (dialog_opts) {
- this.tabs({selected: 0});
- this.dialog(dialog_opts);
- this.find('.ui-tab-dialog-close').append(this.parent().find('.ui-dialog-titlebar-close'));
- this.find('.ui-tab-dialog-close').css({'position':'absolute','right':'0', 'top':'16px'});
- this.find('.ui-tab-dialog-close > a').css({'float':'none','padding':'0'});
- var tabul = this.find('ul:first');
- this.parent().addClass('ui-tabs').prepend(tabul).draggable('option','handle',tabul);
- this.siblings('.ui-dialog-titlebar').remove();
- tabul.addClass('ui-dialog-titlebar');
+ this.tabs({
+ selected: 0
+ });
+ this.dialog(dialog_opts);
+ this.find('.ui-tab-dialog-close').append(this.parent().find('.ui-dialog-titlebar-close'));
+ this.find('.ui-tab-dialog-close').css({
+ 'position': 'absolute',
+ 'right': '0',
+ 'top': '16px'
+ });
+ this.find('.ui-tab-dialog-close > a').css({
+ 'float': 'none',
+ 'padding': '0'
+ });
+ var tabul = this.find('ul:first');
+ this.parent().addClass('ui-tabs').prepend(tabul).draggable('option', 'handle', tabul);
+ this.siblings('.ui-dialog-titlebar').remove();
+ tabul.addClass('ui-dialog-titlebar');
}
-$(document).ready(function() {
-
- // IE8 and below don’t support ES5 Date.now()
- if (!Date.now) {
- Date.now = function() {
- return +new Date();
- };
- }
-
- // IE specific fixes here
- if ($.browser.msie) {
- try {
- document.execCommand("BackgroundImageCache", false, true);
- } catch(err) {}
- $('.dialog_container').css('height',$(window).height()+'px');
- }
-
- if ($.browser.safari) {
- // Move search field's margin down for the styled input
- $('#torrent_search').css('margin-top', 3);
- }
- if (isMobileDevice){
- window.onload = function(){ setTimeout(function() { window.scrollTo(0,1); },500); };
- window.onorientationchange = function(){ setTimeout(function() { window.scrollTo(0,1); },100); };
- if (window.navigator.standalone)
- // Fix min height for isMobileDevice when run in full screen mode from home screen
- // so the footer appears in the right place
- $('body div#torrent_container').css('min-height', '338px');
- $("label[for=torrent_upload_url]").text("URL: ");
- } else {
- // Fix for non-Safari-3 browsers: dark borders to replace shadows.
- $('div.dialog_container div.dialog_window').css('border', '1px solid #777');
- }
-
- // Initialise the dialog controller
- dialog = new Dialog();
-
- // Initialise the main Transmission controller
- transmission = new Transmission();
+$(document).ready(function () {
+
+ // IE8 and below don’t support ES5 Date.now()
+ if (!Date.now) {
+ Date.now = function () {
+ return +new Date();
+ };
+ };
+
+ // IE specific fixes here
+ if ($.browser.msie) {
+ try {
+ document.execCommand("BackgroundImageCache", false, true);
+ } catch (err) {};
+ $('.dialog_container').css('height', $(window).height() + 'px');
+ };
+
+ if ($.browser.safari) {
+ // Move search field's margin down for the styled input
+ $('#torrent_search').css('margin-top', 3);
+ };
+
+ if (isMobileDevice) {
+ window.onload = function () {
+ setTimeout(function () {
+ window.scrollTo(0, 1);
+ }, 500);
+ };
+ window.onorientationchange = function () {
+ setTimeout(function () {
+ window.scrollTo(0, 1);
+ }, 100);
+ };
+ if (window.navigator.standalone) {
+ // Fix min height for isMobileDevice when run in full screen mode from home screen
+ // so the footer appears in the right place
+ $('body div#torrent_container').css('min-height', '338px');
+ };
+ $("label[for=torrent_upload_url]").text("URL: ");
+ } else {
+ // Fix for non-Safari-3 browsers: dark borders to replace shadows.
+ $('div.dialog_container div.dialog_window').css('border', '1px solid #777');
+ };
+
+ // Initialise the dialog controller
+ dialog = new Dialog();
+
+ // Initialise the main Transmission controller
+ transmission = new Transmission();
});
/**
* Checks to see if the content actually changed before poking the DOM.
*/
-function setInnerHTML(e, html)
-{
- if (!e)
- return;
-
- /* innerHTML is listed as a string, but the browser seems to change it.
- * For example, "∞" gets changed to "∞" somewhere down the line.
- * So, let's use an arbitrary different field to test our state... */
- if (e.currentHTML != html)
- {
- e.currentHTML = html;
- e.innerHTML = html;
- }
+function setInnerHTML(e, html) {
+ if (!e) {
+ return;
+ };
+
+ /* innerHTML is listed as a string, but the browser seems to change it.
+ * For example, "∞" gets changed to "∞" somewhere down the line.
+ * So, let's use an arbitrary different field to test our state... */
+ if (e.currentHTML != html) {
+ e.currentHTML = html;
+ e.innerHTML = html;
+ };
};
-function sanitizeText(text)
-{
- return text.replace(/</g, "<").replace(/>/g, ">");
+function sanitizeText(text) {
+ return text.replace(/</g, "<").replace(/>/g, ">");
};
/**
* on torrents whose state hasn't changed since the last update,
* so see if the text actually changed before poking the DOM.
*/
-function setTextContent(e, text)
-{
- if (e && (e.textContent != text))
- e.textContent = text;
+function setTextContent(e, text) {
+ if (e && (e.textContent != text)) {
+ e.textContent = text;
+ };
};
/*
* Given a numerator and denominator, return a ratio string
*/
-Math.ratio = function(numerator, denominator) {
- var result = Math.floor(100 * numerator / denominator) / 100;
+Math.ratio = function (numerator, denominator) {
+ var result = Math.floor(100 * numerator / denominator) / 100;
- // check for special cases
- if (result==Number.POSITIVE_INFINITY || result==Number.NEGATIVE_INFINITY) result = -2;
- else if (isNaN(result)) result = -1;
+ // check for special cases
+ if (result == Number.POSITIVE_INFINITY || result == Number.NEGATIVE_INFINITY) {
+ result = -2;
+ } else if (isNaN(result)) {
+ result = -1;
+ };
- return result;
+ return result;
};
/**
* Round a string of a number to a specified number of decimal places
*/
-Number.prototype.toTruncFixed = function(place) {
- var ret = Math.floor(this * Math.pow (10, place)) / Math.pow(10, place);
- return ret.toFixed(place);
-}
+Number.prototype.toTruncFixed = function (place) {
+ var ret = Math.floor(this * Math.pow(10, place)) / Math.pow(10, place);
+ return ret.toFixed(place);
+};
-Number.prototype.toStringWithCommas = function() {
+Number.prototype.toStringWithCommas = function () {
return this.toString().replace(/\B(?=(?:\d{3})+(?!\d))/g, ",");
-}
-
+};
/*
* Trim whitespace from a string
*/
String.prototype.trim = function () {
- return this.replace(/^\s*/, "").replace(/\s*$/, "");
-}
+ return this.replace(/^\s*/, "").replace(/\s*$/, "");
+};
/***
-**** Preferences
-***/
-
-function Prefs() { }
-Prefs.prototype = { };
-
-Prefs._RefreshRate = 'refresh_rate';
-
-Prefs._FilterMode = 'filter';
-Prefs._FilterAll = 'all';
-Prefs._FilterActive = 'active';
-Prefs._FilterSeeding = 'seeding';
-Prefs._FilterDownloading = 'downloading';
-Prefs._FilterPaused = 'paused';
-Prefs._FilterFinished = 'finished';
-
-Prefs._SortDirection = 'sort_direction';
-Prefs._SortAscending = 'ascending';
-Prefs._SortDescending = 'descending';
-
-Prefs._SortMethod = 'sort_method';
-Prefs._SortByAge = 'age';
-Prefs._SortByActivity = 'activity';
-Prefs._SortByName = 'name';
-Prefs._SortByQueue = 'queue_order';
-Prefs._SortBySize = 'size';
-Prefs._SortByProgress = 'percent_completed';
-Prefs._SortByRatio = 'ratio';
-Prefs._SortByState = 'state';
-
-Prefs._CompactDisplayState= 'compact_display_state';
-
-Prefs._Defaults =
-{
- 'filter': 'all',
- 'refresh_rate' : 5,
- 'sort_direction': 'ascending',
- 'sort_method': 'name',
- 'turtle-state' : false,
- 'compact_display_state' : false
+ **** Preferences
+ ***/
+
+function Prefs() {};
+Prefs.prototype = {};
+
+Prefs._RefreshRate = 'refresh_rate';
+
+Prefs._FilterMode = 'filter';
+Prefs._FilterAll = 'all';
+Prefs._FilterActive = 'active';
+Prefs._FilterSeeding = 'seeding';
+Prefs._FilterDownloading = 'downloading';
+Prefs._FilterPaused = 'paused';
+Prefs._FilterFinished = 'finished';
+
+Prefs._SortDirection = 'sort_direction';
+Prefs._SortAscending = 'ascending';
+Prefs._SortDescending = 'descending';
+
+Prefs._SortMethod = 'sort_method';
+Prefs._SortByAge = 'age';
+Prefs._SortByActivity = 'activity';
+Prefs._SortByName = 'name';
+Prefs._SortByQueue = 'queue_order';
+Prefs._SortBySize = 'size';
+Prefs._SortByProgress = 'percent_completed';
+Prefs._SortByRatio = 'ratio';
+Prefs._SortByState = 'state';
+
+Prefs._CompactDisplayState = 'compact_display_state';
+
+Prefs._Defaults = {
+ 'filter': 'all',
+ 'refresh_rate': 5,
+ 'sort_direction': 'ascending',
+ 'sort_method': 'name',
+ 'turtle-state': false,
+ 'compact_display_state': false
};
/*
* Set a preference option
*/
-Prefs.setValue = function(key, val)
-{
- if (!(key in Prefs._Defaults))
- console.warn("unrecognized preference key '%s'", key);
-
- var date = new Date();
- date.setFullYear (date.getFullYear() + 1);
- document.cookie = key+"="+val+"; expires="+date.toGMTString()+"; path=/";
+Prefs.setValue = function (key, val) {
+ if (!(key in Prefs._Defaults)) {
+ console.warn("unrecognized preference key '%s'", key);
+ };
+
+ var date = new Date();
+ date.setFullYear(date.getFullYear() + 1);
+ document.cookie = key + "=" + val + "; expires=" + date.toGMTString() + "; path=/";
};
/**
* @param key the preference's key
* @param fallback if the option isn't set, return this instead
*/
-Prefs.getValue = function(key, fallback)
-{
- var val;
-
- if (!(key in Prefs._Defaults))
- console.warn("unrecognized preference key '%s'", key);
-
- var lines = document.cookie.split(';');
- for (var i=0, len=lines.length; !val && i<len; ++i) {
- var line = lines[i].trim();
- var delim = line.indexOf('=');
- if ((delim === key.length) && line.indexOf(key) === 0)
- val = line.substring(delim + 1);
- }
-
- // FIXME: we support strings and booleans... add number support too?
- if (!val) val = fallback;
- else if (val === 'true') val = true;
- else if (val === 'false') val = false;
- return val;
+Prefs.getValue = function (key, fallback) {
+ var val;
+
+ if (!(key in Prefs._Defaults)) {
+ console.warn("unrecognized preference key '%s'", key);
+ };
+
+ var lines = document.cookie.split(';');
+ for (var i = 0, len = lines.length; !val && i < len; ++i) {
+ var line = lines[i].trim();
+ var delim = line.indexOf('=');
+ if ((delim === key.length) && line.indexOf(key) === 0) {
+ val = line.substring(delim + 1);
+ };
+ };
+
+ // FIXME: we support strings and booleans... add number support too?
+ if (!val) {
+ val = fallback;
+ } else if (val === 'true') {
+ val = true;
+ } else if (val === 'false') {
+ val = false;
+ };
+ return val;
};
/**
*
* @pararm o object to be populated (optional)
*/
-Prefs.getClutchPrefs = function(o)
-{
- if (!o)
- o = { };
- for (var key in Prefs._Defaults)
- o[key] = Prefs.getValue(key, Prefs._Defaults[key]);
- return o;
+Prefs.getClutchPrefs = function (o) {
+ if (!o) {
+ o = {};
+ };
+ for (var key in Prefs._Defaults) {
+ o[key] = Prefs.getValue(key, Prefs._Defaults[key]);
+ };
+ return o;
};
-
// forceNumeric() plug-in implementation
jQuery.fn.forceNumeric = function () {
- return this.each(function () {
- $(this).keydown(function (e) {
- var key = e.which || e.keyCode;
- return !e.shiftKey && !e.altKey && !e.ctrlKey &&
- // numbers
- key >= 48 && key <= 57 ||
- // Numeric keypad
- key >= 96 && key <= 105 ||
- // comma, period and minus, . on keypad
- key === 190 || key === 188 || key === 109 || key === 110 ||
- // Backspace and Tab and Enter
- key === 8 || key === 9 || key === 13 ||
- // Home and End
- key === 35 || key === 36 ||
- // left and right arrows
- key === 37 || key === 39 ||
- // Del and Ins
- key === 46 || key === 45;
- });
- });
+ return this.each(function () {
+ $(this).keydown(function (e) {
+ var key = e.which || e.keyCode;
+ return !e.shiftKey && !e.altKey && !e.ctrlKey &&
+ // numbers
+ key >= 48 && key <= 57 ||
+ // Numeric keypad
+ key >= 96 && key <= 105 ||
+ // comma, period and minus, . on keypad
+ key === 190 || key === 188 || key === 109 || key === 110 ||
+ // Backspace and Tab and Enter
+ key === 8 || key === 9 || key === 13 ||
+ // Home and End
+ key === 35 || key === 36 ||
+ // left and right arrows
+ key === 37 || key === 39 ||
+ // Del and Ins
+ key === 46 || key === 45;
+ });
+ });
}
-
/**
* http://blog.stevenlevithan.com/archives/parseuri
*
* (c) Steven Levithan <stevenlevithan.com>
* MIT License
*/
-function parseUri (str) {
- var o = parseUri.options,
- m = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
- uri = {},
- i = 14;
-
- while (i--) uri[o.key[i]] = m[i] || "";
-
- uri[o.q.name] = {};
- uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
- if ($1) uri[o.q.name][$1] = $2;
- });
-
- return uri;
+function parseUri(str) {
+ var o = parseUri.options;
+ var m = o.parser[o.strictMode ? "strict" : "loose"].exec(str);
+ var uri = {};
+ var i = 14;
+
+ while (i--) {
+ uri[o.key[i]] = m[i] || "";
+ };
+
+ uri[o.q.name] = {};
+ uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
+ if ($1) {
+ uri[o.q.name][$1] = $2;
+ };
+ });
+
+ return uri;
};
parseUri.options = {
- strictMode: false,
- key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
- q: {
- name: "queryKey",
- parser: /(?:^|&)([^&=]*)=?([^&]*)/g
- },
- parser: {
- strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
- loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
- }
+ strictMode: false,
+ key: ["source", "protocol", "authority", "userInfo", "user", "password", "host", "port", "relative", "path", "directory", "file", "query", "anchor"],
+ q: {
+ name: "queryKey",
+ parser: /(?:^|&)([^&=]*)=?([^&]*)/g
+ },
+ parser: {
+ strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
+ loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
+ }
};
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*/
-function Dialog(){
- this.initialize();
-}
+function Dialog() {
+ this.initialize();
+};
Dialog.prototype = {
- /*
- * Constructor
- */
- initialize: function() {
-
- /*
- * Private Interface Variables
- */
- this._container = $('#dialog_container');
- this._heading = $('#dialog_heading');
- this._message = $('#dialog_message');
- this._cancel_button = $('#dialog_cancel_button');
- this._confirm_button = $('#dialog_confirm_button');
- this._callback = null;
-
- // Observe the buttons
- this._cancel_button.bind('click', {dialog: this}, this.onCancelClicked);
- this._confirm_button.bind('click', {dialog: this}, this.onConfirmClicked);
- },
-
-
-
-
-
- /*--------------------------------------------
- *
- * E V E N T F U N C T I O N S
- *
- *--------------------------------------------*/
-
- hideDialog: function()
- {
- $('body.dialog_showing').removeClass('dialog_showing');
- this._container.hide();
- transmission.hideMobileAddressbar();
- transmission.updateButtonStates();
- },
-
- onCancelClicked: function(event)
- {
- event.data.dialog.hideDialog();
- },
-
- onConfirmClicked: function(event)
- {
- var dialog = event.data.dialog;
- dialog._callback();
- dialog.hideDialog();
- },
-
- /*--------------------------------------------
- *
- * I N T E R F A C E F U N C T I O N S
- *
- *--------------------------------------------*/
-
- /*
- * Display a confirm dialog
- */
- confirm: function(dialog_heading, dialog_message, confirm_button_label,
- callback, cancel_button_label)
- {
- if (!isMobileDevice)
- $('.dialog_container').hide();
- setTextContent(this._heading[0], dialog_heading);
- setTextContent(this._message[0], dialog_message);
- setTextContent(this._cancel_button[0], cancel_button_label || 'Cancel');
- setTextContent(this._confirm_button[0], confirm_button_label);
- this._confirm_button.show();
- this._callback = callback;
- $('body').addClass('dialog_showing');
- this._container.show();
- transmission.updateButtonStates();
- if (isMobileDevice)
- transmission.hideMobileAddressbar();
- },
-
- /*
- * Display an alert dialog
- */
- alert: function(dialog_heading, dialog_message, cancel_button_label) {
- if (!isMobileDevice)
- $('.dialog_container').hide();
- setTextContent(this._heading[0], dialog_heading);
- setTextContent(this._message[0], dialog_message);
- // jquery::hide() doesn't work here in Safari for some odd reason
- this._confirm_button.css('display', 'none');
- setTextContent(this._cancel_button[0], cancel_button_label);
- // Just in case
- $('#upload_container').hide();
- $('#move_container').hide();
- $('body').addClass('dialog_showing');
- transmission.updateButtonStates();
- if (isMobileDevice)
- transmission.hideMobileAddressbar();
- this._container.show();
- }
-
-
-}
+ /*
+ * Constructor
+ */
+ initialize: function () {
+
+ /*
+ * Private Interface Variables
+ */
+ this._container = $('#dialog_container');
+ this._heading = $('#dialog_heading');
+ this._message = $('#dialog_message');
+ this._cancel_button = $('#dialog_cancel_button');
+ this._confirm_button = $('#dialog_confirm_button');
+ this._callback = null;
+
+ // Observe the buttons
+ this._cancel_button.bind('click', {
+ dialog: this
+ }, this.onCancelClicked);
+ this._confirm_button.bind('click', {
+ dialog: this
+ }, this.onConfirmClicked);
+ },
+
+ /*--------------------------------------------
+ *
+ * E V E N T F U N C T I O N S
+ *
+ *--------------------------------------------*/
+
+ hideDialog: function () {
+ $('body.dialog_showing').removeClass('dialog_showing');
+ this._container.hide();
+ transmission.hideMobileAddressbar();
+ transmission.updateButtonStates();
+ },
+
+ onCancelClicked: function (event) {
+ event.data.dialog.hideDialog();
+ },
+
+ onConfirmClicked: function (event) {
+ var dialog = event.data.dialog;
+ dialog._callback();
+ dialog.hideDialog();
+ },
+
+ /*--------------------------------------------
+ *
+ * I N T E R F A C E F U N C T I O N S
+ *
+ *--------------------------------------------*/
+
+ /*
+ * Display a confirm dialog
+ */
+ confirm: function (dialog_heading, dialog_message, confirm_button_label,
+ callback, cancel_button_label) {
+ if (!isMobileDevice) {
+ $('.dialog_container').hide();
+ };
+ setTextContent(this._heading[0], dialog_heading);
+ setTextContent(this._message[0], dialog_message);
+ setTextContent(this._cancel_button[0], cancel_button_label || 'Cancel');
+ setTextContent(this._confirm_button[0], confirm_button_label);
+ this._confirm_button.show();
+ this._callback = callback;
+ $('body').addClass('dialog_showing');
+ this._container.show();
+ transmission.updateButtonStates();
+ if (isMobileDevice) {
+ transmission.hideMobileAddressbar();
+ };
+ },
+
+ /*
+ * Display an alert dialog
+ */
+ alert: function (dialog_heading, dialog_message, cancel_button_label) {
+ if (!isMobileDevice) {
+ $('.dialog_container').hide();
+ };
+ setTextContent(this._heading[0], dialog_heading);
+ setTextContent(this._message[0], dialog_message);
+ // jquery::hide() doesn't work here in Safari for some odd reason
+ this._confirm_button.css('display', 'none');
+ setTextContent(this._cancel_button[0], cancel_button_label);
+ // Just in case
+ $('#upload_container').hide();
+ $('#move_container').hide();
+ $('body').addClass('dialog_showing');
+ transmission.updateButtonStates();
+ if (isMobileDevice) {
+ transmission.hideMobileAddressbar();
+ };
+ this._container.show();
+ }
+};
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*/
-function FileRow(torrent, depth, name, indices, even)
-{
- var fields = {
- have: 0,
- indices: [],
- isWanted: true,
- priorityLow: false,
- priorityNormal: false,
- priorityHigh: false,
- me: this,
- size: 0,
- torrent: null
- },
-
- elements = {
- priority_low_button: null,
- priority_normal_button: null,
- priority_high_button: null,
- progress: null,
- root: null
- },
-
- initialize = function(torrent, depth, name, indices, even) {
- fields.torrent = torrent;
- fields.indices = indices;
- createRow(torrent, depth, name, even);
- },
-
- refreshWantedHTML = function()
- {
- var e = $(elements.root);
- e.toggleClass('skip', !fields.isWanted);
- e.toggleClass('complete', isDone());
- $(e[0].checkbox).prop('disabled', !isEditable());
- $(e[0].checkbox).prop('checked', fields.isWanted);
- },
- refreshProgressHTML = function()
- {
- var pct = 100 * (fields.size ? (fields.have / fields.size) : 1.0),
- c = [ Transmission.fmt.size(fields.have),
- ' of ',
- Transmission.fmt.size(fields.size),
- ' (',
- Transmission.fmt.percentString(pct),
- '%)' ].join('');
- setTextContent(elements.progress, c);
- },
- refreshImpl = function() {
- var i,
- file,
- have = 0,
- size = 0,
- wanted = false,
- low = false,
- normal = false,
- high = false;
-
- // loop through the file_indices that affect this row
- for (i=0; i<fields.indices.length; ++i) {
- file = fields.torrent.getFile (fields.indices[i]);
- have += file.bytesCompleted;
- size += file.length;
- wanted |= file.wanted;
- switch (file.priority) {
- case -1: low = true; break;
- case 0: normal = true; break;
- case 1: high = true; break;
- }
- }
-
- if ((fields.have != have) || (fields.size != size)) {
- fields.have = have;
- fields.size = size;
- refreshProgressHTML();
- }
-
- if (fields.isWanted !== wanted) {
- fields.isWanted = wanted;
- refreshWantedHTML();
- }
-
- if (fields.priorityLow !== low) {
- fields.priorityLow = low;
- $(elements.priority_low_button).toggleClass('selected', low);
- }
-
- if (fields.priorityNormal !== normal) {
- fields.priorityNormal = normal;
- $(elements.priority_normal_button).toggleClass('selected', normal);
- }
-
- if (fields.priorityHigh !== high) {
- fields.priorityHigh = high;
- $(elements.priority_high_button).toggleClass('selected', high);
- }
- },
-
- isDone = function () {
- return fields.have >= fields.size;
- },
- isEditable = function () {
- return (fields.torrent.getFileCount()>1) && !isDone();
- },
-
- createRow = function(torrent, depth, name, even) {
- var e, root, box;
-
- root = document.createElement('li');
- root.className = 'inspector_torrent_file_list_entry' + (even?'even':'odd');
- elements.root = root;
-
- e = document.createElement('input');
- e.type = 'checkbox';
- e.className = "file_wanted_control";
- e.title = 'Download file';
- $(e).change(function(ev){ fireWantedChanged( $(ev.currentTarget).prop('checked')); });
- root.checkbox = e;
- root.appendChild(e);
-
- e = document.createElement('div');
- e.className = 'file-priority-radiobox';
- box = e;
-
- e = document.createElement('div');
- e.className = 'low';
- e.title = 'Low Priority';
- $(e).click(function(){ firePriorityChanged(-1); });
- elements.priority_low_button = e;
- box.appendChild(e);
-
- e = document.createElement('div');
- e.className = 'normal';
- e.title = 'Normal Priority';
- $(e).click(function(){ firePriorityChanged(0); });
- elements.priority_normal_button = e;
- box.appendChild(e);
-
- e = document.createElement('div');
- e.title = 'High Priority';
- e.className = 'high';
- $(e).click(function(){ firePriorityChanged(1); });
- elements.priority_high_button = e;
- box.appendChild(e);
-
- root.appendChild(box);
-
- e = document.createElement('div');
- e.className = "inspector_torrent_file_list_entry_name";
- setTextContent(e, name);
- $(e).click(function(){ fireNameClicked(-1); });
- root.appendChild(e);
-
- e = document.createElement('div');
- e.className = "inspector_torrent_file_list_entry_progress";
- root.appendChild(e);
- $(e).click(function(){ fireNameClicked(-1); });
- elements.progress = e;
-
- $(root).css('margin-left', '' + (depth*16) + 'px');
-
- refreshImpl();
- return root;
- },
-
- fireWantedChanged = function(do_want) {
- $(fields.me).trigger('wantedToggled',[ fields.indices, do_want ]);
- },
- firePriorityChanged = function(priority) {
- $(fields.me).trigger('priorityToggled',[ fields.indices, priority ]);
- },
- fireNameClicked = function() {
- $(fields.me).trigger('nameClicked',[ fields.me, fields.indices ]);
- };
-
- /***
- **** PUBLIC
- ***/
-
- this.getElement = function() {
- return elements.root;
- };
- this.refresh = function() {
- refreshImpl();
- };
-
- initialize(torrent, depth, name, indices, even);
+function FileRow(torrent, depth, name, indices, even) {
+ var fields = {
+ have: 0,
+ indices: [],
+ isWanted: true,
+ priorityLow: false,
+ priorityNormal: false,
+ priorityHigh: false,
+ me: this,
+ size: 0,
+ torrent: null
+ };
+
+ var elements = {
+ priority_low_button: null,
+ priority_normal_button: null,
+ priority_high_button: null,
+ progress: null,
+ root: null
+ };
+
+ var initialize = function (torrent, depth, name, indices, even) {
+ fields.torrent = torrent;
+ fields.indices = indices;
+ createRow(torrent, depth, name, even);
+ };
+
+ var refreshWantedHTML = function () {
+ var e = $(elements.root);
+ e.toggleClass('skip', !fields.isWanted);
+ e.toggleClass('complete', isDone());
+ $(e[0].checkbox).prop('disabled', !isEditable());
+ $(e[0].checkbox).prop('checked', fields.isWanted);
+ };
+
+ var refreshProgressHTML = function () {
+ var pct = 100 * (fields.size ? (fields.have / fields.size) : 1.0)
+ var c = [Transmission.fmt.size(fields.have), ' of ', Transmission.fmt.size(fields.size), ' (', Transmission.fmt.percentString(pct), '%)'].join('');
+ setTextContent(elements.progress, c);
+ };
+
+ var refreshImpl = function () {
+ var i,
+ file,
+ have = 0,
+ size = 0,
+ wanted = false,
+ low = false,
+ normal = false,
+ high = false;
+
+ // loop through the file_indices that affect this row
+ for (i = 0; i < fields.indices.length; ++i) {
+ file = fields.torrent.getFile(fields.indices[i]);
+ have += file.bytesCompleted;
+ size += file.length;
+ wanted |= file.wanted;
+ switch (file.priority) {
+ case -1:
+ low = true;
+ break;
+ case 0:
+ normal = true;
+ break;
+ case 1:
+ high = true;
+ break;
+ }
+ }
+
+ if ((fields.have != have) || (fields.size != size)) {
+ fields.have = have;
+ fields.size = size;
+ refreshProgressHTML();
+ }
+
+ if (fields.isWanted !== wanted) {
+ fields.isWanted = wanted;
+ refreshWantedHTML();
+ }
+
+ if (fields.priorityLow !== low) {
+ fields.priorityLow = low;
+ $(elements.priority_low_button).toggleClass('selected', low);
+ }
+
+ if (fields.priorityNormal !== normal) {
+ fields.priorityNormal = normal;
+ $(elements.priority_normal_button).toggleClass('selected', normal);
+ }
+
+ if (fields.priorityHigh !== high) {
+ fields.priorityHigh = high;
+ $(elements.priority_high_button).toggleClass('selected', high);
+ }
+ };
+
+ var isDone = function () {
+ return fields.have >= fields.size;
+ };
+
+ var isEditable = function () {
+ return (fields.torrent.getFileCount() > 1) && !isDone();
+ };
+
+ var createRow = function (torrent, depth, name, even) {
+ var e, root, box;
+
+ root = document.createElement('li');
+ root.className = 'inspector_torrent_file_list_entry' + (even ? 'even' : 'odd');
+ elements.root = root;
+
+ e = document.createElement('input');
+ e.type = 'checkbox';
+ e.className = "file_wanted_control";
+ e.title = 'Download file';
+ $(e).change(function (ev) {
+ fireWantedChanged($(ev.currentTarget).prop('checked'));
+ });
+ root.checkbox = e;
+ root.appendChild(e);
+
+ e = document.createElement('div');
+ e.className = 'file-priority-radiobox';
+ box = e;
+
+ e = document.createElement('div');
+ e.className = 'low';
+ e.title = 'Low Priority';
+ $(e).click(function () {
+ firePriorityChanged(-1);
+ });
+ elements.priority_low_button = e;
+ box.appendChild(e);
+
+ e = document.createElement('div');
+ e.className = 'normal';
+ e.title = 'Normal Priority';
+ $(e).click(function () {
+ firePriorityChanged(0);
+ });
+ elements.priority_normal_button = e;
+ box.appendChild(e);
+
+ e = document.createElement('div');
+ e.title = 'High Priority';
+ e.className = 'high';
+ $(e).click(function () {
+ firePriorityChanged(1);
+ });
+ elements.priority_high_button = e;
+ box.appendChild(e);
+
+ root.appendChild(box);
+
+ e = document.createElement('div');
+ e.className = "inspector_torrent_file_list_entry_name";
+ setTextContent(e, name);
+ $(e).click(function () {
+ fireNameClicked(-1);
+ });
+ root.appendChild(e);
+
+ e = document.createElement('div');
+ e.className = "inspector_torrent_file_list_entry_progress";
+ root.appendChild(e);
+ $(e).click(function () {
+ fireNameClicked(-1);
+ });
+ elements.progress = e;
+
+ $(root).css('margin-left', '' + (depth * 16) + 'px');
+
+ refreshImpl();
+ return root;
+ };
+
+ var fireWantedChanged = function (do_want) {
+ $(fields.me).trigger('wantedToggled', [fields.indices, do_want]);
+ };
+
+ var firePriorityChanged = function (priority) {
+ $(fields.me).trigger('priorityToggled', [fields.indices, priority]);
+ };
+
+ var fireNameClicked = function () {
+ $(fields.me).trigger('nameClicked', [fields.me, fields.indices]);
+ };
+
+ /***
+ **** PUBLIC
+ ***/
+
+ this.getElement = function () {
+ return elements.root;
+ };
+ this.refresh = function () {
+ refreshImpl();
+ };
+
+ initialize(torrent, depth, name, indices, even);
};
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*/
-Transmission.fmt = (function()
-{
- var speed_K = 1000;
- var speed_B_str = 'B/s';
- var speed_K_str = 'kB/s';
- var speed_M_str = 'MB/s';
- var speed_G_str = 'GB/s';
- var speed_T_str = 'TB/s';
-
- var size_K = 1000;
- var size_B_str = 'B';
- var size_K_str = 'kB';
- var size_M_str = 'MB';
- var size_G_str = 'GB';
- var size_T_str = 'TB';
-
- var mem_K = 1024;
- var mem_B_str = 'B';
- var mem_K_str = 'KiB';
- var mem_M_str = 'MiB';
- var mem_G_str = 'GiB';
- var mem_T_str = 'TiB';
-
- return {
-
- updateUnits: function(u)
- {
-/*
- speed_K = u['speed-bytes'];
- speed_K_str = u['speed-units'][0];
- speed_M_str = u['speed-units'][1];
- speed_G_str = u['speed-units'][2];
- speed_T_str = u['speed-units'][3];
-
- size_K = u['size-bytes'];
- size_K_str = u['size-units'][0];
- size_M_str = u['size-units'][1];
- size_G_str = u['size-units'][2];
- size_T_str = u['size-units'][3];
-
- mem_K = u['memory-bytes'];
- mem_K_str = u['memory-units'][0];
- mem_M_str = u['memory-units'][1];
- mem_G_str = u['memory-units'][2];
- mem_T_str = u['memory-units'][3];
-*/
- },
-
- /*
- * Format a percentage to a string
- */
- percentString: function(x) {
- if (x < 10.0)
- return x.toTruncFixed(2);
- else if (x < 100.0)
- return x.toTruncFixed(1);
- else
- return x.toTruncFixed(0);
- },
-
- /*
- * Format a ratio to a string
- */
- ratioString: function(x) {
- if (x === -1)
- return "None";
- if (x === -2)
- return '∞';
- return this.percentString(x);
- },
-
- /**
- * Formats the a memory size into a human-readable string
- * @param {Number} bytes the filesize in bytes
- * @return {String} human-readable string
- */
- mem: function(bytes)
- {
- if (bytes < mem_K)
- return [ bytes, mem_B_str ].join(' ');
-
- var convertedSize;
- var unit;
-
- if (bytes < Math.pow(mem_K, 2))
- {
- convertedSize = bytes / mem_K;
- unit = mem_K_str;
- }
- else if (bytes < Math.pow(mem_K, 3))
- {
- convertedSize = bytes / Math.pow(mem_K, 2);
- unit = mem_M_str;
- }
- else if (bytes < Math.pow(mem_K, 4))
- {
- convertedSize = bytes / Math.pow(mem_K, 3);
- unit = mem_G_str;
- }
- else
- {
- convertedSize = bytes / Math.pow(mem_K, 4);
- unit = mem_T_str;
- }
-
- // try to have at least 3 digits and at least 1 decimal
- return convertedSize <= 9.995 ? [ convertedSize.toTruncFixed(2), unit ].join(' ')
- : [ convertedSize.toTruncFixed(1), unit ].join(' ');
- },
-
- /**
- * Formats the a disk capacity or file size into a human-readable string
- * @param {Number} bytes the filesize in bytes
- * @return {String} human-readable string
- */
- size: function(bytes)
- {
- if (bytes < size_K)
- return [ bytes, size_B_str ].join(' ');
-
- var convertedSize;
- var unit;
-
- if (bytes < Math.pow(size_K, 2))
- {
- convertedSize = bytes / size_K;
- unit = size_K_str;
- }
- else if (bytes < Math.pow(size_K, 3))
- {
- convertedSize = bytes / Math.pow(size_K, 2);
- unit = size_M_str;
- }
- else if (bytes < Math.pow(size_K, 4))
- {
- convertedSize = bytes / Math.pow(size_K, 3);
- unit = size_G_str;
- }
- else
- {
- convertedSize = bytes / Math.pow(size_K, 4);
- unit = size_T_str;
- }
-
- // try to have at least 3 digits and at least 1 decimal
- return convertedSize <= 9.995 ? [ convertedSize.toTruncFixed(2), unit ].join(' ')
- : [ convertedSize.toTruncFixed(1), unit ].join(' ');
- },
-
- speedBps: function(Bps)
- {
- return this.speed(this.toKBps(Bps));
- },
-
- toKBps: function(Bps)
- {
- return Math.floor(Bps / speed_K);
- },
-
- speed: function(KBps)
- {
- var speed = KBps;
-
- if (speed <= 999.95) // 0 KBps to 999 K
- return [ speed.toTruncFixed(0), speed_K_str ].join(' ');
-
- speed /= speed_K;
-
- if (speed <= 99.995) // 1 M to 99.99 M
- return [ speed.toTruncFixed(2), speed_M_str ].join(' ');
- if (speed <= 999.95) // 100 M to 999.9 M
- return [ speed.toTruncFixed(1), speed_M_str ].join(' ');
-
- // insane speeds
- speed /= speed_K;
- return [ speed.toTruncFixed(2), speed_G_str ].join(' ');
- },
-
- timeInterval: function(seconds)
- {
- var days = Math.floor (seconds / 86400),
- hours = Math.floor ((seconds % 86400) / 3600),
- minutes = Math.floor ((seconds % 3600) / 60),
- seconds = Math.floor (seconds % 60),
- d = days + ' ' + (days > 1 ? 'days' : 'day'),
- h = hours + ' ' + (hours > 1 ? 'hours' : 'hour'),
- m = minutes + ' ' + (minutes > 1 ? 'minutes' : 'minute'),
- s = seconds + ' ' + (seconds > 1 ? 'seconds' : 'second');
-
- if (days) {
- if (days >= 4 || !hours)
- return d;
- return d + ', ' + h;
- }
- if (hours) {
- if (hours >= 4 || !minutes)
- return h;
- return h + ', ' + m;
- }
- if (minutes) {
- if (minutes >= 4 || !seconds)
- return m;
- return m + ', ' + s;
- }
- return s;
- },
-
- timestamp: function(seconds)
- {
- if (!seconds)
- return 'N/A';
-
- var myDate = new Date(seconds*1000);
- var now = new Date();
-
- var date = "";
- var time = "";
-
- var sameYear = now.getFullYear() === myDate.getFullYear();
- var sameMonth = now.getMonth() === myDate.getMonth();
-
- var dateDiff = now.getDate() - myDate.getDate();
- if (sameYear && sameMonth && Math.abs(dateDiff) <= 1){
- if (dateDiff === 0){
- date = "Today";
- }
- else if (dateDiff === 1){
- date = "Yesterday";
- }
- else{
- date = "Tomorrow";
- }
- }
- else{
- date = myDate.toDateString();
- }
-
- var hours = myDate.getHours();
- var period = "AM";
- if (hours > 12){
- hours = hours - 12;
- period = "PM";
- }
- if (hours === 0){
- hours = 12;
- }
- if (hours < 10){
- hours = "0" + hours;
- }
- var minutes = myDate.getMinutes();
- if (minutes < 10){
- minutes = "0" + minutes;
- }
- var seconds = myDate.getSeconds();
- if (seconds < 10){
- seconds = "0" + seconds;
- }
-
- time = [hours, minutes, seconds].join(':');
-
- return [date, time, period].join(' ');
- },
-
- ngettext: function(msgid, msgid_plural, n)
- {
- // TODO(i18n): http://doc.qt.digia.com/4.6/i18n-plural-rules.html
- return n === 1 ? msgid : msgid_plural;
- },
-
- countString: function(msgid, msgid_plural, n)
- {
- return [ n.toStringWithCommas(), this.ngettext(msgid,msgid_plural,n) ].join(' ');
- },
-
- peerStatus: function( flagStr )
- {
- var formattedFlags = [];
- for (var i=0, flag; flag=flagStr[i]; ++i)
- {
- var explanation = null;
- switch (flag)
- {
- case "O": explanation = "Optimistic unchoke"; break;
- case "D": explanation = "Downloading from this peer"; break;
- case "d": explanation = "We would download from this peer if they'd let us"; break;
- case "U": explanation = "Uploading to peer"; break;
- case "u": explanation = "We would upload to this peer if they'd ask"; break;
- case "K": explanation = "Peer has unchoked us, but we're not interested"; break;
- case "?": explanation = "We unchoked this peer, but they're not interested"; break;
- case "E": explanation = "Encrypted Connection"; break;
- case "H": explanation = "Peer was discovered through Distributed Hash Table (DHT)"; break;
- case "X": explanation = "Peer was discovered through Peer Exchange (PEX)"; break;
- case "I": explanation = "Peer is an incoming connection"; break;
- case "T": explanation = "Peer is connected via uTP"; break;
- }
-
- if (!explanation) {
- formattedFlags.push(flag);
- } else {
- formattedFlags.push("<span title=\"" + flag + ': ' + explanation + "\">" + flag + "</span>");
- }
- }
- return formattedFlags.join('');
- }
- }
+Transmission.fmt = (function () {
+ var speed_K = 1000;
+ var speed_B_str = 'B/s';
+ var speed_K_str = 'kB/s';
+ var speed_M_str = 'MB/s';
+ var speed_G_str = 'GB/s';
+ var speed_T_str = 'TB/s';
+
+ var size_K = 1000;
+ var size_B_str = 'B';
+ var size_K_str = 'kB';
+ var size_M_str = 'MB';
+ var size_G_str = 'GB';
+ var size_T_str = 'TB';
+
+ var mem_K = 1024;
+ var mem_B_str = 'B';
+ var mem_K_str = 'KiB';
+ var mem_M_str = 'MiB';
+ var mem_G_str = 'GiB';
+ var mem_T_str = 'TiB';
+
+ return {
+
+ /*
+ * Format a percentage to a string
+ */
+ percentString: function (x) {
+ if (x < 10.0) {
+ return x.toTruncFixed(2);
+ } else if (x < 100.0) {
+ return x.toTruncFixed(1);
+ } else {
+ return x.toTruncFixed(0);
+ }
+ },
+
+ /*
+ * Format a ratio to a string
+ */
+ ratioString: function (x) {
+ if (x === -1) {
+ return "None";
+ }
+ if (x === -2) {
+ return '∞';
+ }
+ return this.percentString(x);
+ },
+
+ /**
+ * Formats the a memory size into a human-readable string
+ * @param {Number} bytes the filesize in bytes
+ * @return {String} human-readable string
+ */
+ mem: function (bytes) {
+ if (bytes < mem_K)
+ return [bytes, mem_B_str].join(' ');
+
+ var convertedSize;
+ var unit;
+
+ if (bytes < Math.pow(mem_K, 2)) {
+ convertedSize = bytes / mem_K;
+ unit = mem_K_str;
+ } else if (bytes < Math.pow(mem_K, 3)) {
+ convertedSize = bytes / Math.pow(mem_K, 2);
+ unit = mem_M_str;
+ } else if (bytes < Math.pow(mem_K, 4)) {
+ convertedSize = bytes / Math.pow(mem_K, 3);
+ unit = mem_G_str;
+ } else {
+ convertedSize = bytes / Math.pow(mem_K, 4);
+ unit = mem_T_str;
+ }
+
+ // try to have at least 3 digits and at least 1 decimal
+ return convertedSize <= 9.995 ? [convertedSize.toTruncFixed(2), unit].join(' ') : [convertedSize.toTruncFixed(1), unit].join(' ');
+ },
+
+ /**
+ * Formats the a disk capacity or file size into a human-readable string
+ * @param {Number} bytes the filesize in bytes
+ * @return {String} human-readable string
+ */
+ size: function (bytes) {
+ if (bytes < size_K) {
+ return [bytes, size_B_str].join(' ');
+ }
+
+ var convertedSize;
+ var unit;
+
+ if (bytes < Math.pow(size_K, 2)) {
+ convertedSize = bytes / size_K;
+ unit = size_K_str;
+ } else if (bytes < Math.pow(size_K, 3)) {
+ convertedSize = bytes / Math.pow(size_K, 2);
+ unit = size_M_str;
+ } else if (bytes < Math.pow(size_K, 4)) {
+ convertedSize = bytes / Math.pow(size_K, 3);
+ unit = size_G_str;
+ } else {
+ convertedSize = bytes / Math.pow(size_K, 4);
+ unit = size_T_str;
+ }
+
+ // try to have at least 3 digits and at least 1 decimal
+ return convertedSize <= 9.995 ? [convertedSize.toTruncFixed(2), unit].join(' ') : [convertedSize.toTruncFixed(1), unit].join(' ');
+ },
+
+ speedBps: function (Bps) {
+ return this.speed(this.toKBps(Bps));
+ },
+
+ toKBps: function (Bps) {
+ return Math.floor(Bps / speed_K);
+ },
+
+ speed: function (KBps) {
+ var speed = KBps;
+
+ if (speed <= 999.95) { // 0 KBps to 999 K
+ return [speed.toTruncFixed(0), speed_K_str].join(' ');
+ }
+
+ speed /= speed_K;
+
+ if (speed <= 99.995) { // 1 M to 99.99 M
+ return [speed.toTruncFixed(2), speed_M_str].join(' ');
+ }
+ if (speed <= 999.95) { // 100 M to 999.9 M
+ return [speed.toTruncFixed(1), speed_M_str].join(' ');
+ }
+
+ // insane speeds
+ speed /= speed_K;
+ return [speed.toTruncFixed(2), speed_G_str].join(' ');
+ },
+
+ timeInterval: function (seconds) {
+ var days = Math.floor(seconds / 86400),
+ hours = Math.floor((seconds % 86400) / 3600),
+ minutes = Math.floor((seconds % 3600) / 60),
+ seconds = Math.floor(seconds % 60),
+ d = days + ' ' + (days > 1 ? 'days' : 'day'),
+ h = hours + ' ' + (hours > 1 ? 'hours' : 'hour'),
+ m = minutes + ' ' + (minutes > 1 ? 'minutes' : 'minute'),
+ s = seconds + ' ' + (seconds > 1 ? 'seconds' : 'second');
+
+ if (days) {
+ if (days >= 4 || !hours) {
+ return d;
+ }
+ return d + ', ' + h;
+ }
+ if (hours) {
+ if (hours >= 4 || !minutes) {
+ return h;
+ }
+ return h + ', ' + m;
+ }
+ if (minutes) {
+ if (minutes >= 4 || !seconds) {
+ return m;
+ }
+ return m + ', ' + s;
+ }
+ return s;
+ },
+
+ timestamp: function (seconds) {
+ if (!seconds) {
+ return 'N/A';
+ }
+
+ var myDate = new Date(seconds * 1000);
+ var now = new Date();
+
+ var date = "";
+ var time = "";
+
+ var sameYear = now.getFullYear() === myDate.getFullYear();
+ var sameMonth = now.getMonth() === myDate.getMonth();
+
+ var dateDiff = now.getDate() - myDate.getDate();
+ if (sameYear && sameMonth && Math.abs(dateDiff) <= 1) {
+ if (dateDiff === 0) {
+ date = "Today";
+ } else if (dateDiff === 1) {
+ date = "Yesterday";
+ } else {
+ date = "Tomorrow";
+ }
+ } else {
+ date = myDate.toDateString();
+ }
+
+ var hours = myDate.getHours();
+ var period = "AM";
+ if (hours > 12) {
+ hours = hours - 12;
+ period = "PM";
+ }
+ if (hours === 0) {
+ hours = 12;
+ }
+ if (hours < 10) {
+ hours = "0" + hours;
+ }
+ var minutes = myDate.getMinutes();
+ if (minutes < 10) {
+ minutes = "0" + minutes;
+ }
+ var seconds = myDate.getSeconds();
+ if (seconds < 10) {
+ seconds = "0" + seconds;
+ }
+
+ time = [hours, minutes, seconds].join(':');
+
+ return [date, time, period].join(' ');
+ },
+
+ ngettext: function (msgid, msgid_plural, n) {
+ // TODO(i18n): http://doc.qt.digia.com/4.6/i18n-plural-rules.html
+ return n === 1 ? msgid : msgid_plural;
+ },
+
+ countString: function (msgid, msgid_plural, n) {
+ return [n.toStringWithCommas(), this.ngettext(msgid, msgid_plural, n)].join(' ');
+ },
+
+ peerStatus: function (flagStr) {
+ var formattedFlags = [];
+ for (var i = 0, flag; flag = flagStr[i]; ++i) {
+ var explanation = null;
+ switch (flag) {
+ case "O":
+ explanation = "Optimistic unchoke";
+ break;
+ case "D":
+ explanation = "Downloading from this peer";
+ break;
+ case "d":
+ explanation = "We would download from this peer if they'd let us";
+ break;
+ case "U":
+ explanation = "Uploading to peer";
+ break;
+ case "u":
+ explanation = "We would upload to this peer if they'd ask";
+ break;
+ case "K":
+ explanation = "Peer has unchoked us, but we're not interested";
+ break;
+ case "?":
+ explanation = "We unchoked this peer, but they're not interested";
+ break;
+ case "E":
+ explanation = "Encrypted Connection";
+ break;
+ case "H":
+ explanation = "Peer was discovered through Distributed Hash Table (DHT)";
+ break;
+ case "X":
+ explanation = "Peer was discovered through Peer Exchange (PEX)";
+ break;
+ case "I":
+ explanation = "Peer is an incoming connection";
+ break;
+ case "T":
+ explanation = "Peer is connected via uTP";
+ break;
+ };
+
+ if (!explanation) {
+ formattedFlags.push(flag);
+ } else {
+ formattedFlags.push("<span title=\"" + flag + ': ' + explanation + "\">" + flag + "</span>");
+ };
+ };
+
+ return formattedFlags.join('');
+ }
+ }
})();
function Inspector(controller) {
- var data = {
- controller: null,
- elements: { },
- torrents: [ ]
- },
-
- needsExtraInfo = function (torrents) {
- var i, id, tor;
-
- for (i = 0; tor = torrents[i]; i++)
- if (!tor.hasExtraInfo())
- return true;
-
- return false;
- },
-
- refreshTorrents = function () {
- var fields,
- ids = $.map(data.torrents.slice(0), function (t) {return t.getId();});
-
- if (ids && ids.length)
- {
- fields = ['id'].concat(Torrent.Fields.StatsExtra);
-
- if (needsExtraInfo(data.torrents))
- $.merge(fields, Torrent.Fields.InfoExtra);
-
- data.controller.updateTorrents(ids, fields);
- }
- },
-
- onTabClicked = function (ev) {
- var tab = ev.currentTarget;
-
- if (isMobileDevice)
- ev.stopPropagation();
-
- // select this tab and deselect the others
- $(tab).addClass('selected').siblings().removeClass('selected');
-
- // show this tab and hide the others
- $('#' + tab.id.replace('tab','page')).show().siblings('.inspector-page').hide();
-
- updateInspector();
- },
-
- updateInspector = function () {
- var e = data.elements,
- torrents = data.torrents,
- name;
-
- // update the name, which is shown on all the pages
- if (!torrents || !torrents.length)
- name = 'No Selection';
- else if(torrents.length === 1)
- name = torrents[0].getName();
- else
- name = '' + torrents.length+' Transfers Selected';
- setTextContent(e.name_lb, name || na);
-
- // update the visible page
- if ($(e.info_page).is(':visible'))
- updateInfoPage();
- else if ($(e.peers_page).is(':visible'))
- updatePeersPage();
- else if ($(e.trackers_page).is(':visible'))
- updateTrackersPage();
- else if ($(e.files_page).is(':visible'))
- updateFilesPage();
- },
-
- /****
- ***** GENERAL INFO PAGE
- ****/
-
- updateInfoPage = function () {
- var torrents = data.torrents,
- e = data.elements,
- fmt = Transmission.fmt,
- none = 'None',
- mixed = 'Mixed',
- unknown = 'Unknown',
- isMixed, allPaused, allFinished,
- str,
- baseline, it, s, i, t,
- sizeWhenDone = 0,
- leftUntilDone = 0,
- available = 0,
- haveVerified = 0,
- haveUnverified = 0,
- verifiedPieces = 0,
- stateString,
- latest,
- pieces,
- size,
- pieceSize,
- creator, mixed_creator,
- date, mixed_date,
- v, u, f, d, pct,
- uri,
- now = Date.now();
-
- //
- // state_lb
- //
-
- if(torrents.length <1)
- str = none;
- else {
- isMixed = false;
- allPaused = true;
- allFinished = true;
-
- baseline = torrents[0].getStatus();
- for(i=0; t=torrents[i]; ++i) {
- it = t.getStatus();
- if(it != baseline)
- isMixed = true;
- if(!t.isStopped())
- allPaused = allFinished = false;
- if(!t.isFinished())
- allFinished = false;
- }
- if( isMixed )
- str = mixed;
- else if( allFinished )
- str = 'Finished';
- else if( allPaused )
- str = 'Paused';
- else
- str = torrents[0].getStateString();
- }
- setTextContent(e.state_lb, str);
- stateString = str;
-
- //
- // have_lb
- //
-
- if(torrents.length < 1)
- str = none;
- else {
- baseline = torrents[0].getStatus();
- for(i=0; t=torrents[i]; ++i) {
- if(!t.needsMetaData()) {
- haveUnverified += t.getHaveUnchecked();
- v = t.getHaveValid();
- haveVerified += v;
- if(t.getPieceSize())
- verifiedPieces += v / t.getPieceSize();
- sizeWhenDone += t.getSizeWhenDone();
- leftUntilDone += t.getLeftUntilDone();
- available += (t.getHave()) + t.getDesiredAvailable();
- }
- }
-
- d = 100.0 * ( sizeWhenDone ? ( sizeWhenDone - leftUntilDone ) / sizeWhenDone : 1 );
- str = fmt.percentString( d );
-
- if( !haveUnverified && !leftUntilDone )
- str = fmt.size(haveVerified) + ' (100%)';
- else if( !haveUnverified )
- str = fmt.size(haveVerified) + ' of ' + fmt.size(sizeWhenDone) + ' (' + str +'%)';
- else
- str = fmt.size(haveVerified) + ' of ' + fmt.size(sizeWhenDone) + ' (' + str +'%), ' + fmt.size(haveUnverified) + ' Unverified';
- }
- setTextContent(e.have_lb, str);
-
- //
- // availability_lb
- //
-
- if(torrents.length < 1)
- str = none;
- else if( sizeWhenDone == 0 )
- str = none;
- else
- str = '' + fmt.percentString( ( 100.0 * available ) / sizeWhenDone ) + '%';
- setTextContent(e.availability_lb, str);
-
- //
- // downloaded_lb
- //
-
- if(torrents.length < 1)
- str = none;
- else {
- d = f = 0;
- for(i=0; t=torrents[i]; ++i) {
- d += t.getDownloadedEver();
- f += t.getFailedEver();
- }
- if(f)
- str = fmt.size(d) + ' (' + fmt.size(f) + ' corrupt)';
- else
- str = fmt.size(d);
- }
- setTextContent(e.downloaded_lb, str);
-
- //
- // uploaded_lb
- //
-
- if(torrents.length < 1)
- str = none;
- else {
- d = u = 0;
- if(torrents.length == 1) {
- d = torrents[0].getDownloadedEver();
- u = torrents[0].getUploadedEver();
-
- if (d == 0)
- d = torrents[0].getHaveValid();
- }
- else {
- for(i=0; t=torrents[i]; ++i) {
- d += t.getDownloadedEver();
- u += t.getUploadedEver();
- }
- }
- str = fmt.size(u) + ' (Ratio: ' + fmt.ratioString( Math.ratio(u,d))+')';
- }
- setTextContent(e.uploaded_lb, str);
-
- //
- // running time
- //
-
- if(torrents.length < 1)
- str = none;
- else {
- allPaused = true;
- baseline = torrents[0].getStartDate();
- for(i=0; t=torrents[i]; ++i) {
- if(baseline != t.getStartDate())
- baseline = 0;
- if(!t.isStopped())
- allPaused = false;
- }
- if(allPaused)
- str = stateString; // paused || finished
- else if(!baseline)
- str = mixed;
- else
- str = fmt.timeInterval(now/1000 - baseline);
- }
- setTextContent(e.running_time_lb, str);
-
- //
- // remaining time
- //
-
- str = '';
- if(torrents.length < 1)
- str = none;
- else {
- baseline = torrents[0].getETA();
- for(i=0; t=torrents[i]; ++i) {
- if(baseline != t.getETA()) {
- str = mixed;
- break;
- }
- }
- }
- if(!str.length) {
- if(baseline < 0)
- str = unknown;
- else
- str = fmt.timeInterval(baseline);
- }
- setTextContent(e.remaining_time_lb, str);
-
- //
- // last activity
- //
-
- latest = -1;
- if(torrents.length < 1)
- str = none;
- else {
- baseline = torrents[0].getLastActivity();
- for(i=0; t=torrents[i]; ++i) {
- d = t.getLastActivity();
- if(latest < d)
- latest = d;
- }
- d = now/1000 - latest; // seconds since last activity
- if(d < 0)
- str = none;
- else if(d < 5)
- str = 'Active now';
- else
- str = fmt.timeInterval(d) + ' ago';
- }
- setTextContent(e.last_activity_lb, str);
-
- //
- // error
- //
-
- if(torrents.length < 1)
- str = none;
- else {
- str = torrents[0].getErrorString();
- for(i=0; t=torrents[i]; ++i) {
- if(str != t.getErrorString()) {
- str = mixed;
- break;
- }
- }
- }
- setTextContent(e.error_lb, str || none);
-
- //
- // size
- //
-
- if(torrents.length < 1)
- str = none;
- else {
- pieces = 0;
- size = 0;
- pieceSize = torrents[0].getPieceSize();
- for(i=0; t=torrents[i]; ++i) {
- pieces += t.getPieceCount();
- size += t.getTotalSize();
- if(pieceSize != t.getPieceSize())
- pieceSize = 0;
- }
- if(!size)
- str = none;
- else if(pieceSize > 0)
- str = fmt.size(size) + ' (' + pieces.toStringWithCommas() + ' pieces @ ' + fmt.mem(pieceSize) + ')';
- else
- str = fmt.size(size) + ' (' + pieces.toStringWithCommas() + ' pieces)';
- }
- setTextContent(e.size_lb, str);
-
- //
- // hash
- //
-
- if(torrents.length < 1)
- str = none;
- else {
- str = torrents[0].getHashString();
- for(i=0; t=torrents[i]; ++i) {
- if(str != t.getHashString()) {
- str = mixed;
- break;
- }
- }
- }
- setTextContent(e.hash_lb, str);
-
- //
- // privacy
- //
-
- if(torrents.length < 1)
- str = none;
- else {
- baseline = torrents[0].getPrivateFlag();
- str = baseline ? 'Private to this tracker -- DHT and PEX disabled' : 'Public torrent';
- for(i=0; t=torrents[i]; ++i) {
- if(baseline != t.getPrivateFlag()) {
- str = mixed;
- break;
- }
- }
- }
- setTextContent(e.privacy_lb, str);
-
- //
- // comment
- //
-
- if(torrents.length < 1)
- str = none;
- else {
- str = torrents[0].getComment();
- for(i=0; t=torrents[i]; ++i) {
- if(str != t.getComment()) {
- str = mixed;
- break;
- }
- }
- }
- if(!str)
- str = none;
- uri = parseUri(str);
- if (uri.protocol == 'http' || uri.parseUri == 'https') {
- str = encodeURI(str);
- setInnerHTML(e.comment_lb, '<a href="' + str + '" target="_blank" >' + str + '</a>');
- }
- else
- setTextContent(e.comment_lb, str);
-
- //
- // origin
- //
-
- if(torrents.length < 1)
- str = none;
- else {
- mixed_creator = false;
- mixed_date = false;
- creator = torrents[0].getCreator();
- date = torrents[0].getDateCreated();
- for(i=0; t=torrents[i]; ++i) {
- if(creator != t.getCreator())
- mixed_creator = true;
- if(date != t.getDateCreated())
- mixed_date = true;
- }
- var empty_creator = !creator || !creator.length,
- empty_date = !date;
- if(mixed_creator || mixed_date)
- str = mixed;
- else if(empty_creator && empty_date)
- str = unknown;
- else if(empty_date && !empty_creator)
- str = 'Created by ' + creator;
- else if(empty_creator && !empty_date)
- str = 'Created on ' + (new Date(date*1000)).toDateString();
- else
- str = 'Created by ' + creator + ' on ' + (new Date(date*1000)).toDateString();
- }
- setTextContent(e.origin_lb, str);
-
- //
- // foldername
- //
-
- if(torrents.length < 1)
- str = none;
- else {
- str = torrents[0].getDownloadDir();
- for(i=0; t=torrents[i]; ++i) {
- if(str != t.getDownloadDir()) {
- str = mixed;
- break;
- }
- }
- }
- setTextContent(e.foldername_lb, str);
- },
-
- /****
- ***** FILES PAGE
- ****/
-
- changeFileCommand = function(fileIndices, command) {
- var torrentId = data.file_torrent.getId();
- data.controller.changeFileCommand(torrentId, fileIndices, command);
- },
-
- onFileWantedToggled = function(ev, fileIndices, want) {
- changeFileCommand(fileIndices, want?'files-wanted':'files-unwanted');
- },
-
- onFilePriorityToggled = function(ev, fileIndices, priority) {
- var command;
- switch(priority) {
- case -1: command = 'priority-low'; break;
- case 1: command = 'priority-high'; break;
- default: command = 'priority-normal'; break;
- }
- changeFileCommand(fileIndices, command);
- },
-
- onNameClicked = function(ev, fileRow, fileIndices) {
- $(fileRow.getElement()).siblings().slideToggle();
- },
-
- clearFileList = function() {
- $(data.elements.file_list).empty();
- delete data.file_torrent;
- delete data.file_torrent_n;
- delete data.file_rows;
- },
-
- createFileTreeModel = function (tor) {
- var i, j, n, name, tokens, walk, tree, token, sub,
- leaves = [ ],
- tree = { children: { }, file_indices: [ ] };
-
- n = tor.getFileCount();
- for (i=0; i<n; ++i) {
- name = tor.getFile(i).name;
- tokens = name.split('/');
- walk = tree;
- for (j=0; j<tokens.length; ++j) {
- token = tokens[j];
- sub = walk.children[token];
- if (!sub) {
- walk.children[token] = sub = {
- name: token,
- parent: walk,
- children: { },
- file_indices: [ ],
- depth: j
- };
- }
- walk = sub;
- }
- walk.file_index = i;
- delete walk.children;
- leaves.push (walk);
- }
-
- for (i=0; i<leaves.length; ++i) {
- walk = leaves[i];
- j = walk.file_index;
- do {
- walk.file_indices.push (j);
- walk = walk.parent;
- } while (walk);
- }
-
- return tree;
- },
-
- addNodeToView = function (tor, parent, sub, i) {
- var row;
- row = new FileRow(tor, sub.depth, sub.name, sub.file_indices, i%2);
- data.file_rows.push(row);
- parent.appendChild(row.getElement());
- $(row).bind('wantedToggled',onFileWantedToggled);
- $(row).bind('priorityToggled',onFilePriorityToggled);
- $(row).bind('nameClicked',onNameClicked);
- },
-
- addSubtreeToView = function (tor, parent, sub, i) {
- var key, div;
- div = document.createElement('div');
- if (sub.parent)
- addNodeToView (tor, div, sub, i++);
- if (sub.children)
- for (key in sub.children)
- i = addSubtreeToView (tor, div, sub.children[key]);
- parent.appendChild(div);
- return i;
- },
-
- updateFilesPage = function() {
- var i, n, tor, fragment, tree,
- file_list = data.elements.file_list,
- torrents = data.torrents;
-
- // only show one torrent at a time
- if (torrents.length !== 1) {
- clearFileList();
- return;
- }
-
- tor = torrents[0];
- n = tor ? tor.getFileCount() : 0;
- if (tor!=data.file_torrent || n!=data.file_torrent_n) {
- // rebuild the file list...
- clearFileList();
- data.file_torrent = tor;
- data.file_torrent_n = n;
- data.file_rows = [ ];
- fragment = document.createDocumentFragment();
- tree = createFileTreeModel (tor);
- addSubtreeToView (tor, fragment, tree, 0);
- file_list.appendChild (fragment);
- } else {
- // ...refresh the already-existing file list
- for (i=0, n=data.file_rows.length; i<n; ++i)
- data.file_rows[i].refresh();
- }
- },
-
- /****
- ***** PEERS PAGE
- ****/
-
- updatePeersPage = function() {
- var i, k, tor, peers, peer, parity,
- html = [],
- fmt = Transmission.fmt,
- peers_list = data.elements.peers_list,
- torrents = data.torrents;
-
- for (k=0; tor=torrents[k]; ++k)
- {
- peers = tor.getPeers();
- html.push('<div class="inspector_group">');
- if (torrents.length > 1) {
- html.push('<div class="inspector_torrent_label">', sanitizeText(tor.getName()), '</div>');
- }
- if (!peers || !peers.length) {
- html.push('<br></div>'); // firefox won't paint the top border if the div is empty
- continue;
- }
- html.push('<table class="peer_list">',
- '<tr class="inspector_peer_entry even">',
- '<th class="encryptedCol"></th>',
- '<th class="upCol">Up</th>',
- '<th class="downCol">Down</th>',
- '<th class="percentCol">%</th>',
- '<th class="statusCol">Status</th>',
- '<th class="addressCol">Address</th>',
- '<th class="clientCol">Client</th>',
- '</tr>');
- for (i=0; peer=peers[i]; ++i) {
- parity = (i%2) ? 'odd' : 'even';
- html.push('<tr class="inspector_peer_entry ', parity, '">',
- '<td>', (peer.isEncrypted ? '<div class="encrypted-peer-cell" title="Encrypted Connection">'
- : '<div class="unencrypted-peer-cell">'), '</div>', '</td>',
- '<td>', (peer.rateToPeer ? fmt.speedBps(peer.rateToPeer) : ''), '</td>',
- '<td>', (peer.rateToClient ? fmt.speedBps(peer.rateToClient) : ''), '</td>',
- '<td class="percentCol">', Math.floor(peer.progress*100), '%', '</td>',
- '<td>', fmt.peerStatus(peer.flagStr), '</td>',
- '<td>', sanitizeText(peer.address), '</td>',
- '<td class="clientCol">', sanitizeText(peer.clientName), '</td>',
- '</tr>');
- }
- html.push('</table></div>');
- }
-
- setInnerHTML(peers_list, html.join(''));
- },
-
- /****
- ***** TRACKERS PAGE
- ****/
-
- getAnnounceState = function(tracker) {
- var timeUntilAnnounce, s = '';
- switch (tracker.announceState) {
- case Torrent._TrackerActive:
- s = 'Announce in progress';
- break;
- case Torrent._TrackerWaiting:
- timeUntilAnnounce = tracker.nextAnnounceTime - ((new Date()).getTime() / 1000);
- if (timeUntilAnnounce < 0) {
- timeUntilAnnounce = 0;
- }
- s = 'Next announce in ' + Transmission.fmt.timeInterval(timeUntilAnnounce);
- break;
- case Torrent._TrackerQueued:
- s = 'Announce is queued';
- break;
- case Torrent._TrackerInactive:
- s = tracker.isBackup ?
- 'Tracker will be used as a backup' :
- 'Announce not scheduled';
- break;
- default:
- s = 'unknown announce state: ' + tracker.announceState;
- }
- return s;
- },
-
- lastAnnounceStatus = function(tracker) {
-
- var lastAnnounceLabel = 'Last Announce',
- lastAnnounce = [ 'N/A' ],
- lastAnnounceTime;
-
- if (tracker.hasAnnounced) {
- lastAnnounceTime = Transmission.fmt.timestamp(tracker.lastAnnounceTime);
- if (tracker.lastAnnounceSucceeded) {
- lastAnnounce = [ lastAnnounceTime, ' (got ', Transmission.fmt.countString('peer','peers',tracker.lastAnnouncePeerCount), ')' ];
- } else {
- lastAnnounceLabel = 'Announce error';
- lastAnnounce = [ (tracker.lastAnnounceResult ? (tracker.lastAnnounceResult + ' - ') : ''), lastAnnounceTime ];
- }
- }
- return { 'label':lastAnnounceLabel, 'value':lastAnnounce.join('') };
- },
-
- lastScrapeStatus = function(tracker) {
-
- var lastScrapeLabel = 'Last Scrape',
- lastScrape = 'N/A',
- lastScrapeTime;
-
- if (tracker.hasScraped) {
- lastScrapeTime = Transmission.fmt.timestamp(tracker.lastScrapeTime);
- if (tracker.lastScrapeSucceeded) {
- lastScrape = lastScrapeTime;
- } else {
- lastScrapeLabel = 'Scrape error';
- lastScrape = (tracker.lastScrapeResult ? tracker.lastScrapeResult + ' - ' : '') + lastScrapeTime;
- }
- }
- return {'label':lastScrapeLabel, 'value':lastScrape};
- },
-
- updateTrackersPage = function() {
- var i, j, tier, tracker, trackers, tor,
- html, parity, lastAnnounceStatusHash,
- announceState, lastScrapeStatusHash,
- na = 'N/A',
- trackers_list = data.elements.trackers_list,
- torrents = data.torrents;
-
- // By building up the HTML as as string, then have the browser
- // turn this into a DOM tree, this is a fast operation.
- html = [];
- for (i=0; tor=torrents[i]; ++i)
- {
- html.push ('<div class="inspector_group">');
-
- if (torrents.length > 1)
- html.push('<div class="inspector_torrent_label">', tor.getName(), '</div>');
-
- tier = -1;
- trackers = tor.getTrackers();
- for (j=0; tracker=trackers[j]; ++j)
- {
- if (tier != tracker.tier)
- {
- if (tier !== -1) // close previous tier
- html.push('</ul></div>');
-
- tier = tracker.tier;
-
- html.push('<div class="inspector_group_label">',
- 'Tier ', tier+1, '</div>',
- '<ul class="tier_list">');
- }
-
- // Display construction
- lastAnnounceStatusHash = lastAnnounceStatus(tracker);
- announceState = getAnnounceState(tracker);
- lastScrapeStatusHash = lastScrapeStatus(tracker);
- parity = (j%2) ? 'odd' : 'even';
- html.push('<li class="inspector_tracker_entry ', parity, '"><div class="tracker_host" title="', sanitizeText(tracker.announce), '">',
- sanitizeText(tracker.host || tracker.announce), '</div>',
- '<div class="tracker_activity">',
- '<div>', lastAnnounceStatusHash['label'], ': ', lastAnnounceStatusHash['value'], '</div>',
- '<div>', announceState, '</div>',
- '<div>', lastScrapeStatusHash['label'], ': ', lastScrapeStatusHash['value'], '</div>',
- '</div><table class="tracker_stats">',
- '<tr><th>Seeders:</th><td>', (tracker.seederCount > -1 ? tracker.seederCount : na), '</td></tr>',
- '<tr><th>Leechers:</th><td>', (tracker.leecherCount > -1 ? tracker.leecherCount : na), '</td></tr>',
- '<tr><th>Downloads:</th><td>', (tracker.downloadCount > -1 ? tracker.downloadCount : na), '</td></tr>',
- '</table></li>');
- }
- if (tier !== -1) // close last tier
- html.push('</ul></div>');
-
- html.push('</div>'); // inspector_group
- }
-
- setInnerHTML (trackers_list, html.join(''));
- },
-
- initialize = function (controller) {
-
- var ti = '#torrent_inspector_';
-
- data.controller = controller;
-
- $('.inspector-tab').click(onTabClicked);
-
- data.elements.info_page = $('#inspector-page-info')[0];
- data.elements.files_page = $('#inspector-page-files')[0];
- data.elements.peers_page = $('#inspector-page-peers')[0];
- data.elements.trackers_page = $('#inspector-page-trackers')[0];
-
- data.elements.file_list = $('#inspector_file_list')[0];
- data.elements.peers_list = $('#inspector_peers_list')[0];
- data.elements.trackers_list = $('#inspector_trackers_list')[0];
-
- data.elements.have_lb = $('#inspector-info-have')[0];
- data.elements.availability_lb = $('#inspector-info-availability')[0];
- data.elements.downloaded_lb = $('#inspector-info-downloaded')[0];
- data.elements.uploaded_lb = $('#inspector-info-uploaded')[0];
- data.elements.state_lb = $('#inspector-info-state')[0];
- data.elements.running_time_lb = $('#inspector-info-running-time')[0];
- data.elements.remaining_time_lb = $('#inspector-info-remaining-time')[0];
- data.elements.last_activity_lb = $('#inspector-info-last-activity')[0];
- data.elements.error_lb = $('#inspector-info-error')[0];
- data.elements.size_lb = $('#inspector-info-size')[0];
- data.elements.foldername_lb = $('#inspector-info-location')[0];
- data.elements.hash_lb = $('#inspector-info-hash')[0];
- data.elements.privacy_lb = $('#inspector-info-privacy')[0];
- data.elements.origin_lb = $('#inspector-info-origin')[0];
- data.elements.comment_lb = $('#inspector-info-comment')[0];
- data.elements.name_lb = $('#torrent_inspector_name')[0];
-
- // force initial 'N/A' updates on all the pages
- updateInspector();
- updateInfoPage();
- updatePeersPage();
- updateTrackersPage();
- updateFilesPage();
- };
-
- /****
- ***** PUBLIC FUNCTIONS
- ****/
-
- this.setTorrents = function (torrents) {
- var d = data;
-
- // update the inspector when a selected torrent's data changes.
- $(d.torrents).unbind('dataChanged.inspector');
- $(torrents).bind('dataChanged.inspector', $.proxy(updateInspector,this));
- d.torrents = torrents;
-
- // periodically ask for updates to the inspector's torrents
- clearInterval(d.refreshInterval);
- d.refreshInterval = setInterval($.proxy(refreshTorrents,this), 2000);
- refreshTorrents();
-
- // refresh the inspector's UI
- updateInspector();
- };
-
- initialize (controller);
+ var data = {
+ controller: null,
+ elements: {},
+ torrents: []
+ },
+
+ needsExtraInfo = function (torrents) {
+ var i, id, tor;
+
+ for (i = 0; tor = torrents[i]; i++)
+ if (!tor.hasExtraInfo())
+ return true;
+
+ return false;
+ },
+
+ refreshTorrents = function () {
+ var fields,
+ ids = $.map(data.torrents.slice(0), function (t) {
+ return t.getId();
+ });
+
+ if (ids && ids.length) {
+ fields = ['id'].concat(Torrent.Fields.StatsExtra);
+
+ if (needsExtraInfo(data.torrents)) {
+ $.merge(fields, Torrent.Fields.InfoExtra);
+ }
+
+ data.controller.updateTorrents(ids, fields);
+ }
+ },
+
+ onTabClicked = function (ev) {
+ var tab = ev.currentTarget;
+
+ if (isMobileDevice) {
+ ev.stopPropagation();
+ }
+
+ // select this tab and deselect the others
+ $(tab).addClass('selected').siblings().removeClass('selected');
+
+ // show this tab and hide the others
+ $('#' + tab.id.replace('tab', 'page')).show().siblings('.inspector-page').hide();
+
+ updateInspector();
+ },
+
+ updateInspector = function () {
+ var e = data.elements,
+ torrents = data.torrents,
+ name;
+
+ // update the name, which is shown on all the pages
+ if (!torrents || !torrents.length) {
+ name = 'No Selection';
+ } else if (torrents.length === 1) {
+ name = torrents[0].getName();
+ } else {
+ name = '' + torrents.length + ' Transfers Selected';
+ }
+ setTextContent(e.name_lb, name || na);
+
+ // update the visible page
+ if ($(e.info_page).is(':visible')) {
+ updateInfoPage();
+ } else if ($(e.peers_page).is(':visible')) {
+ updatePeersPage();
+ } else if ($(e.trackers_page).is(':visible')) {
+ updateTrackersPage();
+ } else if ($(e.files_page).is(':visible')) {
+ updateFilesPage();
+ }
+ },
+
+ /****
+ ***** GENERAL INFO PAGE
+ ****/
+
+ updateInfoPage = function () {
+ var torrents = data.torrents,
+ e = data.elements,
+ fmt = Transmission.fmt,
+ none = 'None',
+ mixed = 'Mixed',
+ unknown = 'Unknown',
+ isMixed, allPaused, allFinished,
+ str,
+ baseline, it, s, i, t,
+ sizeWhenDone = 0,
+ leftUntilDone = 0,
+ available = 0,
+ haveVerified = 0,
+ haveUnverified = 0,
+ verifiedPieces = 0,
+ stateString,
+ latest,
+ pieces,
+ size,
+ pieceSize,
+ creator, mixed_creator,
+ date, mixed_date,
+ v, u, f, d, pct,
+ uri,
+ now = Date.now();
+
+ //
+ // state_lb
+ //
+
+ if (torrents.length < 1) {
+ str = none;
+ } else {
+ isMixed = false;
+ allPaused = true;
+ allFinished = true;
+
+ baseline = torrents[0].getStatus();
+ for (i = 0; t = torrents[i]; ++i) {
+ it = t.getStatus();
+ if (it != baseline) {
+ isMixed = true;
+ }
+ if (!t.isStopped()) {
+ allPaused = allFinished = false;
+ }
+ if (!t.isFinished()) {
+ allFinished = false;
+ }
+ }
+ if (isMixed) {
+ str = mixed;
+ } else if (allFinished) {
+ str = 'Finished';
+ } else if (allPaused) {
+ str = 'Paused';
+ } else {
+ str = torrents[0].getStateString();
+ }
+ }
+ setTextContent(e.state_lb, str);
+ stateString = str;
+
+ //
+ // have_lb
+ //
+
+ if (torrents.length < 1)
+ str = none;
+ else {
+ baseline = torrents[0].getStatus();
+ for (i = 0; t = torrents[i]; ++i) {
+ if (!t.needsMetaData()) {
+ haveUnverified += t.getHaveUnchecked();
+ v = t.getHaveValid();
+ haveVerified += v;
+ if (t.getPieceSize()) {
+ verifiedPieces += v / t.getPieceSize();
+ }
+ sizeWhenDone += t.getSizeWhenDone();
+ leftUntilDone += t.getLeftUntilDone();
+ available += (t.getHave()) + t.getDesiredAvailable();
+ }
+ }
+
+ d = 100.0 * (sizeWhenDone ? (sizeWhenDone - leftUntilDone) / sizeWhenDone : 1);
+ str = fmt.percentString(d);
+
+ if (!haveUnverified && !leftUntilDone) {
+ str = fmt.size(haveVerified) + ' (100%)';
+ } else if (!haveUnverified) {
+ str = fmt.size(haveVerified) + ' of ' + fmt.size(sizeWhenDone) + ' (' + str + '%)';
+ } else {
+ str = fmt.size(haveVerified) + ' of ' + fmt.size(sizeWhenDone) + ' (' + str + '%), ' + fmt.size(haveUnverified) + ' Unverified';
+ }
+ }
+ setTextContent(e.have_lb, str);
+
+ //
+ // availability_lb
+ //
+
+ if (torrents.length < 1) {
+ str = none;
+ } else if (sizeWhenDone == 0) {
+ str = none;
+ } else {
+ str = '' + fmt.percentString((100.0 * available) / sizeWhenDone) + '%';
+ };
+ setTextContent(e.availability_lb, str);
+
+ //
+ // downloaded_lb
+ //
+
+ if (torrents.length < 1) {
+ str = none;
+ } else {
+ d = f = 0;
+ for (i = 0; t = torrents[i]; ++i) {
+ d += t.getDownloadedEver();
+ f += t.getFailedEver();
+ };
+ if (f) {
+ str = fmt.size(d) + ' (' + fmt.size(f) + ' corrupt)';
+ } else {
+ str = fmt.size(d);
+ };
+ };
+ setTextContent(e.downloaded_lb, str);
+
+ //
+ // uploaded_lb
+ //
+
+ if (torrents.length < 1) {
+ str = none;
+ } else {
+ d = u = 0;
+ if (torrents.length == 1) {
+ d = torrents[0].getDownloadedEver();
+ u = torrents[0].getUploadedEver();
+
+ if (d == 0) {
+ d = torrents[0].getHaveValid();
+ };
+ } else {
+ for (i = 0; t = torrents[i]; ++i) {
+ d += t.getDownloadedEver();
+ u += t.getUploadedEver();
+ };
+ };
+ str = fmt.size(u) + ' (Ratio: ' + fmt.ratioString(Math.ratio(u, d)) + ')';
+ };
+ setTextContent(e.uploaded_lb, str);
+
+ //
+ // running time
+ //
+
+ if (torrents.length < 1) {
+ str = none;
+ } else {
+ allPaused = true;
+ baseline = torrents[0].getStartDate();
+ for (i = 0; t = torrents[i]; ++i) {
+ if (baseline != t.getStartDate()) {
+ baseline = 0;
+ }
+ if (!t.isStopped()) {
+ allPaused = false;
+ }
+ }
+ if (allPaused) {
+ str = stateString; // paused || finished}
+ } else if (!baseline) {
+ str = mixed;
+ } else {
+ str = fmt.timeInterval(now / 1000 - baseline);
+ }
+ };
+
+ setTextContent(e.running_time_lb, str);
+
+ //
+ // remaining time
+ //
+
+ str = '';
+ if (torrents.length < 1) {
+ str = none;
+ } else {
+ baseline = torrents[0].getETA();
+ for (i = 0; t = torrents[i]; ++i) {
+ if (baseline != t.getETA()) {
+ str = mixed;
+ break;
+ }
+ }
+ }
+ if (!str.length) {
+ if (baseline < 0) {
+ str = unknown;
+ } else {
+ str = fmt.timeInterval(baseline);
+ }
+ }
+ setTextContent(e.remaining_time_lb, str);
+
+ //
+ // last activity
+ //
+
+ latest = -1;
+ if (torrents.length < 1) {
+ str = none;
+ } else {
+ baseline = torrents[0].getLastActivity();
+ for (i = 0; t = torrents[i]; ++i) {
+ d = t.getLastActivity();
+ if (latest < d) {
+ latest = d;
+ };
+ };
+ d = now / 1000 - latest; // seconds since last activity
+ if (d < 0) {
+ str = none;
+ } else if (d < 5) {
+ str = 'Active now';
+ } else {
+ str = fmt.timeInterval(d) + ' ago';
+ };
+ };
+ setTextContent(e.last_activity_lb, str);
+
+ //
+ // error
+ //
+
+ if (torrents.length < 1) {
+ str = none;
+ } else {
+ str = torrents[0].getErrorString();
+ for (i = 0; t = torrents[i]; ++i) {
+ if (str != t.getErrorString()) {
+ str = mixed;
+ break;
+ };
+ };
+ };
+ setTextContent(e.error_lb, str || none);
+
+ //
+ // size
+ //
+
+ if (torrents.length < 1) {
+ {
+ str = none;
+ };
+ } else {
+ pieces = 0;
+ size = 0;
+ pieceSize = torrents[0].getPieceSize();
+ for (i = 0; t = torrents[i]; ++i) {
+ pieces += t.getPieceCount();
+ size += t.getTotalSize();
+ if (pieceSize != t.getPieceSize()) {
+ pieceSize = 0;
+ }
+ };
+ if (!size) {
+ str = none;
+ } else if (pieceSize > 0) {
+ str = fmt.size(size) + ' (' + pieces.toStringWithCommas() + ' pieces @ ' + fmt.mem(pieceSize) + ')';
+ } else {
+ str = fmt.size(size) + ' (' + pieces.toStringWithCommas() + ' pieces)';
+ };
+ };
+ setTextContent(e.size_lb, str);
+
+ //
+ // hash
+ //
+
+ if (torrents.length < 1) {
+ str = none;
+ } else {
+ str = torrents[0].getHashString();
+ for (i = 0; t = torrents[i]; ++i) {
+ if (str != t.getHashString()) {
+ str = mixed;
+ break;
+ };
+ };
+ };
+ setTextContent(e.hash_lb, str);
+
+ //
+ // privacy
+ //
+
+ if (torrents.length < 1) {
+ str = none;
+ } else {
+ baseline = torrents[0].getPrivateFlag();
+ str = baseline ? 'Private to this tracker -- DHT and PEX disabled' : 'Public torrent';
+ for (i = 0; t = torrents[i]; ++i) {
+ if (baseline != t.getPrivateFlag()) {
+ str = mixed;
+ break;
+ };
+ };
+ };
+ setTextContent(e.privacy_lb, str);
+
+ //
+ // comment
+ //
+
+ if (torrents.length < 1) {
+ str = none;
+ } else {
+ str = torrents[0].getComment();
+ for (i = 0; t = torrents[i]; ++i) {
+ if (str != t.getComment()) {
+ str = mixed;
+ break;
+ };
+ };
+ };
+ if (!str) {
+ str = none;
+ }
+ uri = parseUri(str);
+ if (uri.protocol == 'http' || uri.parseUri == 'https') {
+ str = encodeURI(str);
+ setInnerHTML(e.comment_lb, '<a href="' + str + '" target="_blank" >' + str + '</a>');
+ } else {
+ setTextContent(e.comment_lb, str);
+ };
+
+ //
+ // origin
+ //
+
+ if (torrents.length < 1) {
+ str = none;
+ } else {
+ mixed_creator = false;
+ mixed_date = false;
+ creator = torrents[0].getCreator();
+ date = torrents[0].getDateCreated();
+ for (i = 0; t = torrents[i]; ++i) {
+ if (creator != t.getCreator()) {
+ mixed_creator = true;
+ };
+ if (date != t.getDateCreated()) {
+ mixed_date = true;
+ };
+ };
+ var empty_creator = !creator || !creator.length;
+ var empty_date = !date;
+ if (mixed_creator || mixed_date) {
+ str = mixed;
+ } else if (empty_creator && empty_date) {
+ str = unknown;
+ } else if (empty_date && !empty_creator) {
+ str = 'Created by ' + creator;
+ } else if (empty_creator && !empty_date) {
+ str = 'Created on ' + (new Date(date * 1000)).toDateString();
+ } else {
+ str = 'Created by ' + creator + ' on ' + (new Date(date * 1000)).toDateString();
+ };
+ };
+ setTextContent(e.origin_lb, str);
+
+ //
+ // foldername
+ //
+
+ if (torrents.length < 1) {
+ str = none;
+ } else {
+ str = torrents[0].getDownloadDir();
+ for (i = 0; t = torrents[i]; ++i) {
+ if (str != t.getDownloadDir()) {
+ str = mixed;
+ break;
+ };
+ };
+ };
+ setTextContent(e.foldername_lb, str);
+ },
+
+ /****
+ ***** FILES PAGE
+ ****/
+
+ changeFileCommand = function (fileIndices, command) {
+ var torrentId = data.file_torrent.getId();
+ data.controller.changeFileCommand(torrentId, fileIndices, command);
+ },
+
+ onFileWantedToggled = function (ev, fileIndices, want) {
+ changeFileCommand(fileIndices, want ? 'files-wanted' : 'files-unwanted');
+ },
+
+ onFilePriorityToggled = function (ev, fileIndices, priority) {
+ var command;
+ switch (priority) {
+ case -1:
+ command = 'priority-low';
+ break;
+ case 1:
+ command = 'priority-high';
+ break;
+ default:
+ command = 'priority-normal';
+ break;
+ }
+ changeFileCommand(fileIndices, command);
+ },
+
+ onNameClicked = function (ev, fileRow, fileIndices) {
+ $(fileRow.getElement()).siblings().slideToggle();
+ },
+
+ clearFileList = function () {
+ $(data.elements.file_list).empty();
+ delete data.file_torrent;
+ delete data.file_torrent_n;
+ delete data.file_rows;
+ },
+
+ createFileTreeModel = function (tor) {
+ var i, j, n, name, tokens, walk, tree, token, sub,
+ leaves = [],
+ tree = {
+ children: {},
+ file_indices: []
+ };
+
+ n = tor.getFileCount();
+ for (i = 0; i < n; ++i) {
+ name = tor.getFile(i).name;
+ tokens = name.split('/');
+ walk = tree;
+ for (j = 0; j < tokens.length; ++j) {
+ token = tokens[j];
+ sub = walk.children[token];
+ if (!sub) {
+ walk.children[token] = sub = {
+ name: token,
+ parent: walk,
+ children: {},
+ file_indices: [],
+ depth: j
+ };
+ }
+ walk = sub;
+ }
+ walk.file_index = i;
+ delete walk.children;
+ leaves.push(walk);
+ }
+
+ for (i = 0; i < leaves.length; ++i) {
+ walk = leaves[i];
+ j = walk.file_index;
+ do {
+ walk.file_indices.push(j);
+ walk = walk.parent;
+ } while (walk);
+ }
+
+ return tree;
+ },
+
+ addNodeToView = function (tor, parent, sub, i) {
+ var row;
+ row = new FileRow(tor, sub.depth, sub.name, sub.file_indices, i % 2);
+ data.file_rows.push(row);
+ parent.appendChild(row.getElement());
+ $(row).bind('wantedToggled', onFileWantedToggled);
+ $(row).bind('priorityToggled', onFilePriorityToggled);
+ $(row).bind('nameClicked', onNameClicked);
+ },
+
+ addSubtreeToView = function (tor, parent, sub, i) {
+ var key, div;
+ div = document.createElement('div');
+ if (sub.parent) {
+ addNodeToView(tor, div, sub, i++);
+ }
+ if (sub.children) {
+ for (key in sub.children) {
+ i = addSubtreeToView(tor, div, sub.children[key]);
+ }
+ }
+ parent.appendChild(div);
+ return i;
+ },
+
+ updateFilesPage = function () {
+ var i, n, tor, fragment, tree,
+ file_list = data.elements.file_list,
+ torrents = data.torrents;
+
+ // only show one torrent at a time
+ if (torrents.length !== 1) {
+ clearFileList();
+ return;
+ }
+
+ tor = torrents[0];
+ n = tor ? tor.getFileCount() : 0;
+ if (tor != data.file_torrent || n != data.file_torrent_n) {
+ // rebuild the file list...
+ clearFileList();
+ data.file_torrent = tor;
+ data.file_torrent_n = n;
+ data.file_rows = [];
+ fragment = document.createDocumentFragment();
+ tree = createFileTreeModel(tor);
+ addSubtreeToView(tor, fragment, tree, 0);
+ file_list.appendChild(fragment);
+ } else {
+ // ...refresh the already-existing file list
+ for (i = 0, n = data.file_rows.length; i < n; ++i)
+ data.file_rows[i].refresh();
+ }
+ },
+
+ /****
+ ***** PEERS PAGE
+ ****/
+
+ updatePeersPage = function () {
+ var i, k, tor, peers, peer, parity,
+ html = [],
+ fmt = Transmission.fmt,
+ peers_list = data.elements.peers_list,
+ torrents = data.torrents;
+
+ for (k = 0; tor = torrents[k]; ++k) {
+ peers = tor.getPeers();
+ html.push('<div class="inspector_group">');
+ if (torrents.length > 1) {
+ html.push('<div class="inspector_torrent_label">', sanitizeText(tor.getName()), '</div>');
+ }
+ if (!peers || !peers.length) {
+ html.push('<br></div>'); // firefox won't paint the top border if the div is empty
+ continue;
+ }
+ html.push('<table class="peer_list">',
+ '<tr class="inspector_peer_entry even">',
+ '<th class="encryptedCol"></th>',
+ '<th class="upCol">Up</th>',
+ '<th class="downCol">Down</th>',
+ '<th class="percentCol">%</th>',
+ '<th class="statusCol">Status</th>',
+ '<th class="addressCol">Address</th>',
+ '<th class="clientCol">Client</th>',
+ '</tr>');
+ for (i = 0; peer = peers[i]; ++i) {
+ parity = (i % 2) ? 'odd' : 'even';
+ html.push('<tr class="inspector_peer_entry ', parity, '">',
+ '<td>', (peer.isEncrypted ? '<div class="encrypted-peer-cell" title="Encrypted Connection">' : '<div class="unencrypted-peer-cell">'), '</div>', '</td>',
+ '<td>', (peer.rateToPeer ? fmt.speedBps(peer.rateToPeer) : ''), '</td>',
+ '<td>', (peer.rateToClient ? fmt.speedBps(peer.rateToClient) : ''), '</td>',
+ '<td class="percentCol">', Math.floor(peer.progress * 100), '%', '</td>',
+ '<td>', fmt.peerStatus(peer.flagStr), '</td>',
+ '<td>', sanitizeText(peer.address), '</td>',
+ '<td class="clientCol">', sanitizeText(peer.clientName), '</td>',
+ '</tr>');
+ }
+ html.push('</table></div>');
+ }
+
+ setInnerHTML(peers_list, html.join(''));
+ },
+
+ /****
+ ***** TRACKERS PAGE
+ ****/
+
+ getAnnounceState = function (tracker) {
+ var timeUntilAnnounce, s = '';
+ switch (tracker.announceState) {
+ case Torrent._TrackerActive:
+ s = 'Announce in progress';
+ break;
+ case Torrent._TrackerWaiting:
+ timeUntilAnnounce = tracker.nextAnnounceTime - ((new Date()).getTime() / 1000);
+ if (timeUntilAnnounce < 0) {
+ timeUntilAnnounce = 0;
+ }
+ s = 'Next announce in ' + Transmission.fmt.timeInterval(timeUntilAnnounce);
+ break;
+ case Torrent._TrackerQueued:
+ s = 'Announce is queued';
+ break;
+ case Torrent._TrackerInactive:
+ s = tracker.isBackup ?
+ 'Tracker will be used as a backup' :
+ 'Announce not scheduled';
+ break;
+ default:
+ s = 'unknown announce state: ' + tracker.announceState;
+ }
+ return s;
+ },
+
+ lastAnnounceStatus = function (tracker) {
+
+ var lastAnnounceLabel = 'Last Announce',
+ lastAnnounce = ['N/A'],
+ lastAnnounceTime;
+
+ if (tracker.hasAnnounced) {
+ lastAnnounceTime = Transmission.fmt.timestamp(tracker.lastAnnounceTime);
+ if (tracker.lastAnnounceSucceeded) {
+ lastAnnounce = [lastAnnounceTime, ' (got ', Transmission.fmt.countString('peer', 'peers', tracker.lastAnnouncePeerCount), ')'];
+ } else {
+ lastAnnounceLabel = 'Announce error';
+ lastAnnounce = [(tracker.lastAnnounceResult ? (tracker.lastAnnounceResult + ' - ') : ''), lastAnnounceTime];
+ }
+ }
+ return {
+ 'label': lastAnnounceLabel,
+ 'value': lastAnnounce.join('')
+ };
+ },
+
+ lastScrapeStatus = function (tracker) {
+
+ var lastScrapeLabel = 'Last Scrape',
+ lastScrape = 'N/A',
+ lastScrapeTime;
+
+ if (tracker.hasScraped) {
+ lastScrapeTime = Transmission.fmt.timestamp(tracker.lastScrapeTime);
+ if (tracker.lastScrapeSucceeded) {
+ lastScrape = lastScrapeTime;
+ } else {
+ lastScrapeLabel = 'Scrape error';
+ lastScrape = (tracker.lastScrapeResult ? tracker.lastScrapeResult + ' - ' : '') + lastScrapeTime;
+ }
+ }
+ return {
+ 'label': lastScrapeLabel,
+ 'value': lastScrape
+ };
+ },
+
+ updateTrackersPage = function () {
+ var i, j, tier, tracker, trackers, tor,
+ html, parity, lastAnnounceStatusHash,
+ announceState, lastScrapeStatusHash,
+ na = 'N/A',
+ trackers_list = data.elements.trackers_list,
+ torrents = data.torrents;
+
+ // By building up the HTML as as string, then have the browser
+ // turn this into a DOM tree, this is a fast operation.
+ html = [];
+ for (i = 0; tor = torrents[i]; ++i) {
+ html.push('<div class="inspector_group">');
+
+ if (torrents.length > 1) {
+ html.push('<div class="inspector_torrent_label">', tor.getName(), '</div>');
+ }
+
+ tier = -1;
+ trackers = tor.getTrackers();
+ for (j = 0; tracker = trackers[j]; ++j) {
+ if (tier != tracker.tier) {
+ if (tier !== -1) { // close previous tier
+ html.push('</ul></div>');
+ }
+
+ tier = tracker.tier;
+
+ html.push('<div class="inspector_group_label">',
+ 'Tier ', tier + 1, '</div>',
+ '<ul class="tier_list">');
+ }
+
+ // Display construction
+ lastAnnounceStatusHash = lastAnnounceStatus(tracker);
+ announceState = getAnnounceState(tracker);
+ lastScrapeStatusHash = lastScrapeStatus(tracker);
+ parity = (j % 2) ? 'odd' : 'even';
+ html.push('<li class="inspector_tracker_entry ', parity, '"><div class="tracker_host" title="', sanitizeText(tracker.announce), '">',
+ sanitizeText(tracker.host || tracker.announce), '</div>',
+ '<div class="tracker_activity">',
+ '<div>', lastAnnounceStatusHash['label'], ': ', lastAnnounceStatusHash['value'], '</div>',
+ '<div>', announceState, '</div>',
+ '<div>', lastScrapeStatusHash['label'], ': ', lastScrapeStatusHash['value'], '</div>',
+ '</div><table class="tracker_stats">',
+ '<tr><th>Seeders:</th><td>', (tracker.seederCount > -1 ? tracker.seederCount : na), '</td></tr>',
+ '<tr><th>Leechers:</th><td>', (tracker.leecherCount > -1 ? tracker.leecherCount : na), '</td></tr>',
+ '<tr><th>Downloads:</th><td>', (tracker.downloadCount > -1 ? tracker.downloadCount : na), '</td></tr>',
+ '</table></li>');
+ }
+ if (tier !== -1) { // close last tier
+ html.push('</ul></div>');
+ }
+
+ html.push('</div>'); // inspector_group
+ }
+
+ setInnerHTML(trackers_list, html.join(''));
+ },
+
+ initialize = function (controller) {
+
+ var ti = '#torrent_inspector_';
+
+ data.controller = controller;
+
+ $('.inspector-tab').click(onTabClicked);
+
+ data.elements.info_page = $('#inspector-page-info')[0];
+ data.elements.files_page = $('#inspector-page-files')[0];
+ data.elements.peers_page = $('#inspector-page-peers')[0];
+ data.elements.trackers_page = $('#inspector-page-trackers')[0];
+
+ data.elements.file_list = $('#inspector_file_list')[0];
+ data.elements.peers_list = $('#inspector_peers_list')[0];
+ data.elements.trackers_list = $('#inspector_trackers_list')[0];
+
+ data.elements.have_lb = $('#inspector-info-have')[0];
+ data.elements.availability_lb = $('#inspector-info-availability')[0];
+ data.elements.downloaded_lb = $('#inspector-info-downloaded')[0];
+ data.elements.uploaded_lb = $('#inspector-info-uploaded')[0];
+ data.elements.state_lb = $('#inspector-info-state')[0];
+ data.elements.running_time_lb = $('#inspector-info-running-time')[0];
+ data.elements.remaining_time_lb = $('#inspector-info-remaining-time')[0];
+ data.elements.last_activity_lb = $('#inspector-info-last-activity')[0];
+ data.elements.error_lb = $('#inspector-info-error')[0];
+ data.elements.size_lb = $('#inspector-info-size')[0];
+ data.elements.foldername_lb = $('#inspector-info-location')[0];
+ data.elements.hash_lb = $('#inspector-info-hash')[0];
+ data.elements.privacy_lb = $('#inspector-info-privacy')[0];
+ data.elements.origin_lb = $('#inspector-info-origin')[0];
+ data.elements.comment_lb = $('#inspector-info-comment')[0];
+ data.elements.name_lb = $('#torrent_inspector_name')[0];
+
+ // force initial 'N/A' updates on all the pages
+ updateInspector();
+ updateInfoPage();
+ updatePeersPage();
+ updateTrackersPage();
+ updateFilesPage();
+ };
+
+ /****
+ ***** PUBLIC FUNCTIONS
+ ****/
+
+ this.setTorrents = function (torrents) {
+ var d = data;
+
+ // update the inspector when a selected torrent's data changes.
+ $(d.torrents).unbind('dataChanged.inspector');
+ $(torrents).bind('dataChanged.inspector', $.proxy(updateInspector, this));
+ d.torrents = torrents;
+
+ // periodically ask for updates to the inspector's torrents
+ clearInterval(d.refreshInterval);
+ d.refreshInterval = setInterval($.proxy(refreshTorrents, this), 2000);
+ refreshTorrents();
+
+ // refresh the inspector's UI
+ updateInspector();
+ };
+
+ initialize(controller);
};
var Notifications = {};
$(document).ready(function () {
- if (!window.webkitNotifications) {
- return;
- }
+ if (!window.webkitNotifications) {
+ return;
+ };
- var notificationsEnabled = (window.webkitNotifications.checkPermission() === 0),
- toggle = $('#toggle_notifications');
+ var notificationsEnabled = (window.webkitNotifications.checkPermission() === 0)
+ var toggle = $('#toggle_notifications');
- toggle.show();
- updateMenuTitle();
- $(transmission).bind('downloadComplete seedingComplete', function (event, torrent) {
- if (notificationsEnabled) {
- var title = (event.type == 'downloadComplete' ? 'Download' : 'Seeding') + ' complete',
- content = torrent.getName(),
- notification;
+ toggle.show();
+ updateMenuTitle();
+ $(transmission).bind('downloadComplete seedingComplete', function (event, torrent) {
+ if (notificationsEnabled) {
+ var title = (event.type == 'downloadComplete' ? 'Download' : 'Seeding') + ' complete',
+ content = torrent.getName(),
+ notification;
- notification = window.webkitNotifications.createNotification('style/transmission/images/logo.png', title, content);
- notification.show();
- setTimeout(function () {
- notification.cancel();
- }, 5000);
- };
- });
+ notification = window.webkitNotifications.createNotification('style/transmission/images/logo.png', title, content);
+ notification.show();
+ setTimeout(function () {
+ notification.cancel();
+ }, 5000);
+ };
+ });
- function updateMenuTitle() {
- toggle.html((notificationsEnabled ? 'Disable' : 'Enable') + ' Notifications');
- }
+ function updateMenuTitle() {
+ toggle.html((notificationsEnabled ? 'Disable' : 'Enable') + ' Notifications');
+ };
- Notifications.toggle = function () {
- if (window.webkitNotifications.checkPermission() !== 0) {
- window.webkitNotifications.requestPermission(function () {
- notificationsEnabled = (window.webkitNotifications.checkPermission() === 0);
- updateMenuTitle();
- });
- } else {
- notificationsEnabled = !notificationsEnabled;
- updateMenuTitle();
- }
- };
+ Notifications.toggle = function () {
+ if (window.webkitNotifications.checkPermission() !== 0) {
+ window.webkitNotifications.requestPermission(function () {
+ notificationsEnabled = (window.webkitNotifications.checkPermission() === 0);
+ updateMenuTitle();
+ });
+ } else {
+ notificationsEnabled = !notificationsEnabled;
+ updateMenuTitle();
+ };
+ };
});
function PrefsDialog(remote) {
- var data = {
- dialog: null,
- remote: null,
- elements: { },
-
- // all the RPC session keys that we have gui controls for
- keys: [
- 'alt-speed-down',
- 'alt-speed-time-begin',
- 'alt-speed-time-day',
- 'alt-speed-time-enabled',
- 'alt-speed-time-end',
- 'alt-speed-up',
- 'blocklist-enabled',
- 'blocklist-size',
- 'blocklist-url',
- 'dht-enabled',
- 'download-dir',
- 'encryption',
- 'idle-seeding-limit',
- 'idle-seeding-limit-enabled',
- 'lpd-enabled',
- 'peer-limit-global',
- 'peer-limit-per-torrent',
- 'peer-port',
- 'peer-port-random-on-start',
- 'pex-enabled',
- 'port-forwarding-enabled',
- 'rename-partial-files',
- 'seedRatioLimit',
- 'seedRatioLimited',
- 'speed-limit-down',
- 'speed-limit-down-enabled',
- 'speed-limit-up',
- 'speed-limit-up-enabled',
- 'start-added-torrents',
- 'utp-enabled'
- ],
-
- // map of keys that are enabled only if a 'parent' key is enabled
- groups: {
- 'alt-speed-time-enabled': ['alt-speed-time-begin',
- 'alt-speed-time-day',
- 'alt-speed-time-end' ],
- 'blocklist-enabled': ['blocklist-url',
- 'blocklist-update-button' ],
- 'idle-seeding-limit-enabled': [ 'idle-seeding-limit' ],
- 'seedRatioLimited': [ 'seedRatioLimit' ],
- 'speed-limit-down-enabled': [ 'speed-limit-down' ],
- 'speed-limit-up-enabled': [ 'speed-limit-up' ]
- }
- },
-
- initTimeDropDown = function(e)
- {
- var i, hour, mins, value, content;
-
- for (i=0; i<24*4; ++i) {
- hour = parseInt(i/4, 10);
- mins = ((i%4) * 15);
- value = i * 15;
- content = hour + ':' + (mins || '00');
- e.options[i] = new Option(content, value);
- }
- },
-
- onPortChecked = function(response)
- {
- var is_open = response['arguments']['port-is-open'],
- text = 'Port is <b>' + (is_open ? 'Open' : 'Closed') + '</b>',
- e = data.elements.root.find('#port-label');
- setInnerHTML(e[0],text);
- },
-
- setGroupEnabled = function(parent_key, enabled)
- {
- var i, key, keys, root;
-
- if (parent_key in data.groups)
- {
- root = data.elements.root,
- keys = data.groups[parent_key];
-
- for (i=0; key=keys[i]; ++i)
- root.find('#'+key).attr('disabled',!enabled);
- }
- },
-
- onBlocklistUpdateClicked = function ()
- {
- data.remote.updateBlocklist();
- setBlocklistButtonEnabled(false);
- },
- setBlocklistButtonEnabled = function(b)
- {
- var e = data.elements.blocklist_button;
- e.attr('disabled',!b);
- e.val(b ? 'Update' : 'Updating...');
- },
-
- getValue = function(e)
- {
- var str;
-
- switch (e[0].type)
- {
- case 'checkbox':
- case 'radio':
- return e.prop('checked');
-
- case 'text':
- case 'url':
- case 'email':
- case 'number':
- case 'search':
- case 'select-one':
- str = e.val();
- if( parseInt(str,10).toString() === str)
- return parseInt(str,10);
- if( parseFloat(str).toString() === str)
- return parseFloat(str);
- return str;
-
- default:
- return null;
- }
- },
-
- /* this callback is for controls whose changes can be applied
- immediately, like checkboxs, radioboxes, and selects */
- onControlChanged = function(ev)
- {
- var o = {};
- o[ev.target.id] = getValue($(ev.target));
- data.remote.savePrefs(o);
- },
-
- /* these two callbacks are for controls whose changes can't be applied
- immediately -- like a text entry field -- because it takes many
- change events for the user to get to the desired result */
- onControlFocused = function(ev)
- {
- data.oldValue = getValue($(ev.target));
- },
- onControlBlurred = function(ev)
- {
- var newValue = getValue($(ev.target));
- if (newValue !== data.oldValue)
- {
- var o = {};
- o[ev.target.id] = newValue;
- data.remote.savePrefs(o);
- delete data.oldValue;
- }
- },
-
- getDefaultMobileOptions = function()
- {
- return {
- width: $(window).width(),
- height: $(window).height(),
- position: [ 'left', 'top' ]
- };
- },
-
- initialize = function (remote)
- {
- var i, key, e, o;
-
- data.remote = remote;
-
- e = $('#prefs-dialog');
- data.elements.root = e;
-
- initTimeDropDown(e.find('#alt-speed-time-begin')[0]);
- initTimeDropDown(e.find('#alt-speed-time-end')[0]);
-
- o = isMobileDevice
- ? getDefaultMobileOptions()
- : { width: 350, height: 400 };
- o.autoOpen = false;
- o.show = o.hide = 'fade';
- o.close = onDialogClosed;
- e.tabbedDialog(o);
-
- e = e.find('#blocklist-update-button');
- data.elements.blocklist_button = e;
- e.click(onBlocklistUpdateClicked);
-
- // listen for user input
- for (i=0; key=data.keys[i]; ++i)
- {
- e = data.elements.root.find('#'+key);
- switch (e[0].type)
- {
- case 'checkbox':
- case 'radio':
- case 'select-one':
- e.change(onControlChanged);
- break;
-
- case 'text':
- case 'url':
- case 'email':
- case 'number':
- case 'search':
- e.focus(onControlFocused);
- e.blur(onControlBlurred);
-
- default:
- break;
- }
- }
- },
-
- getValues = function()
- {
- var i, key, val, o={},
- keys = data.keys,
- root = data.elements.root;
-
- for (i=0; key=keys[i]; ++i) {
- val = getValue(root.find('#'+key));
- if (val !== null)
- o[key] = val;
- }
-
- return o;
- },
-
- onDialogClosed = function()
- {
- transmission.hideMobileAddressbar();
-
- $(data.dialog).trigger('closed', getValues());
- };
-
- /****
- ***** PUBLIC FUNCTIONS
- ****/
-
- // update the dialog's controls
- this.set = function (o)
- {
- var e, i, key, val, option,
- keys = data.keys,
- root = data.elements.root;
-
- setBlocklistButtonEnabled(true);
-
- for (i=0; key=keys[i]; ++i)
- {
- val = o[key];
- e = root.find('#'+key);
-
- if (key === 'blocklist-size')
- {
- // special case -- regular text area
- e.text('' + val.toStringWithCommas());
- }
- else switch (e[0].type)
- {
- case 'checkbox':
- case 'radio':
- e.prop('checked', val);
- setGroupEnabled(key, val);
- break;
- case 'text':
- case 'url':
- case 'email':
- case 'number':
- case 'search':
- // don't change the text if the user's editing it.
- // it's very annoying when that happens!
- if (e[0] !== document.activeElement)
- e.val(val);
- break;
- case 'select-one':
- e.val(val);
- break;
- default:
- break;
- }
- }
- };
-
- this.show = function ()
- {
- transmission.hideMobileAddressbar();
-
- setBlocklistButtonEnabled(true);
- data.remote.checkPort(onPortChecked,this);
- data.elements.root.dialog('open');
- };
-
- this.close = function ()
- {
- transmission.hideMobileAddressbar();
- data.elements.root.dialog('close');
- },
-
- this.shouldAddedTorrentsStart = function()
- {
- return data.elements.root.find('#start-added-torrents')[0].checked;
- };
-
- data.dialog = this;
- initialize (remote);
+ var data = {
+ dialog: null,
+ remote: null,
+ elements: {},
+
+ // all the RPC session keys that we have gui controls for
+ keys: [
+ 'alt-speed-down',
+ 'alt-speed-time-begin',
+ 'alt-speed-time-day',
+ 'alt-speed-time-enabled',
+ 'alt-speed-time-end',
+ 'alt-speed-up',
+ 'blocklist-enabled',
+ 'blocklist-size',
+ 'blocklist-url',
+ 'dht-enabled',
+ 'download-dir',
+ 'encryption',
+ 'idle-seeding-limit',
+ 'idle-seeding-limit-enabled',
+ 'lpd-enabled',
+ 'peer-limit-global',
+ 'peer-limit-per-torrent',
+ 'peer-port',
+ 'peer-port-random-on-start',
+ 'pex-enabled',
+ 'port-forwarding-enabled',
+ 'rename-partial-files',
+ 'seedRatioLimit',
+ 'seedRatioLimited',
+ 'speed-limit-down',
+ 'speed-limit-down-enabled',
+ 'speed-limit-up',
+ 'speed-limit-up-enabled',
+ 'start-added-torrents',
+ 'utp-enabled'
+ ],
+
+ // map of keys that are enabled only if a 'parent' key is enabled
+ groups: {
+ 'alt-speed-time-enabled': ['alt-speed-time-begin',
+ 'alt-speed-time-day',
+ 'alt-speed-time-end'
+ ],
+ 'blocklist-enabled': ['blocklist-url',
+ 'blocklist-update-button'
+ ],
+ 'idle-seeding-limit-enabled': ['idle-seeding-limit'],
+ 'seedRatioLimited': ['seedRatioLimit'],
+ 'speed-limit-down-enabled': ['speed-limit-down'],
+ 'speed-limit-up-enabled': ['speed-limit-up']
+ }
+ };
+
+ var initTimeDropDown = function (e) {
+ var i, hour, mins, value, content;
+
+ for (i = 0; i < 24 * 4; ++i) {
+ hour = parseInt(i / 4, 10);
+ mins = ((i % 4) * 15);
+ value = i * 15;
+ content = hour + ':' + (mins || '00');
+ e.options[i] = new Option(content, value);
+ }
+ };
+
+ var onPortChecked = function (response) {
+ var is_open = response['arguments']['port-is-open'];
+ var text = 'Port is <b>' + (is_open ? 'Open' : 'Closed') + '</b>';
+ var e = data.elements.root.find('#port-label');
+ setInnerHTML(e[0], text);
+ };
+
+ var setGroupEnabled = function (parent_key, enabled) {
+ var i, key, keys, root;
+
+ if (parent_key in data.groups) {
+ root = data.elements.root;
+ keys = data.groups[parent_key];
+
+ for (i = 0; key = keys[i]; ++i) {
+ root.find('#' + key).attr('disabled', !enabled);
+ };
+ };
+ };
+
+ var onBlocklistUpdateClicked = function () {
+ data.remote.updateBlocklist();
+ setBlocklistButtonEnabled(false);
+ };
+
+ var setBlocklistButtonEnabled = function (b) {
+ var e = data.elements.blocklist_button;
+ e.attr('disabled', !b);
+ e.val(b ? 'Update' : 'Updating...');
+ };
+
+ var getValue = function (e) {
+ var str;
+
+ switch (e[0].type) {
+ case 'checkbox':
+ case 'radio':
+ return e.prop('checked');
+
+ case 'text':
+ case 'url':
+ case 'email':
+ case 'number':
+ case 'search':
+ case 'select-one':
+ str = e.val();
+ if (parseInt(str, 10).toString() === str) {
+ return parseInt(str, 10);
+ };
+ if (parseFloat(str).toString() === str) {
+ return parseFloat(str);
+ };
+ return str;
+
+ default:
+ return null;
+ }
+ };
+
+ /* this callback is for controls whose changes can be applied
+ immediately, like checkboxs, radioboxes, and selects */
+ var onControlChanged = function (ev) {
+ var o = {};
+ o[ev.target.id] = getValue($(ev.target));
+ data.remote.savePrefs(o);
+ };
+
+ /* these two callbacks are for controls whose changes can't be applied
+ immediately -- like a text entry field -- because it takes many
+ change events for the user to get to the desired result */
+ var onControlFocused = function (ev) {
+ data.oldValue = getValue($(ev.target));
+ };
+
+ var onControlBlurred = function (ev) {
+ var newValue = getValue($(ev.target));
+ if (newValue !== data.oldValue) {
+ var o = {};
+ o[ev.target.id] = newValue;
+ data.remote.savePrefs(o);
+ delete data.oldValue;
+ }
+ };
+
+ var getDefaultMobileOptions = function () {
+ return {
+ width: $(window).width(),
+ height: $(window).height(),
+ position: ['left', 'top']
+ };
+ };
+
+ var initialize = function (remote) {
+ var i, key, e, o;
+
+ data.remote = remote;
+
+ e = $('#prefs-dialog');
+ data.elements.root = e;
+
+ initTimeDropDown(e.find('#alt-speed-time-begin')[0]);
+ initTimeDropDown(e.find('#alt-speed-time-end')[0]);
+
+ o = isMobileDevice ? getDefaultMobileOptions() : {
+ width: 350,
+ height: 400
+ };
+ o.autoOpen = false;
+ o.show = o.hide = 'fade';
+ o.close = onDialogClosed;
+ e.tabbedDialog(o);
+
+ e = e.find('#blocklist-update-button');
+ data.elements.blocklist_button = e;
+ e.click(onBlocklistUpdateClicked);
+
+ // listen for user input
+ for (i = 0; key = data.keys[i]; ++i) {
+ e = data.elements.root.find('#' + key);
+ switch (e[0].type) {
+ case 'checkbox':
+ case 'radio':
+ case 'select-one':
+ e.change(onControlChanged);
+ break;
+
+ case 'text':
+ case 'url':
+ case 'email':
+ case 'number':
+ case 'search':
+ e.focus(onControlFocused);
+ e.blur(onControlBlurred);
+
+ default:
+ break;
+ };
+ };
+ };
+
+ var getValues = function () {
+ var i, key, val, o = {},
+ keys = data.keys,
+ root = data.elements.root;
+
+ for (i = 0; key = keys[i]; ++i) {
+ val = getValue(root.find('#' + key));
+ if (val !== null) {
+ o[key] = val;
+ };
+ };
+
+ return o;
+ };
+
+ var onDialogClosed = function () {
+ transmission.hideMobileAddressbar();
+
+ $(data.dialog).trigger('closed', getValues());
+ };
+
+ /****
+ ***** PUBLIC FUNCTIONS
+ ****/
+
+ // update the dialog's controls
+ this.set = function (o) {
+ var e, i, key, val, option;
+ var keys = data.keys;
+ var root = data.elements.root;
+
+ setBlocklistButtonEnabled(true);
+
+ for (i = 0; key = keys[i]; ++i) {
+ val = o[key];
+ e = root.find('#' + key);
+
+ if (key === 'blocklist-size') {
+ // special case -- regular text area
+ e.text('' + val.toStringWithCommas());
+ } else switch (e[0].type) {
+ case 'checkbox':
+ case 'radio':
+ e.prop('checked', val);
+ setGroupEnabled(key, val);
+ break;
+ case 'text':
+ case 'url':
+ case 'email':
+ case 'number':
+ case 'search':
+ // don't change the text if the user's editing it.
+ // it's very annoying when that happens!
+ if (e[0] !== document.activeElement) {
+ e.val(val);
+ };
+ break;
+ case 'select-one':
+ e.val(val);
+ break;
+ default:
+ break;
+ };
+ };
+ };
+
+ this.show = function () {
+ transmission.hideMobileAddressbar();
+
+ setBlocklistButtonEnabled(true);
+ data.remote.checkPort(onPortChecked, this);
+ data.elements.root.dialog('open');
+ };
+
+ this.close = function () {
+ transmission.hideMobileAddressbar();
+ data.elements.root.dialog('close');
+ };
+
+ this.shouldAddedTorrentsStart = function () {
+ return data.elements.root.find('#start-added-torrents')[0].checked;
+ };
+
+ data.dialog = this;
+ initialize(remote);
};
*/
var RPC = {
- _DaemonVersion : 'version',
- _DownSpeedLimit : 'speed-limit-down',
- _DownSpeedLimited : 'speed-limit-down-enabled',
- _QueueMoveTop : 'queue-move-top',
- _QueueMoveBottom : 'queue-move-bottom',
- _QueueMoveUp : 'queue-move-up',
- _QueueMoveDown : 'queue-move-down',
- _Root : '../rpc',
- _TurtleDownSpeedLimit : 'alt-speed-down',
- _TurtleState : 'alt-speed-enabled',
- _TurtleUpSpeedLimit : 'alt-speed-up',
- _UpSpeedLimit : 'speed-limit-up',
- _UpSpeedLimited : 'speed-limit-up-enabled'
+ _DaemonVersion: 'version',
+ _DownSpeedLimit: 'speed-limit-down',
+ _DownSpeedLimited: 'speed-limit-down-enabled',
+ _QueueMoveTop: 'queue-move-top',
+ _QueueMoveBottom: 'queue-move-bottom',
+ _QueueMoveUp: 'queue-move-up',
+ _QueueMoveDown: 'queue-move-down',
+ _Root: '../rpc',
+ _TurtleDownSpeedLimit: 'alt-speed-down',
+ _TurtleState: 'alt-speed-enabled',
+ _TurtleUpSpeedLimit: 'alt-speed-up',
+ _UpSpeedLimit: 'speed-limit-up',
+ _UpSpeedLimited: 'speed-limit-up-enabled'
};
-function TransmissionRemote(controller)
-{
- this.initialize(controller);
- return this;
+function TransmissionRemote(controller) {
+ this.initialize(controller);
+ return this;
}
-TransmissionRemote.prototype =
-{
- /*
- * Constructor
- */
- initialize: function(controller) {
- this._controller = controller;
- this._error = '';
- this._token = '';
- },
+TransmissionRemote.prototype = {
+ /*
+ * Constructor
+ */
+ initialize: function (controller) {
+ this._controller = controller;
+ this._error = '';
+ this._token = '';
+ },
- /*
- * Display an error if an ajax request fails, and stop sending requests
- * or on a 409, globally set the X-Transmission-Session-Id and resend
- */
- ajaxError: function(request, error_string, exception, ajaxObject) {
- var token,
- remote = this;
+ /*
+ * Display an error if an ajax request fails, and stop sending requests
+ * or on a 409, globally set the X-Transmission-Session-Id and resend
+ */
+ ajaxError: function (request, error_string, exception, ajaxObject) {
+ var token;
+ var remote = this;
- // set the Transmission-Session-Id on a 409
- if (request.status === 409 && (token = request.getResponseHeader('X-Transmission-Session-Id'))){
- remote._token = token;
- $.ajax(ajaxObject);
- return;
- }
+ // set the Transmission-Session-Id on a 409
+ if (request.status === 409 && (token = request.getResponseHeader('X-Transmission-Session-Id'))) {
+ remote._token = token;
+ $.ajax(ajaxObject);
+ return;
+ };
- remote._error = request.responseText
- ? request.responseText.trim().replace(/(<([^>]+)>)/ig,"")
- : "";
- if (!remote._error.length)
- remote._error = 'Server not responding';
+ remote._error = request.responseText ? request.responseText.trim().replace(/(<([^>]+)>)/ig, "") : "";
+ if (!remote._error.length) {
+ remote._error = 'Server not responding';
+ };
- dialog.confirm('Connection Failed',
- 'Could not connect to the server. You may need to reload the page to reconnect.',
- 'Details',
- function() {
- alert(remote._error);
- },
- 'Dismiss');
- remote._controller.togglePeriodicSessionRefresh(false);
- },
+ dialog.confirm('Connection Failed',
+ 'Could not connect to the server. You may need to reload the page to reconnect.',
+ 'Details',
+ function () {
+ alert(remote._error);
+ },
+ 'Dismiss');
+ remote._controller.togglePeriodicSessionRefresh(false);
+ },
- appendSessionId: function(XHR) {
- if (this._token) {
- XHR.setRequestHeader('X-Transmission-Session-Id', this._token);
- }
- },
+ appendSessionId: function (XHR) {
+ if (this._token) {
+ XHR.setRequestHeader('X-Transmission-Session-Id', this._token);
+ };
+ },
- sendRequest: function(data, callback, context, async) {
- var remote = this;
- if (typeof async != 'boolean')
- async = true;
+ sendRequest: function (data, callback, context, async) {
+ var remote = this;
+ if (typeof async != 'boolean') {
+ async = true;
+ };
- var ajaxSettings = {
- url: RPC._Root,
- type: 'POST',
- contentType: 'json',
- dataType: 'json',
- cache: false,
- data: JSON.stringify(data),
- beforeSend: function(XHR){ remote.appendSessionId(XHR); },
- error: function(request, error_string, exception){ remote.ajaxError(request, error_string, exception, ajaxSettings); },
- success: callback,
- context: context,
- async: async
- };
+ var ajaxSettings = {
+ url: RPC._Root,
+ type: 'POST',
+ contentType: 'json',
+ dataType: 'json',
+ cache: false,
+ data: JSON.stringify(data),
+ beforeSend: function (XHR) {
+ remote.appendSessionId(XHR);
+ },
+ error: function (request, error_string, exception) {
+ remote.ajaxError(request, error_string, exception, ajaxSettings);
+ },
+ success: callback,
+ context: context,
+ async: async
+ };
- $.ajax(ajaxSettings);
- },
+ $.ajax(ajaxSettings);
+ },
- loadDaemonPrefs: function(callback, context, async) {
- var o = { method: 'session-get' };
- this.sendRequest(o, callback, context, async);
- },
+ loadDaemonPrefs: function (callback, context, async) {
+ var o = {
+ method: 'session-get'
+ };
+ this.sendRequest(o, callback, context, async);
+ },
- checkPort: function(callback, context, async) {
- var o = { method: 'port-test' };
- this.sendRequest(o, callback, context, async);
- },
+ checkPort: function (callback, context, async) {
+ var o = {
+ method: 'port-test'
+ };
+ this.sendRequest(o, callback, context, async);
+ },
- renameTorrent: function(torrentIds, oldpath, newname, callback, context) {
- var o = {
- method: 'torrent-rename-path',
- arguments: {
- 'ids': torrentIds,
- 'path': oldpath,
- 'name': newname
- }
- };
- this.sendRequest(o, callback, context);
- },
+ renameTorrent: function (torrentIds, oldpath, newname, callback, context) {
+ var o = {
+ method: 'torrent-rename-path',
+ arguments: {
+ 'ids': torrentIds,
+ 'path': oldpath,
+ 'name': newname
+ }
+ };
+ this.sendRequest(o, callback, context);
+ },
- loadDaemonStats: function(callback, context, async) {
- var o = { method: 'session-stats' };
- this.sendRequest(o, callback, context, async);
- },
+ loadDaemonStats: function (callback, context, async) {
+ var o = {
+ method: 'session-stats'
+ };
+ this.sendRequest(o, callback, context, async);
+ },
- updateTorrents: function(torrentIds, fields, callback, context) {
- var o = {
- method: 'torrent-get',
- arguments: {
- 'fields': fields
- }
- };
- if (torrentIds)
- o['arguments'].ids = torrentIds;
- this.sendRequest(o, function(response) {
- var args = response['arguments'];
- callback.call(context,args.torrents,args.removed);
- });
- },
+ updateTorrents: function (torrentIds, fields, callback, context) {
+ var o = {
+ method: 'torrent-get',
+ arguments: {
+ 'fields': fields
+ }
+ };
+ if (torrentIds) {
+ o['arguments'].ids = torrentIds;
+ };
+ this.sendRequest(o, function (response) {
+ var args = response['arguments'];
+ callback.call(context, args.torrents, args.removed);
+ });
+ },
- getFreeSpace: function(dir, callback, context) {
- var remote = this;
- var o = {
- method: 'free-space',
- arguments: { path: dir }
- };
- this.sendRequest(o, function(response) {
- var args = response['arguments'];
- callback.call (context, args.path, args['size-bytes']);
- });
- },
+ getFreeSpace: function (dir, callback, context) {
+ var remote = this;
+ var o = {
+ method: 'free-space',
+ arguments: {
+ path: dir
+ }
+ };
+ this.sendRequest(o, function (response) {
+ var args = response['arguments'];
+ callback.call(context, args.path, args['size-bytes']);
+ });
+ },
- changeFileCommand: function(torrentId, fileIndices, command) {
- var remote = this,
- args = { ids: [torrentId] };
- args[command] = fileIndices;
- this.sendRequest({
- arguments: args,
- method: 'torrent-set'
- }, function() {
- remote._controller.refreshTorrents([torrentId]);
- });
- },
+ changeFileCommand: function (torrentId, fileIndices, command) {
+ var remote = this,
+ args = {
+ ids: [torrentId]
+ };
+ args[command] = fileIndices;
+ this.sendRequest({
+ arguments: args,
+ method: 'torrent-set'
+ }, function () {
+ remote._controller.refreshTorrents([torrentId]);
+ });
+ },
- sendTorrentSetRequests: function(method, torrent_ids, args, callback, context) {
- if (!args) args = { };
- args['ids'] = torrent_ids;
- var o = {
- method: method,
- arguments: args
- };
- this.sendRequest(o, callback, context);
- },
+ sendTorrentSetRequests: function (method, torrent_ids, args, callback, context) {
+ if (!args) {
+ args = {};
+ };
+ args['ids'] = torrent_ids;
+ var o = {
+ method: method,
+ arguments: args
+ };
+ this.sendRequest(o, callback, context);
+ },
- sendTorrentActionRequests: function(method, torrent_ids, callback, context) {
- this.sendTorrentSetRequests(method, torrent_ids, null, callback, context);
- },
+ sendTorrentActionRequests: function (method, torrent_ids, callback, context) {
+ this.sendTorrentSetRequests(method, torrent_ids, null, callback, context);
+ },
- startTorrents: function(torrent_ids, noqueue, callback, context) {
- var name = noqueue ? 'torrent-start-now' : 'torrent-start';
- this.sendTorrentActionRequests(name, torrent_ids, callback, context);
- },
- stopTorrents: function(torrent_ids, callback, context) {
- this.sendTorrentActionRequests('torrent-stop', torrent_ids, callback, context);
- },
+ startTorrents: function (torrent_ids, noqueue, callback, context) {
+ var name = noqueue ? 'torrent-start-now' : 'torrent-start';
+ this.sendTorrentActionRequests(name, torrent_ids, callback, context);
+ },
+ stopTorrents: function (torrent_ids, callback, context) {
+ this.sendTorrentActionRequests('torrent-stop', torrent_ids, callback, context);
+ },
- moveTorrents: function(torrent_ids, new_location, callback, context) {
- var remote = this;
- this.sendTorrentSetRequests( 'torrent-set-location', torrent_ids,
- {"move": true, "location": new_location}, callback, context);
- },
+ moveTorrents: function (torrent_ids, new_location, callback, context) {
+ var remote = this;
+ this.sendTorrentSetRequests('torrent-set-location', torrent_ids, {
+ "move": true,
+ "location": new_location
+ }, callback, context);
+ },
- removeTorrents: function(torrent_ids, callback, context) {
- this.sendTorrentActionRequests('torrent-remove', torrent_ids, callback, context);
- },
- removeTorrentsAndData: function(torrents) {
- var remote = this;
- var o = {
- method: 'torrent-remove',
- arguments: {
- 'delete-local-data': true,
- ids: [ ]
- }
- };
+ removeTorrents: function (torrent_ids, callback, context) {
+ this.sendTorrentActionRequests('torrent-remove', torrent_ids, callback, context);
+ },
+ removeTorrentsAndData: function (torrents) {
+ var remote = this;
+ var o = {
+ method: 'torrent-remove',
+ arguments: {
+ 'delete-local-data': true,
+ ids: []
+ }
+ };
- if (torrents) {
- for (var i=0, len=torrents.length; i<len; ++i) {
- o.arguments.ids.push(torrents[i].getId());
- }
- }
- this.sendRequest(o, function() {
- remote._controller.refreshTorrents();
- });
- },
- verifyTorrents: function(torrent_ids, callback, context) {
- this.sendTorrentActionRequests('torrent-verify', torrent_ids, callback, context);
- },
- reannounceTorrents: function(torrent_ids, callback, context) {
- this.sendTorrentActionRequests('torrent-reannounce', torrent_ids, callback, context);
- },
- addTorrentByUrl: function(url, options) {
- var remote = this;
- if (url.match(/^[0-9a-f]{40}$/i)) {
- url = 'magnet:?xt=urn:btih:'+url;
- }
- var o = {
- method: 'torrent-add',
- arguments: {
- paused: (options.paused),
- filename: url
- }
- };
- this.sendRequest(o, function() {
- remote._controller.refreshTorrents();
- });
- },
- savePrefs: function(args) {
- var remote = this;
- var o = {
- method: 'session-set',
- arguments: args
- };
- this.sendRequest(o, function() {
- remote._controller.loadDaemonPrefs();
- });
- },
- updateBlocklist: function() {
- var remote = this;
- var o = {
- method: 'blocklist-update'
- };
- this.sendRequest(o, function() {
- remote._controller.loadDaemonPrefs();
- });
- },
+ if (torrents) {
+ for (var i = 0, len = torrents.length; i < len; ++i) {
+ o.arguments.ids.push(torrents[i].getId());
+ };
+ };
+ this.sendRequest(o, function () {
+ remote._controller.refreshTorrents();
+ });
+ },
+ verifyTorrents: function (torrent_ids, callback, context) {
+ this.sendTorrentActionRequests('torrent-verify', torrent_ids, callback, context);
+ },
+ reannounceTorrents: function (torrent_ids, callback, context) {
+ this.sendTorrentActionRequests('torrent-reannounce', torrent_ids, callback, context);
+ },
+ addTorrentByUrl: function (url, options) {
+ var remote = this;
+ if (url.match(/^[0-9a-f]{40}$/i)) {
+ url = 'magnet:?xt=urn:btih:' + url;
+ }
+ var o = {
+ method: 'torrent-add',
+ arguments: {
+ paused: (options.paused),
+ filename: url
+ }
+ };
+ this.sendRequest(o, function () {
+ remote._controller.refreshTorrents();
+ });
+ },
+ savePrefs: function (args) {
+ var remote = this;
+ var o = {
+ method: 'session-set',
+ arguments: args
+ };
+ this.sendRequest(o, function () {
+ remote._controller.loadDaemonPrefs();
+ });
+ },
+ updateBlocklist: function () {
+ var remote = this;
+ var o = {
+ method: 'blocklist-update'
+ };
+ this.sendRequest(o, function () {
+ remote._controller.loadDaemonPrefs();
+ });
+ },
- // Added queue calls
- moveTorrentsToTop: function(torrent_ids, callback, context) {
- this.sendTorrentActionRequests(RPC._QueueMoveTop, torrent_ids, callback, context);
- },
- moveTorrentsToBottom: function(torrent_ids, callback, context) {
- this.sendTorrentActionRequests(RPC._QueueMoveBottom, torrent_ids, callback, context);
- },
- moveTorrentsUp: function(torrent_ids, callback, context) {
- this.sendTorrentActionRequests(RPC._QueueMoveUp, torrent_ids, callback, context);
- },
- moveTorrentsDown: function(torrent_ids, callback, context) {
- this.sendTorrentActionRequests(RPC._QueueMoveDown, torrent_ids, callback, context);
- }
+ // Added queue calls
+ moveTorrentsToTop: function (torrent_ids, callback, context) {
+ this.sendTorrentActionRequests(RPC._QueueMoveTop, torrent_ids, callback, context);
+ },
+ moveTorrentsToBottom: function (torrent_ids, callback, context) {
+ this.sendTorrentActionRequests(RPC._QueueMoveBottom, torrent_ids, callback, context);
+ },
+ moveTorrentsUp: function (torrent_ids, callback, context) {
+ this.sendTorrentActionRequests(RPC._QueueMoveUp, torrent_ids, callback, context);
+ },
+ moveTorrentsDown: function (torrent_ids, callback, context) {
+ this.sendTorrentActionRequests(RPC._QueueMoveDown, torrent_ids, callback, context);
+ }
};
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*/
-function TorrentRendererHelper()
-{
-}
-
-TorrentRendererHelper.getProgressInfo = function(controller, t)
-{
- var pct, extra,
- s = t.getStatus(),
- seed_ratio_limit = t.seedRatioLimit(controller);
-
- if (t.needsMetaData())
- pct = t.getMetadataPercentComplete() * 100;
- else if (!t.isDone())
- pct = Math.round(t.getPercentDone() * 100);
- else if (seed_ratio_limit > 0 && t.isSeeding()) // don't split up the bar if paused or queued
- pct = Math.round(t.getUploadRatio() * 100 / seed_ratio_limit);
- else
- pct = 100;
-
- if (s === Torrent._StatusStopped)
- extra = 'paused';
- else if (s === Torrent._StatusDownloadWait)
- extra = 'leeching queued';
- else if (t.needsMetaData())
- extra = 'magnet';
- else if (s === Torrent._StatusDownload)
- extra = 'leeching';
- else if (s === Torrent._StatusSeedWait)
- extra = 'seeding queued';
- else if (s === Torrent._StatusSeed)
- extra = 'seeding';
- else
- extra = '';
-
- return {
- percent: pct,
- complete: [ 'torrent_progress_bar', 'complete', extra ].join(' '),
- incomplete: [ 'torrent_progress_bar', 'incomplete', extra ].join(' ')
- };
+function TorrentRendererHelper() {}
+
+TorrentRendererHelper.getProgressInfo = function (controller, t) {
+ var pct, extra;
+ var s = t.getStatus();
+ var seed_ratio_limit = t.seedRatioLimit(controller);
+
+ if (t.needsMetaData()) {
+ pct = t.getMetadataPercentComplete() * 100;
+ } else if (!t.isDone()) {
+ pct = Math.round(t.getPercentDone() * 100);
+ } else if (seed_ratio_limit > 0 && t.isSeeding()) { // don't split up the bar if paused or queued
+ pct = Math.round(t.getUploadRatio() * 100 / seed_ratio_limit);
+ } else {
+ pct = 100;
+ };
+
+ if (s === Torrent._StatusStopped) {
+ extra = 'paused';
+ } else if (s === Torrent._StatusDownloadWait) {
+ extra = 'leeching queued';
+ } else if (t.needsMetaData()) {
+ extra = 'magnet';
+ } else if (s === Torrent._StatusDownload) {
+ extra = 'leeching';
+ } else if (s === Torrent._StatusSeedWait) {
+ extra = 'seeding queued';
+ } else if (s === Torrent._StatusSeed) {
+ extra = 'seeding';
+ } else {
+ extra = '';
+ };
+
+ return {
+ percent: pct,
+ complete: ['torrent_progress_bar', 'complete', extra].join(' '),
+ incomplete: ['torrent_progress_bar', 'incomplete', extra].join(' ')
+ };
};
-TorrentRendererHelper.createProgressbar = function(classes)
-{
- var complete, incomplete, progressbar;
+TorrentRendererHelper.createProgressbar = function (classes) {
+ var complete, incomplete, progressbar;
- complete = document.createElement('div');
- complete.className = 'torrent_progress_bar complete';
+ complete = document.createElement('div');
+ complete.className = 'torrent_progress_bar complete';
- incomplete = document.createElement('div');
- incomplete.className = 'torrent_progress_bar incomplete';
+ incomplete = document.createElement('div');
+ incomplete.className = 'torrent_progress_bar incomplete';
- progressbar = document.createElement('div');
- progressbar.className = 'torrent_progress_bar_container ' + classes;
- progressbar.appendChild(complete);
- progressbar.appendChild(incomplete);
+ progressbar = document.createElement('div');
+ progressbar.className = 'torrent_progress_bar_container ' + classes;
+ progressbar.appendChild(complete);
+ progressbar.appendChild(incomplete);
- return { 'element': progressbar, 'complete': complete, 'incomplete': incomplete };
+ return {
+ 'element': progressbar,
+ 'complete': complete,
+ 'incomplete': incomplete
+ };
};
-TorrentRendererHelper.renderProgressbar = function(controller, t, progressbar)
-{
- var e, style, width, display,
- info = TorrentRendererHelper.getProgressInfo(controller, t);
-
- // update the complete progressbar
- e = progressbar.complete;
- style = e.style;
- width = '' + info.percent + '%';
- display = info.percent > 0 ? 'block' : 'none';
- if (style.width!==width || style.display!==display)
- $(e).css({ width: ''+info.percent+'%', display: display });
- if (e.className !== info.complete)
- e.className = info.complete;
-
- // update the incomplete progressbar
- e = progressbar.incomplete;
- display = (info.percent < 100) ? 'block' : 'none';
- if (e.style.display !== display)
- e.style.display = display;
- if (e.className !== info.incomplete)
- e.className = info.incomplete;
+TorrentRendererHelper.renderProgressbar = function (controller, t, progressbar) {
+ var e, style, width, display
+ var info = TorrentRendererHelper.getProgressInfo(controller, t);
+
+ // update the complete progressbar
+ e = progressbar.complete;
+ style = e.style;
+ width = '' + info.percent + '%';
+ display = info.percent > 0 ? 'block' : 'none';
+ if (style.width !== width || style.display !== display) {
+ $(e).css({
+ width: '' + info.percent + '%',
+ display: display
+ });
+ };
+
+ if (e.className !== info.complete) {
+ e.className = info.complete;
+ };
+
+ // update the incomplete progressbar
+ e = progressbar.incomplete;
+ display = (info.percent < 100) ? 'block' : 'none';
+
+ if (e.style.display !== display) {
+ e.style.display = display;
+ };
+
+ if (e.className !== info.incomplete) {
+ e.className = info.incomplete;
+ };
};
-TorrentRendererHelper.formatUL = function(t)
-{
- return '↑ ' + Transmission.fmt.speedBps(t.getUploadSpeed());
+TorrentRendererHelper.formatUL = function (t) {
+ return '↑ ' + Transmission.fmt.speedBps(t.getUploadSpeed());
};
-TorrentRendererHelper.formatDL = function(t)
-{
- return '↓ ' + Transmission.fmt.speedBps(t.getDownloadSpeed());
+TorrentRendererHelper.formatDL = function (t) {
+ return '↓ ' + Transmission.fmt.speedBps(t.getDownloadSpeed());
};
/****
-*****
-*****
-****/
-
-function TorrentRendererFull()
-{
-}
-TorrentRendererFull.prototype =
-{
- createRow: function()
- {
- var root, name, peers, progressbar, details, image, button;
-
- root = document.createElement('li');
- root.className = 'torrent';
-
- name = document.createElement('div');
- name.className = 'torrent_name';
-
- peers = document.createElement('div');
- peers.className = 'torrent_peer_details';
-
- progressbar = TorrentRendererHelper.createProgressbar('full');
-
- details = document.createElement('div');
- details.className = 'torrent_progress_details';
-
- image = document.createElement('div');
- button = document.createElement('a');
- button.appendChild(image);
-
- root.appendChild(name);
- root.appendChild(peers);
- root.appendChild(button);
- root.appendChild(progressbar.element);
- root.appendChild(details);
-
- root._name_container = name;
- root._peer_details_container = peers;
- root._progress_details_container = details;
- root._progressbar = progressbar;
- root._pause_resume_button_image = image;
- root._toggle_running_button = button;
-
- return root;
- },
-
- getPeerDetails: function(t)
- {
- var err,
- peer_count,
- webseed_count,
- fmt = Transmission.fmt;
-
- if ((err = t.getErrorMessage()))
- return err;
-
- if (t.isDownloading())
- {
- peer_count = t.getPeersConnected();
- webseed_count = t.getWebseedsSendingToUs();
-
- if (webseed_count && peer_count)
- {
- // Downloading from 2 of 3 peer(s) and 2 webseed(s)
- return [ 'Downloading from',
- t.getPeersSendingToUs(),
- 'of',
- fmt.countString('peer','peers',peer_count),
- 'and',
- fmt.countString('web seed','web seeds',webseed_count),
- '-',
- TorrentRendererHelper.formatDL(t),
- TorrentRendererHelper.formatUL(t) ].join(' ');
- }
- else if (webseed_count)
- {
- // Downloading from 2 webseed(s)
- return [ 'Downloading from',
- fmt.countString('web seed','web seeds',webseed_count),
- '-',
- TorrentRendererHelper.formatDL(t),
- TorrentRendererHelper.formatUL(t) ].join(' ');
- }
- else
- {
- // Downloading from 2 of 3 peer(s)
- return [ 'Downloading from',
- t.getPeersSendingToUs(),
- 'of',
- fmt.countString('peer','peers',peer_count),
- '-',
- TorrentRendererHelper.formatDL(t),
- TorrentRendererHelper.formatUL(t) ].join(' ');
- }
- }
-
- if (t.isSeeding())
- return [ 'Seeding to',
- t.getPeersGettingFromUs(),
- 'of',
- fmt.countString ('peer','peers',t.getPeersConnected()),
- '-',
- TorrentRendererHelper.formatUL(t) ].join(' ');
-
- if (t.isChecking())
- return [ 'Verifying local data (',
- Transmission.fmt.percentString(100.0 * t.getRecheckProgress()),
- '% tested)' ].join('');
-
- return t.getStateString();
- },
-
- getProgressDetails: function(controller, t)
- {
- if (t.needsMetaData()) {
- var MetaDataStatus = "retrieving";
- if (t.isStopped())
- MetaDataStatus = "needs";
- var percent = 100 * t.getMetadataPercentComplete();
- return [ "Magnetized transfer - " + MetaDataStatus + " metadata (",
- Transmission.fmt.percentString(percent),
- "%)" ].join('');
- }
-
- var c,
- sizeWhenDone = t.getSizeWhenDone(),
- totalSize = t.getTotalSize(),
- is_done = t.isDone() || t.isSeeding();
-
- if (is_done) {
- if (totalSize === sizeWhenDone) // seed: '698.05 MiB'
- c = [ Transmission.fmt.size(totalSize) ];
- else // partial seed: '127.21 MiB of 698.05 MiB (18.2%)'
- c = [ Transmission.fmt.size(sizeWhenDone),
- ' of ',
- Transmission.fmt.size(t.getTotalSize()),
- ' (', t.getPercentDoneStr(), '%)' ];
- // append UL stats: ', uploaded 8.59 GiB (Ratio: 12.3)'
- c.push(', uploaded ',
- Transmission.fmt.size(t.getUploadedEver()),
- ' (Ratio ',
- Transmission.fmt.ratioString(t.getUploadRatio()),
- ')');
- } else { // not done yet
- c = [ Transmission.fmt.size(sizeWhenDone - t.getLeftUntilDone()),
- ' of ', Transmission.fmt.size(sizeWhenDone),
- ' (', t.getPercentDoneStr(), '%)' ];
- }
-
- // maybe append eta
- if (!t.isStopped() && (!is_done || t.seedRatioLimit(controller)>0)) {
- c.push(' - ');
- var eta = t.getETA();
- if (eta < 0 || eta >= (999*60*60) /* arbitrary */)
- c.push('remaining time unknown');
- else
- c.push(Transmission.fmt.timeInterval(t.getETA()),
- ' remaining');
- }
-
- return c.join('');
- },
-
- render: function(controller, t, root)
- {
- // name
- setTextContent(root._name_container, t.getName());
-
- // progressbar
- TorrentRendererHelper.renderProgressbar(controller, t, root._progressbar);
-
- // peer details
- var has_error = t.getError() !== Torrent._ErrNone;
- var e = root._peer_details_container;
- $(e).toggleClass('error',has_error);
- setTextContent(e, this.getPeerDetails(t));
-
- // progress details
- e = root._progress_details_container;
- setTextContent(e, this.getProgressDetails(controller, t));
-
- // pause/resume button
- var is_stopped = t.isStopped();
- e = root._pause_resume_button_image;
- e.alt = is_stopped ? 'Resume' : 'Pause';
- e.className = is_stopped ? 'torrent_resume' : 'torrent_pause';
- }
+ *****
+ *****
+ ****/
+
+function TorrentRendererFull() {};
+TorrentRendererFull.prototype = {
+ createRow: function () {
+ var root, name, peers, progressbar, details, image, button;
+
+ root = document.createElement('li');
+ root.className = 'torrent';
+
+ name = document.createElement('div');
+ name.className = 'torrent_name';
+
+ peers = document.createElement('div');
+ peers.className = 'torrent_peer_details';
+
+ progressbar = TorrentRendererHelper.createProgressbar('full');
+
+ details = document.createElement('div');
+ details.className = 'torrent_progress_details';
+
+ image = document.createElement('div');
+ button = document.createElement('a');
+ button.appendChild(image);
+
+ root.appendChild(name);
+ root.appendChild(peers);
+ root.appendChild(button);
+ root.appendChild(progressbar.element);
+ root.appendChild(details);
+
+ root._name_container = name;
+ root._peer_details_container = peers;
+ root._progress_details_container = details;
+ root._progressbar = progressbar;
+ root._pause_resume_button_image = image;
+ root._toggle_running_button = button;
+
+ return root;
+ },
+
+ getPeerDetails: function (t) {
+ var err,
+ peer_count,
+ webseed_count,
+ fmt = Transmission.fmt;
+
+ if ((err = t.getErrorMessage())) {
+ return err;
+ };
+
+ if (t.isDownloading()) {
+ peer_count = t.getPeersConnected();
+ webseed_count = t.getWebseedsSendingToUs();
+
+ if (webseed_count && peer_count) {
+ // Downloading from 2 of 3 peer(s) and 2 webseed(s)
+ return ['Downloading from',
+ t.getPeersSendingToUs(),
+ 'of',
+ fmt.countString('peer', 'peers', peer_count),
+ 'and',
+ fmt.countString('web seed', 'web seeds', webseed_count),
+ '-',
+ TorrentRendererHelper.formatDL(t),
+ TorrentRendererHelper.formatUL(t)
+ ].join(' ');
+ } else if (webseed_count) {
+ // Downloading from 2 webseed(s)
+ return ['Downloading from',
+ fmt.countString('web seed', 'web seeds', webseed_count),
+ '-',
+ TorrentRendererHelper.formatDL(t),
+ TorrentRendererHelper.formatUL(t)
+ ].join(' ');
+ } else {
+ // Downloading from 2 of 3 peer(s)
+ return ['Downloading from',
+ t.getPeersSendingToUs(),
+ 'of',
+ fmt.countString('peer', 'peers', peer_count),
+ '-',
+ TorrentRendererHelper.formatDL(t),
+ TorrentRendererHelper.formatUL(t)
+ ].join(' ');
+ };
+ };
+
+ if (t.isSeeding()) {
+ return ['Seeding to', t.getPeersGettingFromUs(), 'of', fmt.countString('peer', 'peers', t.getPeersConnected()), '-', TorrentRendererHelper.formatUL(t)].join(' ');
+ };
+
+ if (t.isChecking()) {
+ return ['Verifying local data (', Transmission.fmt.percentString(100.0 * t.getRecheckProgress()), '% tested)'].join('');
+ }
+
+ return t.getStateString();
+ },
+
+ getProgressDetails: function (controller, t) {
+ if (t.needsMetaData()) {
+ var MetaDataStatus = "retrieving";
+ if (t.isStopped()) {
+ MetaDataStatus = "needs";
+ };
+ var percent = 100 * t.getMetadataPercentComplete();
+ return ["Magnetized transfer - " + MetaDataStatus + " metadata (",
+ Transmission.fmt.percentString(percent),
+ "%)"
+ ].join('');
+ }
+
+ var c;
+ var sizeWhenDone = t.getSizeWhenDone();
+ var totalSize = t.getTotalSize();
+ var is_done = t.isDone() || t.isSeeding();
+
+ if (is_done) {
+ if (totalSize === sizeWhenDone) {
+ // seed: '698.05 MiB'
+ c = [Transmission.fmt.size(totalSize)];
+ } else { // partial seed: '127.21 MiB of 698.05 MiB (18.2%)'
+ c = [Transmission.fmt.size(sizeWhenDone), ' of ', Transmission.fmt.size(t.getTotalSize()), ' (', t.getPercentDoneStr(), '%)'];
+ };
+ // append UL stats: ', uploaded 8.59 GiB (Ratio: 12.3)'
+ c.push(', uploaded ',
+ Transmission.fmt.size(t.getUploadedEver()),
+ ' (Ratio ',
+ Transmission.fmt.ratioString(t.getUploadRatio()),
+ ')');
+ } else { // not done yet
+ c = [Transmission.fmt.size(sizeWhenDone - t.getLeftUntilDone()),
+ ' of ', Transmission.fmt.size(sizeWhenDone),
+ ' (', t.getPercentDoneStr(), '%)'
+ ];
+ };
+
+ // maybe append eta
+ if (!t.isStopped() && (!is_done || t.seedRatioLimit(controller) > 0)) {
+ c.push(' - ');
+ var eta = t.getETA();
+ if (eta < 0 || eta >= (999 * 60 * 60) /* arbitrary */ ) {
+ c.push('remaining time unknown');
+ } else {
+ c.push(Transmission.fmt.timeInterval(t.getETA()), ' remaining');
+ };
+ };
+
+ return c.join('');
+ },
+
+ render: function (controller, t, root) {
+ // name
+ setTextContent(root._name_container, t.getName());
+
+ // progressbar
+ TorrentRendererHelper.renderProgressbar(controller, t, root._progressbar);
+
+ // peer details
+ var has_error = t.getError() !== Torrent._ErrNone;
+ var e = root._peer_details_container;
+ $(e).toggleClass('error', has_error);
+ setTextContent(e, this.getPeerDetails(t));
+
+ // progress details
+ e = root._progress_details_container;
+ setTextContent(e, this.getProgressDetails(controller, t));
+
+ // pause/resume button
+ var is_stopped = t.isStopped();
+ e = root._pause_resume_button_image;
+ e.alt = is_stopped ? 'Resume' : 'Pause';
+ e.className = is_stopped ? 'torrent_resume' : 'torrent_pause';
+ }
};
/****
-*****
-*****
-****/
-
-function TorrentRendererCompact()
-{
-}
-TorrentRendererCompact.prototype =
-{
- createRow: function()
- {
- var progressbar, details, name, root;
-
- progressbar = TorrentRendererHelper.createProgressbar('compact');
-
- details = document.createElement('div');
- details.className = 'torrent_peer_details compact';
-
- name = document.createElement('div');
- name.className = 'torrent_name compact';
-
- root = document.createElement('li');
- root.appendChild(progressbar.element);
- root.appendChild(details);
- root.appendChild(name);
- root.className = 'torrent compact';
- root._progressbar = progressbar;
- root._details_container = details;
- root._name_container = name;
- return root;
- },
-
- getPeerDetails: function(t)
- {
- var c;
- if ((c = t.getErrorMessage()))
- return c;
- if (t.isDownloading()) {
- var have_dn = t.getDownloadSpeed() > 0,
- have_up = t.getUploadSpeed() > 0;
- if (!have_up && !have_dn)
- return 'Idle';
- var s = '';
- if (have_dn)
- s += TorrentRendererHelper.formatDL(t);
- if (have_dn && have_up)
- s += ' '
- if (have_up)
- s += TorrentRendererHelper.formatUL(t);
- return s;
- }
- if (t.isSeeding())
- return [ 'Ratio: ',
- Transmission.fmt.ratioString(t.getUploadRatio()),
- ', ',
- TorrentRendererHelper.formatUL(t) ].join('');
- return t.getStateString();
- },
-
- render: function(controller, t, root)
- {
- // name
- var is_stopped = t.isStopped();
- var e = root._name_container;
- $(e).toggleClass('paused', is_stopped);
- setTextContent(e, t.getName());
-
- // peer details
- var has_error = t.getError() !== Torrent._ErrNone;
- e = root._details_container;
- $(e).toggleClass('error', has_error);
- setTextContent(e, this.getPeerDetails(t));
-
- // progressbar
- TorrentRendererHelper.renderProgressbar(controller, t, root._progressbar);
- }
+ *****
+ *****
+ ****/
+
+function TorrentRendererCompact() {};
+TorrentRendererCompact.prototype = {
+ createRow: function () {
+ var progressbar, details, name, root;
+
+ progressbar = TorrentRendererHelper.createProgressbar('compact');
+
+ details = document.createElement('div');
+ details.className = 'torrent_peer_details compact';
+
+ name = document.createElement('div');
+ name.className = 'torrent_name compact';
+
+ root = document.createElement('li');
+ root.appendChild(progressbar.element);
+ root.appendChild(details);
+ root.appendChild(name);
+ root.className = 'torrent compact';
+ root._progressbar = progressbar;
+ root._details_container = details;
+ root._name_container = name;
+ return root;
+ },
+
+ getPeerDetails: function (t) {
+ var c;
+ if ((c = t.getErrorMessage())) {
+ return c;
+ };
+ if (t.isDownloading()) {
+ var have_dn = t.getDownloadSpeed() > 0;
+ var have_up = t.getUploadSpeed() > 0;
+
+ if (!have_up && !have_dn) {
+ return 'Idle';
+ };
+ var s = '';
+ if (have_dn) {
+ s += TorrentRendererHelper.formatDL(t);
+ };
+ if (have_dn && have_up) {
+ s += ' ';
+ };
+ if (have_up) {
+ s += TorrentRendererHelper.formatUL(t);
+ };
+ return s;
+ };
+ if (t.isSeeding()) {
+ return ['Ratio: ', Transmission.fmt.ratioString(t.getUploadRatio()), ', ', TorrentRendererHelper.formatUL(t)].join('');
+ };
+ return t.getStateString();
+ },
+
+ render: function (controller, t, root) {
+ // name
+ var is_stopped = t.isStopped();
+ var e = root._name_container;
+ $(e).toggleClass('paused', is_stopped);
+ setTextContent(e, t.getName());
+
+ // peer details
+ var has_error = t.getError() !== Torrent._ErrNone;
+ e = root._details_container;
+ $(e).toggleClass('error', has_error);
+ setTextContent(e, this.getPeerDetails(t));
+
+ // progressbar
+ TorrentRendererHelper.renderProgressbar(controller, t, root._progressbar);
+ }
};
/****
-*****
-*****
-****/
-
-function TorrentRow(view, controller, torrent)
-{
- this.initialize(view, controller, torrent);
-}
-TorrentRow.prototype =
-{
- initialize: function(view, controller, torrent) {
- var row = this;
- this._view = view;
- this._torrent = torrent;
- this._element = view.createRow();
- this.render(controller);
- $(this._torrent).bind('dataChanged.torrentRowListener',function(){row.render(controller);});
-
- },
- getElement: function() {
- return this._element;
- },
- render: function(controller) {
- var tor = this.getTorrent();
- if (tor)
- this._view.render(controller, tor, this.getElement());
- },
- isSelected: function() {
- return this.getElement().className.indexOf('selected') !== -1;
- },
-
- getTorrent: function() {
- return this._torrent;
- },
- getTorrentId: function() {
- return this.getTorrent().getId();
- }
+ *****
+ *****
+ ****/
+
+function TorrentRow(view, controller, torrent) {
+ this.initialize(view, controller, torrent);
+};
+TorrentRow.prototype = {
+ initialize: function (view, controller, torrent) {
+ var row = this;
+ this._view = view;
+ this._torrent = torrent;
+ this._element = view.createRow();
+ this.render(controller);
+ $(this._torrent).bind('dataChanged.torrentRowListener', function () {
+ row.render(controller);
+ });
+
+ },
+ getElement: function () {
+ return this._element;
+ },
+ render: function (controller) {
+ var tor = this.getTorrent();
+ if (tor) {
+ this._view.render(controller, tor, this.getElement());
+ };
+ },
+ isSelected: function () {
+ return this.getElement().className.indexOf('selected') !== -1;
+ },
+
+ getTorrent: function () {
+ return this._torrent;
+ },
+ getTorrentId: function () {
+ return this.getTorrent().getId();
+ }
};
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*/
-function Torrent(data)
-{
- this.initialize(data);
-}
+function Torrent(data) {
+ this.initialize(data);
+};
/***
-****
-**** Constants
-****
-***/
+ ****
+ **** Constants
+ ****
+ ***/
// Torrent.fields.status
-Torrent._StatusStopped = 0;
-Torrent._StatusCheckWait = 1;
-Torrent._StatusCheck = 2;
-Torrent._StatusDownloadWait = 3;
-Torrent._StatusDownload = 4;
-Torrent._StatusSeedWait = 5;
-Torrent._StatusSeed = 6;
+Torrent._StatusStopped = 0;
+Torrent._StatusCheckWait = 1;
+Torrent._StatusCheck = 2;
+Torrent._StatusDownloadWait = 3;
+Torrent._StatusDownload = 4;
+Torrent._StatusSeedWait = 5;
+Torrent._StatusSeed = 6;
// Torrent.fields.seedRatioMode
-Torrent._RatioUseGlobal = 0;
-Torrent._RatioUseLocal = 1;
-Torrent._RatioUnlimited = 2;
+Torrent._RatioUseGlobal = 0;
+Torrent._RatioUseLocal = 1;
+Torrent._RatioUnlimited = 2;
// Torrent.fields.error
-Torrent._ErrNone = 0;
-Torrent._ErrTrackerWarning = 1;
-Torrent._ErrTrackerError = 2;
-Torrent._ErrLocalError = 3;
+Torrent._ErrNone = 0;
+Torrent._ErrTrackerWarning = 1;
+Torrent._ErrTrackerError = 2;
+Torrent._ErrLocalError = 3;
// TrackerStats' announceState
-Torrent._TrackerInactive = 0;
-Torrent._TrackerWaiting = 1;
-Torrent._TrackerQueued = 2;
-Torrent._TrackerActive = 3;
-
+Torrent._TrackerInactive = 0;
+Torrent._TrackerWaiting = 1;
+Torrent._TrackerQueued = 2;
+Torrent._TrackerActive = 3;
-Torrent.Fields = { };
+Torrent.Fields = {};
// commonly used fields which only need to be loaded once,
// either on startup or when a magnet finishes downloading its metadata
// finishes downloading its metadata
Torrent.Fields.Metadata = [
- 'addedDate',
- 'name',
- 'totalSize'
+ 'addedDate',
+ 'name',
+ 'totalSize'
];
// commonly used fields which need to be periodically refreshed
Torrent.Fields.Stats = [
- 'error',
- 'errorString',
- 'eta',
- 'isFinished',
- 'isStalled',
- 'leftUntilDone',
- 'metadataPercentComplete',
- 'peersConnected',
- 'peersGettingFromUs',
- 'peersSendingToUs',
- 'percentDone',
- 'queuePosition',
- 'rateDownload',
- 'rateUpload',
- 'recheckProgress',
- 'seedRatioMode',
- 'seedRatioLimit',
- 'sizeWhenDone',
- 'status',
- 'trackers',
- 'downloadDir',
- 'uploadedEver',
- 'uploadRatio',
- 'webseedsSendingToUs'
+ 'error',
+ 'errorString',
+ 'eta',
+ 'isFinished',
+ 'isStalled',
+ 'leftUntilDone',
+ 'metadataPercentComplete',
+ 'peersConnected',
+ 'peersGettingFromUs',
+ 'peersSendingToUs',
+ 'percentDone',
+ 'queuePosition',
+ 'rateDownload',
+ 'rateUpload',
+ 'recheckProgress',
+ 'seedRatioMode',
+ 'seedRatioLimit',
+ 'sizeWhenDone',
+ 'status',
+ 'trackers',
+ 'downloadDir',
+ 'uploadedEver',
+ 'uploadRatio',
+ 'webseedsSendingToUs'
];
// fields used by the inspector which only need to be loaded once
Torrent.Fields.InfoExtra = [
- 'comment',
- 'creator',
- 'dateCreated',
- 'files',
- 'hashString',
- 'isPrivate',
- 'pieceCount',
- 'pieceSize'
+ 'comment',
+ 'creator',
+ 'dateCreated',
+ 'files',
+ 'hashString',
+ 'isPrivate',
+ 'pieceCount',
+ 'pieceSize'
];
// fields used in the inspector which need to be periodically refreshed
Torrent.Fields.StatsExtra = [
- 'activityDate',
- 'corruptEver',
- 'desiredAvailable',
- 'downloadedEver',
- 'fileStats',
- 'haveUnchecked',
- 'haveValid',
- 'peers',
- 'startDate',
- 'trackerStats'
+ 'activityDate',
+ 'corruptEver',
+ 'desiredAvailable',
+ 'downloadedEver',
+ 'fileStats',
+ 'haveUnchecked',
+ 'haveValid',
+ 'peers',
+ 'startDate',
+ 'trackerStats'
];
/***
-****
-**** Methods
-****
-***/
-
-Torrent.prototype =
-{
- initialize: function(data)
- {
- this.fields = {};
- this.fieldObservers = {};
- this.refresh (data);
- },
-
- notifyOnFieldChange: function(field, callback) {
- this.fieldObservers[field] = this.fieldObservers[field] || [];
- this.fieldObservers[field].push(callback);
- },
-
- setField: function(o, name, value)
- {
- var i, observer;
-
- if (o[name] === value)
- return false;
- if (o == this.fields && this.fieldObservers[name] && this.fieldObservers[name].length) {
- for (i=0; observer=this.fieldObservers[name][i]; ++i) {
- observer.call(this, value, o[name], name);
- }
- }
- o[name] = value;
- return true;
- },
-
- // fields.files is an array of unions of RPC's "files" and "fileStats" objects.
- updateFiles: function(files)
- {
- var changed = false,
- myfiles = this.fields.files || [],
- keys = [ 'length', 'name', 'bytesCompleted', 'wanted', 'priority' ],
- i, f, j, key, myfile;
-
- for (i=0; f=files[i]; ++i) {
- myfile = myfiles[i] || {};
- for (j=0; key=keys[j]; ++j)
- if(key in f)
- changed |= this.setField(myfile,key,f[key]);
- myfiles[i] = myfile;
- }
- this.fields.files = myfiles;
- return changed;
- },
-
- collateTrackers: function(trackers)
- {
- var i, t, announces = [];
-
- for (i=0; t=trackers[i]; ++i)
- announces.push(t.announce.toLowerCase());
- return announces.join('\t');
- },
-
- refreshFields: function(data)
- {
- var key,
- changed = false;
-
- for (key in data) {
- switch (key) {
- case 'files':
- case 'fileStats': // merge files and fileStats together
- changed |= this.updateFiles(data[key]);
- break;
- case 'trackerStats': // 'trackerStats' is a superset of 'trackers'...
- changed |= this.setField(this.fields,'trackers',data[key]);
- break;
- case 'trackers': // ...so only save 'trackers' if we don't have it already
- if (!(key in this.fields))
- changed |= this.setField(this.fields,key,data[key]);
- break;
- default:
- changed |= this.setField(this.fields,key,data[key]);
- }
- }
-
- return changed;
- },
-
- refresh: function(data)
- {
- if (this.refreshFields(data))
- $(this).trigger('dataChanged', this);
- },
-
- /****
- *****
- ****/
-
- // simple accessors
- getComment: function() { return this.fields.comment; },
- getCreator: function() { return this.fields.creator; },
- getDateAdded: function() { return this.fields.addedDate; },
- getDateCreated: function() { return this.fields.dateCreated; },
- getDesiredAvailable: function() { return this.fields.desiredAvailable; },
- getDownloadDir: function() { return this.fields.downloadDir; },
- getDownloadSpeed: function() { return this.fields.rateDownload; },
- getDownloadedEver: function() { return this.fields.downloadedEver; },
- getError: function() { return this.fields.error; },
- getErrorString: function() { return this.fields.errorString; },
- getETA: function() { return this.fields.eta; },
- getFailedEver: function(i) { return this.fields.corruptEver; },
- getFile: function(i) { return this.fields.files[i]; },
- getFileCount: function() { return this.fields.files ? this.fields.files.length : 0; },
- getHashString: function() { return this.fields.hashString; },
- getHave: function() { return this.getHaveValid() + this.getHaveUnchecked() },
- getHaveUnchecked: function() { return this.fields.haveUnchecked; },
- getHaveValid: function() { return this.fields.haveValid; },
- getId: function() { return this.fields.id; },
- getLastActivity: function() { return this.fields.activityDate; },
- getLeftUntilDone: function() { return this.fields.leftUntilDone; },
- getMetadataPercentComplete: function() { return this.fields.metadataPercentComplete; },
- getName: function() { return this.fields.name || 'Unknown'; },
- getPeers: function() { return this.fields.peers; },
- getPeersConnected: function() { return this.fields.peersConnected; },
- getPeersGettingFromUs: function() { return this.fields.peersGettingFromUs; },
- getPeersSendingToUs: function() { return this.fields.peersSendingToUs; },
- getPieceCount: function() { return this.fields.pieceCount; },
- getPieceSize: function() { return this.fields.pieceSize; },
- getPrivateFlag: function() { return this.fields.isPrivate; },
- getQueuePosition: function() { return this.fields.queuePosition; },
- getRecheckProgress: function() { return this.fields.recheckProgress; },
- getSeedRatioLimit: function() { return this.fields.seedRatioLimit; },
- getSeedRatioMode: function() { return this.fields.seedRatioMode; },
- getSizeWhenDone: function() { return this.fields.sizeWhenDone; },
- getStartDate: function() { return this.fields.startDate; },
- getStatus: function() { return this.fields.status; },
- getTotalSize: function() { return this.fields.totalSize; },
- getTrackers: function() { return this.fields.trackers; },
- getUploadSpeed: function() { return this.fields.rateUpload; },
- getUploadRatio: function() { return this.fields.uploadRatio; },
- getUploadedEver: function() { return this.fields.uploadedEver; },
- getWebseedsSendingToUs: function() { return this.fields.webseedsSendingToUs; },
- isFinished: function() { return this.fields.isFinished; },
-
- // derived accessors
- hasExtraInfo: function() { return 'hashString' in this.fields; },
- isSeeding: function() { return this.getStatus() === Torrent._StatusSeed; },
- isStopped: function() { return this.getStatus() === Torrent._StatusStopped; },
- isChecking: function() { return this.getStatus() === Torrent._StatusCheck; },
- isDownloading: function() { return this.getStatus() === Torrent._StatusDownload; },
- isQueued: function() { return this.getStatus() === Torrent._StatusDownloadWait ||
- this.getStatus() === Torrent._StatusSeedWait; },
- isDone: function() { return this.getLeftUntilDone() < 1; },
- needsMetaData: function(){ return this.getMetadataPercentComplete() < 1; },
- getActivity: function() { return this.getDownloadSpeed() + this.getUploadSpeed(); },
- getPercentDoneStr: function() { return Transmission.fmt.percentString(100*this.getPercentDone()); },
- getPercentDone: function() { return this.fields.percentDone; },
- getStateString: function() {
- switch(this.getStatus()) {
- case Torrent._StatusStopped: return this.isFinished() ? 'Seeding complete' : 'Paused';
- case Torrent._StatusCheckWait: return 'Queued for verification';
- case Torrent._StatusCheck: return 'Verifying local data';
- case Torrent._StatusDownloadWait: return 'Queued for download';
- case Torrent._StatusDownload: return 'Downloading';
- case Torrent._StatusSeedWait: return 'Queued for seeding';
- case Torrent._StatusSeed: return 'Seeding';
- case null:
- case undefined: return 'Unknown';
- default: return 'Error';
- }
- },
- seedRatioLimit: function(controller){
- switch(this.getSeedRatioMode()) {
- case Torrent._RatioUseGlobal: return controller.seedRatioLimit();
- case Torrent._RatioUseLocal: return this.getSeedRatioLimit();
- default: return -1;
- }
- },
- getErrorMessage: function() {
- var str = this.getErrorString();
- switch(this.getError()) {
- case Torrent._ErrTrackerWarning:
- return 'Tracker returned a warning: ' + str;
- case Torrent._ErrTrackerError:
- return 'Tracker returned an error: ' + str;
- case Torrent._ErrLocalError:
- return 'Error: ' + str;
- default:
- return null;
- }
- },
- getCollatedName: function() {
- var f = this.fields;
- if (!f.collatedName && f.name)
- f.collatedName = f.name.toLowerCase();
- return f.collatedName || '';
- },
- getCollatedTrackers: function() {
- var f = this.fields;
- if (!f.collatedTrackers && f.trackers)
- f.collatedTrackers = this.collateTrackers(f.trackers);
- return f.collatedTrackers || '';
- },
-
- /****
- *****
- ****/
-
- testState: function(state)
- {
- var s = this.getStatus();
-
- switch(state)
- {
- case Prefs._FilterActive:
- return this.getPeersGettingFromUs() > 0
- || this.getPeersSendingToUs() > 0
- || this.getWebseedsSendingToUs() > 0
- || this.isChecking();
- case Prefs._FilterSeeding:
- return (s === Torrent._StatusSeed)
- || (s === Torrent._StatusSeedWait);
- case Prefs._FilterDownloading:
- return (s === Torrent._StatusDownload)
- || (s === Torrent._StatusDownloadWait);
- case Prefs._FilterPaused:
- return this.isStopped();
- case Prefs._FilterFinished:
- return this.isFinished();
- default:
- return true;
- }
- },
-
- /**
- * @param filter one of Prefs._Filter*
- * @param search substring to look for, or null
- * @return true if it passes the test, false if it fails
- */
- test: function(state, search, tracker)
- {
- // flter by state...
- var pass = this.testState(state);
-
- // maybe filter by text...
- if (pass && search && search.length)
- pass = this.getCollatedName().indexOf(search.toLowerCase()) !== -1;
-
- // maybe filter by tracker...
- if (pass && tracker && tracker.length)
- pass = this.getCollatedTrackers().indexOf(tracker) !== -1;
-
- return pass;
- }
+ ****
+ **** Methods
+ ****
+ ***/
+
+Torrent.prototype = {
+ initialize: function (data) {
+ this.fields = {};
+ this.fieldObservers = {};
+ this.refresh(data);
+ },
+
+ notifyOnFieldChange: function (field, callback) {
+ this.fieldObservers[field] = this.fieldObservers[field] || [];
+ this.fieldObservers[field].push(callback);
+ },
+
+ setField: function (o, name, value) {
+ var i, observer;
+
+ if (o[name] === value) {
+ return false;
+ };
+ if (o == this.fields && this.fieldObservers[name] && this.fieldObservers[name].length) {
+ for (i = 0; observer = this.fieldObservers[name][i]; ++i) {
+ observer.call(this, value, o[name], name);
+ };
+ };
+ o[name] = value;
+ return true;
+ },
+
+ // fields.files is an array of unions of RPC's "files" and "fileStats" objects.
+ updateFiles: function (files) {
+ var changed = false;
+ var myfiles = this.fields.files || [];
+ var keys = ['length', 'name', 'bytesCompleted', 'wanted', 'priority'];
+ var i, f, j, key, myfile;
+
+ for (i = 0; f = files[i]; ++i) {
+ myfile = myfiles[i] || {};
+ for (j = 0; key = keys[j]; ++j) {
+ if (key in f) {
+ changed |= this.setField(myfile, key, f[key]);
+ };
+ };
+ myfiles[i] = myfile;
+ }
+ this.fields.files = myfiles;
+ return changed;
+ },
+
+ collateTrackers: function (trackers) {
+ var i, t, announces = [];
+
+ for (i = 0; t = trackers[i]; ++i) {
+ announces.push(t.announce.toLowerCase());
+ };
+ return announces.join('\t');
+ },
+
+ refreshFields: function (data) {
+ var key;
+ var changed = false;
+
+ for (key in data) {
+ switch (key) {
+ case 'files':
+ case 'fileStats': // merge files and fileStats together
+ changed |= this.updateFiles(data[key]);
+ break;
+ case 'trackerStats': // 'trackerStats' is a superset of 'trackers'...
+ changed |= this.setField(this.fields, 'trackers', data[key]);
+ break;
+ case 'trackers': // ...so only save 'trackers' if we don't have it already
+ if (!(key in this.fields)) {
+ changed |= this.setField(this.fields, key, data[key]);
+ };
+ break;
+ default:
+ changed |= this.setField(this.fields, key, data[key]);
+ };
+ };
+
+ return changed;
+ },
+
+ refresh: function (data) {
+ if (this.refreshFields(data)) {
+ $(this).trigger('dataChanged', this);
+ };
+ },
+
+ /****
+ *****
+ ****/
+
+ // simple accessors
+ getComment: function () {
+ return this.fields.comment;
+ },
+ getCreator: function () {
+ return this.fields.creator;
+ },
+ getDateAdded: function () {
+ return this.fields.addedDate;
+ },
+ getDateCreated: function () {
+ return this.fields.dateCreated;
+ },
+ getDesiredAvailable: function () {
+ return this.fields.desiredAvailable;
+ },
+ getDownloadDir: function () {
+ return this.fields.downloadDir;
+ },
+ getDownloadSpeed: function () {
+ return this.fields.rateDownload;
+ },
+ getDownloadedEver: function () {
+ return this.fields.downloadedEver;
+ },
+ getError: function () {
+ return this.fields.error;
+ },
+ getErrorString: function () {
+ return this.fields.errorString;
+ },
+ getETA: function () {
+ return this.fields.eta;
+ },
+ getFailedEver: function (i) {
+ return this.fields.corruptEver;
+ },
+ getFile: function (i) {
+ return this.fields.files[i];
+ },
+ getFileCount: function () {
+ return this.fields.files ? this.fields.files.length : 0;
+ },
+ getHashString: function () {
+ return this.fields.hashString;
+ },
+ getHave: function () {
+ return this.getHaveValid() + this.getHaveUnchecked()
+ },
+ getHaveUnchecked: function () {
+ return this.fields.haveUnchecked;
+ },
+ getHaveValid: function () {
+ return this.fields.haveValid;
+ },
+ getId: function () {
+ return this.fields.id;
+ },
+ getLastActivity: function () {
+ return this.fields.activityDate;
+ },
+ getLeftUntilDone: function () {
+ return this.fields.leftUntilDone;
+ },
+ getMetadataPercentComplete: function () {
+ return this.fields.metadataPercentComplete;
+ },
+ getName: function () {
+ return this.fields.name || 'Unknown';
+ },
+ getPeers: function () {
+ return this.fields.peers;
+ },
+ getPeersConnected: function () {
+ return this.fields.peersConnected;
+ },
+ getPeersGettingFromUs: function () {
+ return this.fields.peersGettingFromUs;
+ },
+ getPeersSendingToUs: function () {
+ return this.fields.peersSendingToUs;
+ },
+ getPieceCount: function () {
+ return this.fields.pieceCount;
+ },
+ getPieceSize: function () {
+ return this.fields.pieceSize;
+ },
+ getPrivateFlag: function () {
+ return this.fields.isPrivate;
+ },
+ getQueuePosition: function () {
+ return this.fields.queuePosition;
+ },
+ getRecheckProgress: function () {
+ return this.fields.recheckProgress;
+ },
+ getSeedRatioLimit: function () {
+ return this.fields.seedRatioLimit;
+ },
+ getSeedRatioMode: function () {
+ return this.fields.seedRatioMode;
+ },
+ getSizeWhenDone: function () {
+ return this.fields.sizeWhenDone;
+ },
+ getStartDate: function () {
+ return this.fields.startDate;
+ },
+ getStatus: function () {
+ return this.fields.status;
+ },
+ getTotalSize: function () {
+ return this.fields.totalSize;
+ },
+ getTrackers: function () {
+ return this.fields.trackers;
+ },
+ getUploadSpeed: function () {
+ return this.fields.rateUpload;
+ },
+ getUploadRatio: function () {
+ return this.fields.uploadRatio;
+ },
+ getUploadedEver: function () {
+ return this.fields.uploadedEver;
+ },
+ getWebseedsSendingToUs: function () {
+ return this.fields.webseedsSendingToUs;
+ },
+ isFinished: function () {
+ return this.fields.isFinished;
+ },
+
+ // derived accessors
+ hasExtraInfo: function () {
+ return 'hashString' in this.fields;
+ },
+ isSeeding: function () {
+ return this.getStatus() === Torrent._StatusSeed;
+ },
+ isStopped: function () {
+ return this.getStatus() === Torrent._StatusStopped;
+ },
+ isChecking: function () {
+ return this.getStatus() === Torrent._StatusCheck;
+ },
+ isDownloading: function () {
+ return this.getStatus() === Torrent._StatusDownload;
+ },
+ isQueued: function () {
+ return this.getStatus() === Torrent._StatusDownloadWait || this.getStatus() === Torrent._StatusSeedWait;
+ },
+ isDone: function () {
+ return this.getLeftUntilDone() < 1;
+ },
+ needsMetaData: function () {
+ return this.getMetadataPercentComplete() < 1;
+ },
+ getActivity: function () {
+ return this.getDownloadSpeed() + this.getUploadSpeed();
+ },
+ getPercentDoneStr: function () {
+ return Transmission.fmt.percentString(100 * this.getPercentDone());
+ },
+ getPercentDone: function () {
+ return this.fields.percentDone;
+ },
+ getStateString: function () {
+ switch (this.getStatus()) {
+ case Torrent._StatusStopped:
+ return this.isFinished() ? 'Seeding complete' : 'Paused';
+ case Torrent._StatusCheckWait:
+ return 'Queued for verification';
+ case Torrent._StatusCheck:
+ return 'Verifying local data';
+ case Torrent._StatusDownloadWait:
+ return 'Queued for download';
+ case Torrent._StatusDownload:
+ return 'Downloading';
+ case Torrent._StatusSeedWait:
+ return 'Queued for seeding';
+ case Torrent._StatusSeed:
+ return 'Seeding';
+ case null:
+ case undefined:
+ return 'Unknown';
+ default:
+ return 'Error';
+ }
+ },
+ seedRatioLimit: function (controller) {
+ switch (this.getSeedRatioMode()) {
+ case Torrent._RatioUseGlobal:
+ return controller.seedRatioLimit();
+ case Torrent._RatioUseLocal:
+ return this.getSeedRatioLimit();
+ default:
+ return -1;
+ }
+ },
+ getErrorMessage: function () {
+ var str = this.getErrorString();
+ switch (this.getError()) {
+ case Torrent._ErrTrackerWarning:
+ return 'Tracker returned a warning: ' + str;
+ case Torrent._ErrTrackerError:
+ return 'Tracker returned an error: ' + str;
+ case Torrent._ErrLocalError:
+ return 'Error: ' + str;
+ default:
+ return null;
+ }
+ },
+ getCollatedName: function () {
+ var f = this.fields;
+ if (!f.collatedName && f.name) {
+ f.collatedName = f.name.toLowerCase();
+ };
+ return f.collatedName || '';
+ },
+ getCollatedTrackers: function () {
+ var f = this.fields;
+ if (!f.collatedTrackers && f.trackers) {
+ f.collatedTrackers = this.collateTrackers(f.trackers);
+ };
+ return f.collatedTrackers || '';
+ },
+
+ /****
+ *****
+ ****/
+
+ testState: function (state) {
+ var s = this.getStatus();
+
+ switch (state) {
+ case Prefs._FilterActive:
+ return this.getPeersGettingFromUs() > 0 || this.getPeersSendingToUs() > 0 || this.getWebseedsSendingToUs() > 0 || this.isChecking();
+ case Prefs._FilterSeeding:
+ return (s === Torrent._StatusSeed) || (s === Torrent._StatusSeedWait);
+ case Prefs._FilterDownloading:
+ return (s === Torrent._StatusDownload) || (s === Torrent._StatusDownloadWait);
+ case Prefs._FilterPaused:
+ return this.isStopped();
+ case Prefs._FilterFinished:
+ return this.isFinished();
+ default:
+ return true;
+ }
+ },
+
+ /**
+ * @param filter one of Prefs._Filter*
+ * @param search substring to look for, or null
+ * @return true if it passes the test, false if it fails
+ */
+ test: function (state, search, tracker) {
+ // flter by state...
+ var pass = this.testState(state);
+
+ // maybe filter by text...
+ if (pass && search && search.length) {
+ pass = this.getCollatedName().indexOf(search.toLowerCase()) !== -1;
+ };
+
+ // maybe filter by tracker...
+ if (pass && tracker && tracker.length) {
+ pass = this.getCollatedTrackers().indexOf(tracker) !== -1;
+ };
+
+ return pass;
+ }
};
-
/***
-****
-**** SORTING
-****
-***/
-
-Torrent.compareById = function(ta, tb)
-{
- return ta.getId() - tb.getId();
+ ****
+ **** SORTING
+ ****
+ ***/
+
+Torrent.compareById = function (ta, tb) {
+ return ta.getId() - tb.getId();
};
-Torrent.compareByName = function(ta, tb)
-{
- return ta.getCollatedName().localeCompare(tb.getCollatedName())
- || Torrent.compareById(ta, tb);
+Torrent.compareByName = function (ta, tb) {
+ return ta.getCollatedName().localeCompare(tb.getCollatedName()) || Torrent.compareById(ta, tb);
};
-Torrent.compareByQueue = function(ta, tb)
-{
- return ta.getQueuePosition() - tb.getQueuePosition();
+Torrent.compareByQueue = function (ta, tb) {
+ return ta.getQueuePosition() - tb.getQueuePosition();
};
-Torrent.compareByAge = function(ta, tb)
-{
- var a = ta.getDateAdded(),
- b = tb.getDateAdded();
+Torrent.compareByAge = function (ta, tb) {
+ var a = ta.getDateAdded();
+ var b = tb.getDateAdded();
- return (b - a) || Torrent.compareByQueue(ta, tb);
+ return (b - a) || Torrent.compareByQueue(ta, tb);
};
-Torrent.compareByState = function(ta, tb)
-{
- var a = ta.getStatus(),
- b = tb.getStatus();
+Torrent.compareByState = function (ta, tb) {
+ var a = ta.getStatus();
+ var b = tb.getStatus();
- return (b - a) || Torrent.compareByQueue(ta, tb);
+ return (b - a) || Torrent.compareByQueue(ta, tb);
};
-Torrent.compareByActivity = function(ta, tb)
-{
- var a = ta.getActivity(),
- b = tb.getActivity();
+Torrent.compareByActivity = function (ta, tb) {
+ var a = ta.getActivity();
+ var b = tb.getActivity();
- return (b - a) || Torrent.compareByState(ta, tb);
+ return (b - a) || Torrent.compareByState(ta, tb);
};
-Torrent.compareByRatio = function(ta, tb)
-{
- var a = ta.getUploadRatio(),
- b = tb.getUploadRatio();
-
- if (a < b) return 1;
- if (a > b) return -1;
- return Torrent.compareByState(ta, tb);
+Torrent.compareByRatio = function (ta, tb) {
+ var a = ta.getUploadRatio();
+ var b = tb.getUploadRatio();
+
+ if (a < b) {
+ return 1;
+ };
+ if (a > b) {
+ return -1;
+ };
+ return Torrent.compareByState(ta, tb);
};
-Torrent.compareByProgress = function(ta, tb)
-{
- var a = ta.getPercentDone(),
- b = tb.getPercentDone();
+Torrent.compareByProgress = function (ta, tb) {
+ var a = ta.getPercentDone();
+ var b = tb.getPercentDone();
- return (a - b) || Torrent.compareByRatio(ta, tb);
+ return (a - b) || Torrent.compareByRatio(ta, tb);
};
-Torrent.compareBySize = function(ta, tb)
-{
- var a = ta.getTotalSize(),
- b = tb.getTotalSize();
+Torrent.compareBySize = function (ta, tb) {
+ var a = ta.getTotalSize();
+ var b = tb.getTotalSize();
- return (a - b) || Torrent.compareByName(ta, tb);
+ return (a - b) || Torrent.compareByName(ta, tb);
};
-Torrent.compareTorrents = function(a, b, sortMethod, sortDirection)
-{
- var i;
-
- switch(sortMethod)
- {
- case Prefs._SortByActivity:
- i = Torrent.compareByActivity(a,b);
- break;
- case Prefs._SortByAge:
- i = Torrent.compareByAge(a,b);
- break;
- case Prefs._SortByQueue:
- i = Torrent.compareByQueue(a,b);
- break;
- case Prefs._SortByProgress:
- i = Torrent.compareByProgress(a,b);
- break;
- case Prefs._SortBySize:
- i = Torrent.compareBySize(a,b);
- break;
- case Prefs._SortByState:
- i = Torrent.compareByState(a,b);
- break;
- case Prefs._SortByRatio:
- i = Torrent.compareByRatio(a,b);
- break;
- default:
- i = Torrent.compareByName(a,b);
- break;
- }
-
- if (sortDirection === Prefs._SortDescending)
- i = -i;
-
- return i;
+Torrent.compareTorrents = function (a, b, sortMethod, sortDirection) {
+ var i;
+
+ switch (sortMethod) {
+ case Prefs._SortByActivity:
+ i = Torrent.compareByActivity(a, b);
+ break;
+ case Prefs._SortByAge:
+ i = Torrent.compareByAge(a, b);
+ break;
+ case Prefs._SortByQueue:
+ i = Torrent.compareByQueue(a, b);
+ break;
+ case Prefs._SortByProgress:
+ i = Torrent.compareByProgress(a, b);
+ break;
+ case Prefs._SortBySize:
+ i = Torrent.compareBySize(a, b);
+ break;
+ case Prefs._SortByState:
+ i = Torrent.compareByState(a, b);
+ break;
+ case Prefs._SortByRatio:
+ i = Torrent.compareByRatio(a, b);
+ break;
+ default:
+ i = Torrent.compareByName(a, b);
+ break;
+ };
+
+ if (sortDirection === Prefs._SortDescending) {
+ i = -i;
+ };
+
+ return i;
};
/**
* @param sortMethod one of Prefs._SortBy*
* @param sortDirection Prefs._SortAscending or Prefs._SortDescending
*/
-Torrent.sortTorrents = function(torrents, sortMethod, sortDirection)
-{
- switch(sortMethod)
- {
- case Prefs._SortByActivity:
- torrents.sort(this.compareByActivity);
- break;
- case Prefs._SortByAge:
- torrents.sort(this.compareByAge);
- break;
- case Prefs._SortByQueue:
- torrents.sort(this.compareByQueue);
- break;
- case Prefs._SortByProgress:
- torrents.sort(this.compareByProgress);
- break;
- case Prefs._SortBySize:
- torrents.sort(this.compareBySize);
- break;
- case Prefs._SortByState:
- torrents.sort(this.compareByState);
- break;
- case Prefs._SortByRatio:
- torrents.sort(this.compareByRatio);
- break;
- default:
- torrents.sort(this.compareByName);
- break;
- }
-
- if (sortDirection === Prefs._SortDescending)
- torrents.reverse();
-
- return torrents;
+Torrent.sortTorrents = function (torrents, sortMethod, sortDirection) {
+ switch (sortMethod) {
+ case Prefs._SortByActivity:
+ torrents.sort(this.compareByActivity);
+ break;
+ case Prefs._SortByAge:
+ torrents.sort(this.compareByAge);
+ break;
+ case Prefs._SortByQueue:
+ torrents.sort(this.compareByQueue);
+ break;
+ case Prefs._SortByProgress:
+ torrents.sort(this.compareByProgress);
+ break;
+ case Prefs._SortBySize:
+ torrents.sort(this.compareBySize);
+ break;
+ case Prefs._SortByState:
+ torrents.sort(this.compareByState);
+ break;
+ case Prefs._SortByRatio:
+ torrents.sort(this.compareByRatio);
+ break;
+ default:
+ torrents.sort(this.compareByName);
+ break;
+ };
+
+ if (sortDirection === Prefs._SortDescending) {
+ torrents.reverse();
+ };
+
+ return torrents;
};
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*/
-function Transmission()
-{
- this.initialize();
+function Transmission() {
+ this.initialize();
}
-Transmission.prototype =
-{
- /****
- *****
- ***** STARTUP
- *****
- ****/
-
- initialize: function()
- {
- var e;
-
- // Initialize the helper classes
- this.remote = new TransmissionRemote(this);
- this.inspector = new Inspector(this, this.remote);
- this.prefsDialog = new PrefsDialog(this.remote);
- $(this.prefsDialog).bind('closed', $.proxy(this.onPrefsDialogClosed,this));
-
- this.isMenuEnabled = !isMobileDevice;
-
- // Initialize the implementation fields
- this.filterText = '';
- this._torrents = {};
- this._rows = [];
- this.dirtyTorrents = {};
- this.uriCache = {};
-
- // Initialize the clutch preferences
- Prefs.getClutchPrefs(this);
-
- // Set up user events
- $('#toolbar-pause').click($.proxy(this.stopSelectedClicked,this));
- $('#toolbar-start').click($.proxy(this.startSelectedClicked,this));
- $('#toolbar-pause-all').click($.proxy(this.stopAllClicked,this));
- $('#toolbar-start-all').click($.proxy(this.startAllClicked,this));
- $('#toolbar-remove').click($.proxy(this.removeClicked,this));
- $('#toolbar-open').click($.proxy(this.openTorrentClicked,this));
-
- $('#prefs-button').click($.proxy(this.togglePrefsDialogClicked,this));
-
- $('#upload_confirm_button').click($.proxy(this.confirmUploadClicked,this));
- $('#upload_cancel_button').click($.proxy(this.hideUploadDialog,this));
-
- $('#rename_confirm_button').click($.proxy(this.confirmRenameClicked,this));
- $('#rename_cancel_button').click($.proxy(this.hideRenameDialog,this));
-
-
- $('#move_confirm_button').click($.proxy(this.confirmMoveClicked,this));
- $('#move_cancel_button').click($.proxy(this.hideMoveDialog,this));
-
- $('#turtle-button').click($.proxy(this.toggleTurtleClicked,this));
- $('#compact-button').click($.proxy(this.toggleCompactClicked,this));
-
- // tell jQuery to copy the dataTransfer property from events over if it exists
- jQuery.event.props.push("dataTransfer");
-
- $('#torrent_upload_form').submit(function() { $('#upload_confirm_button').click(); return false; });
-
- $('#toolbar-inspector').click($.proxy(this.toggleInspector,this));
-
- e = $('#filter-mode');
- e.val(this[Prefs._FilterMode]);
- e.change($.proxy(this.onFilterModeClicked,this));
- $('#filter-tracker').change($.proxy(this.onFilterTrackerClicked,this));
-
- if (!isMobileDevice) {
- $(document).bind('keydown', $.proxy(this.keyDown,this) );
- $(document).bind('keyup', $.proxy(this.keyUp, this) );
- $('#torrent_container').click( $.proxy(this.deselectAll,this) );
- $('#torrent_container').bind('dragover', $.proxy(this.dragenter,this));
- $('#torrent_container').bind('dragenter', $.proxy(this.dragenter,this));
- $('#torrent_container').bind('drop', $.proxy(this.drop,this));
- $('#inspector_link').click( $.proxy(this.toggleInspector,this) );
-
- this.setupSearchBox();
- this.createContextMenu();
- }
-
- if (this.isMenuEnabled)
- this.createSettingsMenu();
-
- e = {};
- e.torrent_list = $('#torrent_list')[0];
- e.toolbar_buttons = $('#toolbar ul li');
- e.toolbar_pause_button = $('#toolbar-pause')[0];
- e.toolbar_start_button = $('#toolbar-start')[0];
- e.toolbar_remove_button = $('#toolbar-remove')[0];
- this.elements = e;
-
- // Apply the prefs settings to the gui
- this.initializeSettings();
-
- // Get preferences & torrents from the daemon
- var async = false;
- this.loadDaemonPrefs(async);
- this.loadDaemonStats(async);
- this.initializeTorrents();
- this.refreshTorrents();
- this.togglePeriodicSessionRefresh(true);
-
- this.updateButtonsSoon();
- },
-
- loadDaemonPrefs: function(async) {
- this.remote.loadDaemonPrefs(function(data) {
- var o = data['arguments'];
- Prefs.getClutchPrefs(o);
- this.updateGuiFromSession(o);
- this.sessionProperties = o;
- }, this, async);
- },
-
- loadImages: function() {
- for (var i=0, row; row=arguments[i]; ++i)
- jQuery("<img>").attr("src", row);
- },
-
- /*
- * Load the clutch prefs and init the GUI according to those prefs
- */
- initializeSettings: function()
- {
- Prefs.getClutchPrefs(this);
-
- if (this.isMenuEnabled)
- {
- $('#sort_by_' + this[Prefs._SortMethod]).selectMenuItem();
-
- if (this[Prefs._SortDirection] === Prefs._SortDescending)
- $('#reverse_sort_order').selectMenuItem();
- }
-
- this.initCompactMode();
- },
-
- /*
- * Set up the search box
- */
- setupSearchBox: function()
- {
- var tr = this;
- var search_box = $('#torrent_search');
- search_box.bind('keyup click', function() {
- tr.setFilterText(this.value);
- });
- if (!$.browser.safari)
- {
- search_box.addClass('blur');
- search_box[0].value = 'Filter';
- search_box.bind('blur', function() {
- if (this.value === '') {
- $(this).addClass('blur');
- this.value = 'Filter';
- tr.setFilterText(null);
- }
- }).bind('focus', function() {
- if ($(this).is('.blur')) {
- this.value = '';
- $(this).removeClass('blur');
- }
- });
- }
- },
-
- /**
- * Create the torrent right-click menu
- */
- createContextMenu: function() {
- var tr = this;
- var bindings = {
- pause_selected: function() { tr.stopSelectedTorrents(); },
- resume_selected: function() { tr.startSelectedTorrents(false); },
- resume_now_selected: function() { tr.startSelectedTorrents(true); },
- move: function() { tr.moveSelectedTorrents(false); },
- remove: function() { tr.removeSelectedTorrents(); },
- remove_data: function() { tr.removeSelectedTorrentsAndData(); },
- verify: function() { tr.verifySelectedTorrents(); },
- rename: function() { tr.renameSelectedTorrents(); },
- reannounce: function() { tr.reannounceSelectedTorrents(); },
- move_top: function() { tr.moveTop(); },
- move_up: function() { tr.moveUp(); },
- move_down: function() { tr.moveDown(); },
- move_bottom: function() { tr.moveBottom(); },
- select_all: function() { tr.selectAll(); },
- deselect_all: function() { tr.deselectAll(); }
- };
-
- // Set up the context menu
- $("ul#torrent_list").contextmenu({
- delegate: ".torrent",
- menu: "#torrent_context_menu",
- preventSelect: true,
- taphold: true,
- show: { effect: "none" },
- hide: { effect: "none" },
- select: function(event, ui) { bindings[ui.cmd](); },
- beforeOpen: $.proxy(function(event, ui) {
- var element = $(event.currentTarget);
- var i = $('#torrent_list > li').index(element);
- if ((i!==-1) && !this._rows[i].isSelected())
- this.setSelectedRow(this._rows[i]);
-
- this.calculateTorrentStates(function(s) {
- var tl = $(event.target);
- tl.contextmenu("enableEntry", "pause_selected", s.activeSel > 0);
- tl.contextmenu("enableEntry", "resume_selected", s.pausedSel > 0);
- tl.contextmenu("enableEntry", "resume_now_selected", s.pausedSel > 0 || s.queuedSel > 0);
- tl.contextmenu("enableEntry", "rename", s.sel == 1);
- });
- }, this)
- });
- },
-
- createSettingsMenu: function() {
- $("#footer_super_menu").transMenu({
- open: function() { $("#settings_menu").addClass("selected"); },
- close: function() { $("#settings_menu").removeClass("selected"); },
- select: $.proxy(this.onMenuClicked, this)
- });
- $("#settings_menu").click(function(event) {
- $("#footer_super_menu").transMenu("open");
- });
- },
-
- /****
- *****
- ****/
-
- updateFreeSpaceInAddDialog: function()
- {
- var formdir = $('input#add-dialog-folder-input').val();
- this.remote.getFreeSpace (formdir, this.onFreeSpaceResponse, this);
- },
-
- onFreeSpaceResponse: function(dir, bytes)
- {
- var e, str, formdir;
-
- formdir = $('input#add-dialog-folder-input').val();
- if (formdir == dir)
- {
- e = $('label#add-dialog-folder-label');
- if (bytes > 0)
- str = ' <i>(' + Transmission.fmt.size(bytes) + ' Free)</i>';
- else
- str = '';
- e.html ('Destination folder' + str + ':');
- }
- },
-
-
- /****
- *****
- ***** UTILITIES
- *****
- ****/
-
- getAllTorrents: function()
- {
- var torrents = [];
- for (var key in this._torrents)
- torrents.push(this._torrents[key]);
- return torrents;
- },
-
- getTorrentIds: function(torrents)
- {
- return $.map(torrents.slice(0), function(t) {return t.getId();});
- },
-
- scrollToRow: function(row)
- {
- if (isMobileDevice) // FIXME: why?
- return;
-
- var list = $('#torrent_container'),
- scrollTop = list.scrollTop(),
- innerHeight = list.innerHeight(),
- offsetTop = row.getElement().offsetTop,
- offsetHeight = $(row.getElement()).outerHeight();
-
- if (offsetTop < scrollTop)
- list.scrollTop(offsetTop);
- else if (innerHeight + scrollTop < offsetTop + offsetHeight)
- list.scrollTop(offsetTop + offsetHeight - innerHeight);
- },
-
- seedRatioLimit: function() {
- var p = this.sessionProperties;
- if (p && p.seedRatioLimited)
- return p.seedRatioLimit;
- return -1;
- },
-
- setPref: function(key, val)
- {
- this[key] = val;
- Prefs.setValue(key, val);
- },
-
- /****
- *****
- ***** SELECTION
- *****
- ****/
-
- getSelectedRows: function() {
- return $.grep(this._rows, function(r) {return r.isSelected();});
- },
-
- getSelectedTorrents: function() {
- return $.map(this.getSelectedRows(),function(r) {
- return r.getTorrent();
- });
- },
-
- getSelectedTorrentIds: function() {
- return this.getTorrentIds(this.getSelectedTorrents());
- },
-
- setSelectedRow: function(row) {
- $(this.elements.torrent_list).children('.selected').removeClass('selected');
- this.selectRow(row);
- },
-
- selectRow: function(row) {
- $(row.getElement()).addClass('selected');
- this.callSelectionChangedSoon();
- },
-
- deselectRow: function(row) {
- $(row.getElement()).removeClass('selected');
- this.callSelectionChangedSoon();
- },
-
- selectAll: function() {
- $(this.elements.torrent_list).children().addClass('selected');
- this.callSelectionChangedSoon();
- },
- deselectAll: function() {
- $(this.elements.torrent_list).children('.selected').removeClass('selected');
- this.callSelectionChangedSoon();
- delete this._last_torrent_clicked;
- },
-
- indexOfLastTorrent: function() {
- for (var i=0, r; r=this._rows[i]; ++i)
- if (r.getTorrentId() === this._last_torrent_clicked)
- return i;
- return -1;
- },
-
- // Select a range from this row to the last clicked torrent
- selectRange: function(row)
- {
- var last = this.indexOfLastTorrent();
-
- if (last === -1)
- {
- this.selectRow(row);
- }
- else // select the range between the prevous & current
- {
- var next = this._rows.indexOf(row);
- var min = Math.min(last, next);
- var max = Math.max(last, next);
- for (var i=min; i<=max; ++i)
- this.selectRow(this._rows[i]);
- }
-
- this.callSelectionChangedSoon();
- },
-
- selectionChanged: function()
- {
- this.updateButtonStates();
-
- this.inspector.setTorrents(this.inspectorIsVisible() ? this.getSelectedTorrents() : []);
-
- clearTimeout(this.selectionChangedTimer);
- delete this.selectionChangedTimer;
-
- },
-
- callSelectionChangedSoon: function()
- {
- if (!this.selectionChangedTimer)
- {
- var callback = $.proxy(this.selectionChanged,this),
- msec = 200;
- this.selectionChangedTimer = setTimeout(callback, msec);
- }
- },
-
- /*--------------------------------------------
- *
- * E V E N T F U N C T I O N S
- *
- *--------------------------------------------*/
-
- /*
- * Process key event
- */
- keyDown: function(ev)
- {
- var handled = false,
- rows = this._rows,
- up = ev.keyCode === 38, // up key pressed
- dn = ev.keyCode === 40, // down key pressed
- shift = ev.keyCode === 16; // shift key pressed
-
- if ((up || dn) && rows.length)
- {
- var last = this.indexOfLastTorrent(),
- i = last,
- anchor = this._shift_index,
- r,
- min = 0,
- max = rows.length - 1;
-
- if (dn && (i+1 <= max))
- ++i;
- else if (up && (i-1 >= min))
- --i;
-
- var r = rows[i];
-
- if (anchor >= 0)
- {
- // user is extending the selection
- // with the shift + arrow keys...
- if ( ((anchor <= last) && (last < i))
- || ((anchor >= last) && (last > i)))
- {
- this.selectRow(r);
- }
- else if (((anchor >= last) && (i > last))
- || ((anchor <= last) && (last > i)))
- {
- this.deselectRow(rows[last]);
- }
- }
- else
- {
- if (ev.shiftKey)
- this.selectRange(r);
- else
- this.setSelectedRow(r);
- }
- this._last_torrent_clicked = r.getTorrentId();
- this.scrollToRow(r);
- handled = true;
- }
- else if (shift)
- {
- this._shift_index = this.indexOfLastTorrent();
- }
-
- return !handled;
- },
-
- keyUp: function(ev) {
- if (ev.keyCode === 16) // shift key pressed
- delete this._shift_index;
- },
-
- isButtonEnabled: function(ev) {
- var p = (ev.target || ev.srcElement).parentNode;
- return p.className!=='disabled'
- && p.parentNode.className!=='disabled';
- },
-
- stopSelectedClicked: function(ev) {
- if (this.isButtonEnabled(ev)) {
- this.stopSelectedTorrents();
- this.hideMobileAddressbar();
- }
- },
-
- startSelectedClicked: function(ev) {
- if (this.isButtonEnabled(ev)) {
- this.startSelectedTorrents(false);
- this.hideMobileAddressbar();
- }
- },
-
- stopAllClicked: function(ev) {
- if (this.isButtonEnabled(ev)) {
- this.stopAllTorrents();
- this.hideMobileAddressbar();
- }
- },
-
- startAllClicked: function(ev) {
- if (this.isButtonEnabled(ev)) {
- this.startAllTorrents(false);
- this.hideMobileAddressbar();
- }
- },
-
- openTorrentClicked: function(ev) {
- if (this.isButtonEnabled(ev)) {
- $('body').addClass('open_showing');
- this.uploadTorrentFile();
- this.updateButtonStates();
- }
- },
-
- dragenter: function(ev) {
- if (ev.dataTransfer && ev.dataTransfer.types) {
- var types = ["text/uri-list", "text/plain"];
- for (var i = 0; i < types.length; ++i) {
- // it would be better to look at the links here;
- // sadly, with Firefox, trying would throw.
- if (ev.dataTransfer.types.contains(types[i])) {
- ev.stopPropagation();
- ev.preventDefault();
- ev.dropEffect = "copy";
- return false;
- }
- }
- }
- else if (ev.dataTransfer) {
- ev.dataTransfer.dropEffect = "none";
- }
- return true;
- },
-
- drop: function(ev)
- {
- var i, uri, uris=null,
- types = ["text/uri-list", "text/plain"],
- paused = this.shouldAddedTorrentsStart();
-
- if (!ev.dataTransfer || !ev.dataTransfer.types)
- return true;
-
- for (i=0; !uris && i<types.length; ++i)
- if (ev.dataTransfer.types.contains(types[i]))
- uris = ev.dataTransfer.getData(types[i]).split("\n");
-
- for (i=0; uri=uris[i]; ++i) {
- if (/^#/.test(uri)) // lines which start with "#" are comments
- continue;
- if (/^[a-z-]+:/i.test(uri)) // close enough to a url
- this.remote.addTorrentByUrl(uri, paused);
- }
-
- ev.preventDefault();
- return false;
- },
-
- hideUploadDialog: function() {
- $('body.open_showing').removeClass('open_showing');
- $('#upload_container').hide();
- this.updateButtonStates();
- },
-
- confirmUploadClicked: function() {
- this.uploadTorrentFile(true);
- this.hideUploadDialog();
- },
-
- hideMoveDialog: function() {
- $('#move_container').hide();
- this.updateButtonStates();
- },
-
- confirmMoveClicked: function() {
- this.moveSelectedTorrents(true);
- this.hideUploadDialog();
- },
-
- hideRenameDialog: function() {
- $('body.open_showing').removeClass('open_showing');
- $('#rename_container').hide();
- },
-
- confirmRenameClicked: function() {
- var torrents = this.getSelectedTorrents();
- this.renameTorrent(torrents[0], $('input#torrent_rename_name').attr('value'));
- this.hideRenameDialog();
- },
-
- removeClicked: function(ev) {
- if (this.isButtonEnabled(ev)) {
- this.removeSelectedTorrents();
- this.hideMobileAddressbar();
- }
- },
-
- // turn the periodic ajax session refresh on & off
- togglePeriodicSessionRefresh: function(enabled) {
- clearInterval(this.sessionInterval);
- delete this.sessionInterval;
- if (enabled) {
- var callback = $.proxy(this.loadDaemonPrefs,this),
- msec = 8000;
- this.sessionInterval = setInterval(callback, msec);
- }
- },
-
- toggleTurtleClicked: function()
- {
- var o = {};
- o[RPC._TurtleState] = !$('#turtle-button').hasClass('selected');
- this.remote.savePrefs(o);
- },
-
- /*--------------------------------------------
- *
- * I N T E R F A C E F U N C T I O N S
- *
- *--------------------------------------------*/
-
- onPrefsDialogClosed: function() {
- $('#prefs-button').removeClass('selected');
- },
-
- togglePrefsDialogClicked: function(ev)
- {
- var e = $('#prefs-button');
-
- if (e.hasClass('selected'))
- this.prefsDialog.close();
- else {
- e.addClass('selected');
- this.prefsDialog.show();
- }
- },
-
- setFilterText: function(search) {
- this.filterText = search ? search.trim() : null;
- this.refilter(true);
- },
-
- setSortMethod: function(sort_method) {
- this.setPref(Prefs._SortMethod, sort_method);
- this.refilter(true);
- },
-
- setSortDirection: function(direction) {
- this.setPref(Prefs._SortDirection, direction);
- this.refilter(true);
- },
-
- onMenuClicked: function(event, ui)
- {
- var o, dir,
- id = ui.id,
- remote = this.remote,
- element = ui.target;
-
- if (ui.group == 'sort-mode')
- {
- element.selectMenuItem();
- this.setSortMethod(id.replace(/sort_by_/, ''));
- }
- else if (element.hasClass('upload-speed'))
- {
- o = {};
- o[RPC._UpSpeedLimit] = parseInt(element.text());
- o[RPC._UpSpeedLimited] = true;
- remote.savePrefs(o);
- }
- else if (element.hasClass('download-speed'))
- {
- o = {};
- o[RPC._DownSpeedLimit] = parseInt(element.text());
- o[RPC._DownSpeedLimited] = true;
- remote.savePrefs(o);
- }
- else switch (id)
- {
- case 'statistics':
- this.showStatsDialog();
- break;
-
- case 'about-button':
- o = 'Transmission ' + this.serverVersion;
- $('#about-dialog #about-title').html(o);
- $('#about-dialog').dialog({
- title: 'About',
- show: 'fade',
- hide: 'fade'
- });
- break;
-
- case 'homepage':
- window.open('http://www.transmissionbt.com/');
- break;
-
- case 'tipjar':
- window.open('http://www.transmissionbt.com/donate.php');
- break;
-
- case 'unlimited_download_rate':
- o = {};
- o[RPC._DownSpeedLimited] = false;
- remote.savePrefs(o);
- break;
-
- case 'limited_download_rate':
- o = {};
- o[RPC._DownSpeedLimited] = true;
- remote.savePrefs(o);
- break;
-
- case 'unlimited_upload_rate':
- o = {};
- o[RPC._UpSpeedLimited] = false;
- remote.savePrefs(o);
- break;
-
- case 'limited_upload_rate':
- o = {};
- o[RPC._UpSpeedLimited] = true;
- remote.savePrefs(o);
- break;
-
- case 'reverse_sort_order':
- if (element.menuItemIsSelected()) {
- dir = Prefs._SortAscending;
- element.deselectMenuItem();
- } else {
- dir = Prefs._SortDescending;
- element.selectMenuItem();
- }
- this.setSortDirection(dir);
- break;
-
- case 'toggle_notifications':
- Notifications && Notifications.toggle();
- break;
-
- default:
- console.log('unhandled: ' + id);
- break;
-
- }
- },
-
-
- onTorrentChanged: function(ev, tor)
- {
- // update our dirty fields
- this.dirtyTorrents[ tor.getId() ] = true;
-
- // enqueue ui refreshes
- this.refilterSoon();
- this.updateButtonsSoon();
- },
-
- updateFromTorrentGet: function(updates, removed_ids)
- {
- var i, o, t, id, needed, needinfo = [],
- callback, fields;
-
- for (i=0; o=updates[i]; ++i)
- {
- id = o.id;
- if ((t = this._torrents[id]))
- {
- needed = t.needsMetaData();
- t.refresh(o);
- if (needed && !t.needsMetaData())
- needinfo.push(id);
- }
- else {
- t = this._torrents[id] = new Torrent(o);
- this.dirtyTorrents[id] = true;
- callback = $.proxy(this.onTorrentChanged,this);
- $(t).bind('dataChanged',callback);
- // do we need more info for this torrent?
- if(!('name' in t.fields) || !('status' in t.fields))
- needinfo.push(id);
-
- t.notifyOnFieldChange('status', $.proxy(function (newValue, oldValue) {
- if (oldValue === Torrent._StatusDownload && (newValue == Torrent._StatusSeed || newValue == Torrent._StatusSeedWait)) {
- $(this).trigger('downloadComplete', [t]);
- } else if (oldValue === Torrent._StatusSeed && newValue === Torrent._StatusStopped && t.isFinished()) {
- $(this).trigger('seedingComplete', [t]);
- } else {
- $(this).trigger('statusChange', [t]);
- }
- }, this));
- }
- }
-
- if (needinfo.length) {
- // whee, new torrents! get their initial information.
- fields = ['id'].concat(Torrent.Fields.Metadata,
- Torrent.Fields.Stats);
- this.updateTorrents(needinfo, fields);
- this.refilterSoon();
- }
-
- if (removed_ids) {
- this.deleteTorrents(removed_ids);
- this.refilterSoon();
- }
- },
-
- updateTorrents: function(ids, fields)
- {
- this.remote.updateTorrents(ids, fields,
- this.updateFromTorrentGet, this);
- },
-
- refreshTorrents: function()
- {
- var callback = $.proxy(this.refreshTorrents,this),
- msec = this[Prefs._RefreshRate] * 1000,
- fields = ['id'].concat(Torrent.Fields.Stats);
-
- // send a request right now
- this.updateTorrents('recently-active', fields);
-
- // schedule the next request
- clearTimeout(this.refreshTorrentsTimeout);
- this.refreshTorrentsTimeout = setTimeout(callback, msec);
- },
-
- initializeTorrents: function()
- {
- var fields = ['id'].concat(Torrent.Fields.Metadata,
- Torrent.Fields.Stats);
- this.updateTorrents(null, fields);
- },
-
- onRowClicked: function(ev)
- {
- var meta_key = ev.metaKey || ev.ctrlKey,
- row = ev.currentTarget.row;
-
- // handle the per-row "torrent_resume" button
- if (ev.target.className === 'torrent_resume') {
- this.startTorrent(row.getTorrent());
- return;
- }
-
- // handle the per-row "torrent_pause" button
- if (ev.target.className === 'torrent_pause') {
- this.stopTorrent(row.getTorrent());
- return;
- }
-
- // Prevents click carrying to parent element
- // which deselects all on click
- ev.stopPropagation();
-
- if (isMobileDevice) {
- if (row.isSelected())
- this.setInspectorVisible(true);
- this.setSelectedRow(row);
-
- } else if (ev.shiftKey) {
- this.selectRange(row);
- // Need to deselect any selected text
- window.focus();
-
- // Apple-Click, not selected
- } else if (!row.isSelected() && meta_key) {
- this.selectRow(row);
-
- // Regular Click, not selected
- } else if (!row.isSelected()) {
- this.setSelectedRow(row);
-
- // Apple-Click, selected
- } else if (row.isSelected() && meta_key) {
- this.deselectRow(row);
-
- // Regular Click, selected
- } else if (row.isSelected()) {
- this.setSelectedRow(row);
- }
-
- this._last_torrent_clicked = row.getTorrentId();
- },
-
- deleteTorrents: function(ids)
- {
- var i, id;
-
- if (ids && ids.length)
- {
- for (i=0; id=ids[i]; ++i) {
- this.dirtyTorrents[id] = true;
- delete this._torrents[id];
- }
- this.refilter();
- }
- },
-
- shouldAddedTorrentsStart: function()
- {
- return this.prefsDialog.shouldAddedTorrentsStart();
- },
-
- /*
- * Select a torrent file to upload
- */
- uploadTorrentFile: function(confirmed)
- {
- var i, file,
- reader,
- fileInput = $('input#torrent_upload_file'),
- folderInput = $('input#add-dialog-folder-input'),
- startInput = $('input#torrent_auto_start'),
- urlInput = $('input#torrent_upload_url');
-
- if (!confirmed)
- {
- // update the upload dialog's fields
- fileInput.attr('value', '');
- urlInput.attr('value', '');
- startInput.attr('checked', this.shouldAddedTorrentsStart());
- folderInput.attr('value', $("#download-dir").val());
- folderInput.change($.proxy(this.updateFreeSpaceInAddDialog,this));
- this.updateFreeSpaceInAddDialog();
-
- // show the dialog
- $('#upload_container').show();
- urlInput.focus();
- }
- else
- {
- var paused = !startInput.is(':checked'),
- destination = folderInput.val(),
- remote = this.remote;
-
- jQuery.each (fileInput[0].files, function(i,file) {
- var reader = new FileReader();
- reader.onload = function(e) {
- var contents = e.target.result;
- var key = "base64,"
- var index = contents.indexOf (key);
- if (index > -1) {
- var metainfo = contents.substring (index + key.length);
- var o = {
- 'method': 'torrent-add',
- arguments: {
- 'paused': paused,
- 'download-dir': destination,
- 'metainfo': metainfo
- }
- };
- remote.sendRequest (o, function(response) {
- if (response.result != 'success')
- alert ('Error adding "' + file.name + '": ' + response.result);
- });
- }
- }
- reader.readAsDataURL (file);
- });
-
- var url = $('#torrent_upload_url').val();
- if (url != '') {
- if (url.match(/^[0-9a-f]{40}$/i))
- url = 'magnet:?xt=urn:btih:'+url;
- var o = {
- 'method': 'torrent-add',
- arguments: {
- 'paused': paused,
- 'download-dir': destination,
- 'filename': url
- }
- };
- remote.sendRequest (o, function(response) {
- if (response.result != 'success')
- alert ('Error adding "' + url + '": ' + response.result);
- });
- }
- }
- },
-
- promptSetLocation: function(confirmed, torrents) {
- if (! confirmed) {
- var path;
- if (torrents.length === 1) {
- path = torrents[0].getDownloadDir();
- } else {
- path = $("#download-dir").val();
- }
- $('input#torrent_path').attr('value', path);
- $('#move_container').show();
- $('#torrent_path').focus();
- } else {
- var ids = this.getTorrentIds(torrents);
- this.remote.moveTorrents(
- ids,
- $("input#torrent_path").val(),
- this.refreshTorrents,
- this);
- $('#move_container').hide();
- }
- },
-
- moveSelectedTorrents: function(confirmed) {
- var torrents = this.getSelectedTorrents();
- if (torrents.length)
- this.promptSetLocation(confirmed, torrents);
- },
-
- removeSelectedTorrents: function() {
- var torrents = this.getSelectedTorrents();
- if (torrents.length)
- this.promptToRemoveTorrents(torrents);
- },
-
- removeSelectedTorrentsAndData: function() {
- var torrents = this.getSelectedTorrents();
- if (torrents.length)
- this.promptToRemoveTorrentsAndData(torrents);
- },
-
- promptToRemoveTorrents: function(torrents) {
- if (torrents.length === 1)
- {
- var torrent = torrents[0],
- header = 'Remove ' + torrent.getName() + '?',
- message = 'Once removed, continuing the transfer will require the torrent file. Are you sure you want to remove it?';
- dialog.confirm(header, message, 'Remove', function() {
- transmission.removeTorrents(torrents);
- });
- }
- else
- {
- var header = 'Remove ' + torrents.length + ' transfers?',
- message = 'Once removed, continuing the transfers will require the torrent files. Are you sure you want to remove them?';
- dialog.confirm(header, message, 'Remove', function() {
- transmission.removeTorrents(torrents);
- });
- }
- },
-
- promptToRemoveTorrentsAndData:function(torrents)
- {
- if (torrents.length === 1)
- {
- var torrent = torrents[0],
- header = 'Remove ' + torrent.getName() + ' and delete data?',
- message = 'All data downloaded for this torrent will be deleted. Are you sure you want to remove it?';
- dialog.confirm(header, message, 'Remove', function() {
- transmission.removeTorrentsAndData(torrents);
- });
- }
- else
- {
- var header = 'Remove ' + torrents.length + ' transfers and delete data?',
- message = 'All data downloaded for these torrents will be deleted. Are you sure you want to remove them?';
- dialog.confirm(header, message, 'Remove', function() {
- transmission.removeTorrentsAndData(torrents);
- });
- }
- },
-
- removeTorrents: function(torrents) {
- var ids = this.getTorrentIds(torrents);
- this.remote.removeTorrents(ids, this.refreshTorrents, this);
- },
-
- removeTorrentsAndData: function(torrents) {
- this.remote.removeTorrentsAndData(torrents);
- },
-
- promptToRenameTorrent: function(torrent) {
- $('body').addClass('open_showing');
- $('input#torrent_rename_name').attr('value', torrent.getName());
- $('#rename_container').show();
- $('#torrent_rename_name').focus();
- },
-
- renameSelectedTorrents: function() {
- var torrents = this.getSelectedTorrents();
- if (torrents.length != 1)
- dialog.alert("Renaming", "You can rename only one torrent at a time.", "Ok");
- else
- this.promptToRenameTorrent(torrents[0]);
- },
-
- onTorrentRenamed: function(response) {
- var torrent;
- if ((response.result === 'success') &&
- (response.arguments) &&
- ((torrent = this._torrents[response.arguments.id])))
- {
- torrent.refresh(response.arguments);
- }
- },
-
- renameTorrent: function (torrent, newname) {
- var oldpath = torrent.getName();
- this.remote.renameTorrent([torrent.getId()], oldpath, newname, this.onTorrentRenamed, this);
- },
-
- verifySelectedTorrents: function() {
- this.verifyTorrents(this.getSelectedTorrents());
- },
-
- reannounceSelectedTorrents: function() {
- this.reannounceTorrents(this.getSelectedTorrents());
- },
-
- startAllTorrents: function(force) {
- this.startTorrents(this.getAllTorrents(), force);
- },
- startSelectedTorrents: function(force) {
- this.startTorrents(this.getSelectedTorrents(), force);
- },
- startTorrent: function(torrent) {
- this.startTorrents([ torrent ], false);
- },
-
- startTorrents: function(torrents, force) {
- this.remote.startTorrents(this.getTorrentIds(torrents), force,
- this.refreshTorrents, this);
- },
- verifyTorrent: function(torrent) {
- this.verifyTorrents([ torrent ]);
- },
- verifyTorrents: function(torrents) {
- this.remote.verifyTorrents(this.getTorrentIds(torrents),
- this.refreshTorrents, this);
- },
-
- reannounceTorrent: function(torrent) {
- this.reannounceTorrents([ torrent ]);
- },
- reannounceTorrents: function(torrents) {
- this.remote.reannounceTorrents(this.getTorrentIds(torrents),
- this.refreshTorrents, this);
- },
-
- stopAllTorrents: function() {
- this.stopTorrents(this.getAllTorrents());
- },
- stopSelectedTorrents: function() {
- this.stopTorrents(this.getSelectedTorrents());
- },
- stopTorrent: function(torrent) {
- this.stopTorrents([ torrent ]);
- },
- stopTorrents: function(torrents) {
- this.remote.stopTorrents(this.getTorrentIds(torrents),
- this.refreshTorrents, this);
- },
- changeFileCommand: function(torrentId, rowIndices, command) {
- this.remote.changeFileCommand(torrentId, rowIndices, command);
- },
-
- hideMobileAddressbar: function(delaySecs) {
- if (isMobileDevice && !scroll_timeout) {
- var callback = $.proxy(this.doToolbarHide,this),
- msec = delaySecs*1000 || 150;
- scroll_timeout = setTimeout(callback,msec);
- }
- },
- doToolbarHide: function() {
- window.scrollTo(0,1);
- scroll_timeout=null;
- },
-
- // Queue
- moveTop: function() {
- this.remote.moveTorrentsToTop(this.getSelectedTorrentIds(),
- this.refreshTorrents, this);
- },
- moveUp: function() {
- this.remote.moveTorrentsUp(this.getSelectedTorrentIds(),
- this.refreshTorrents, this);
- },
- moveDown: function() {
- this.remote.moveTorrentsDown(this.getSelectedTorrentIds(),
- this.refreshTorrents, this);
- },
- moveBottom: function() {
- this.remote.moveTorrentsToBottom(this.getSelectedTorrentIds(),
- this.refreshTorrents, this);
- },
-
- /***
- ****
- ***/
-
- updateGuiFromSession: function(o)
- {
- var limit, limited, e, b, text,
- fmt = Transmission.fmt,
- menu = $('#footer_super_menu');
-
- this.serverVersion = o.version;
-
- this.prefsDialog.set(o);
-
- if (RPC._TurtleState in o)
- {
- b = o[RPC._TurtleState];
- e = $('#turtle-button');
- text = [ 'Click to ', (b?'disable':'enable'),
- ' Temporary Speed Limits (',
- fmt.speed(o[RPC._TurtleUpSpeedLimit]),
- ' up,',
- fmt.speed(o[RPC._TurtleDownSpeedLimit]),
- ' down)' ].join('');
- e.toggleClass('selected', b);
- e.attr('title', text);
- }
-
- if (this.isMenuEnabled && (RPC._DownSpeedLimited in o)
- && (RPC._DownSpeedLimit in o))
- {
- limit = o[RPC._DownSpeedLimit];
- limited = o[RPC._DownSpeedLimited];
-
- e = menu.find('#limited_download_rate');
- e.html('Limit (' + fmt.speed(limit) + ')');
-
- if (!limited)
- e = menu.find('#unlimited_download_rate');
- e.selectMenuItem();
- }
-
- if (this.isMenuEnabled && (RPC._UpSpeedLimited in o)
- && (RPC._UpSpeedLimit in o))
- {
- limit = o[RPC._UpSpeedLimit];
- limited = o[RPC._UpSpeedLimited];
-
- e = menu.find('#limited_upload_rate');
- e.html('Limit (' + fmt.speed(limit) + ')');
-
- if (!limited)
- e = menu.find('#unlimited_upload_rate');
- e.selectMenuItem();
- }
- },
-
- updateStatusbar: function()
- {
- var u=0, d=0,
- i, row,
- fmt = Transmission.fmt,
- torrents = this.getAllTorrents();
-
- // up/down speed
- for (i=0; row=torrents[i]; ++i) {
- u += row.getUploadSpeed();
- d += row.getDownloadSpeed();
- }
-
- $('#speed-up-container').toggleClass('active', u>0 );
- $('#speed-up-label').text( fmt.speedBps( u ) );
-
- $('#speed-dn-container').toggleClass('active', d>0 );
- $('#speed-dn-label').text( fmt.speedBps( d ) );
-
- // visible torrents
- $('#filter-count').text( fmt.countString('Transfer','Transfers',this._rows.length ) );
- },
-
- setEnabled: function(key, flag)
- {
- $(key).toggleClass('disabled', !flag);
- },
-
- updateFilterSelect: function()
- {
- var i, names, name, str, o,
- e = $('#filter-tracker'),
- trackers = this.getTrackers();
-
- // build a sorted list of names
- names = [];
- for (name in trackers)
- names.push (name);
- names.sort();
-
- // build the new html
- if (!this.filterTracker)
- str = '<option value="all" selected="selected">All</option>';
- else
- str = '<option value="all">All</option>';
- for (i=0; name=names[i]; ++i) {
- o = trackers[name];
- str += '<option value="' + o.domain + '"';
- if (trackers[name].domain === this.filterTracker) str += ' selected="selected"';
- str += '>' + name + '</option>';
- }
-
- if (!this.filterTrackersStr || (this.filterTrackersStr !== str)) {
- this.filterTrackersStr = str;
- $('#filter-tracker').html(str);
- }
- },
-
- updateButtonsSoon: function()
- {
- if (!this.buttonRefreshTimer)
- {
- var callback = $.proxy(this.updateButtonStates,this),
- msec = 100;
- this.buttonRefreshTimer = setTimeout(callback, msec);
- }
- },
-
- calculateTorrentStates: function(callback)
- {
- var stats = {
- total: 0,
- active: 0,
- paused: 0,
- sel: 0,
- activeSel: 0,
- pausedSel: 0,
- queuedSel: 0
- };
-
- clearTimeout(this.buttonRefreshTimer);
- delete this.buttonRefreshTimer;
-
- for (var i=0, row; row=this._rows[i]; ++i) {
- var isStopped = row.getTorrent().isStopped();
- var isSelected = row.isSelected();
- var isQueued = row.getTorrent().isQueued();
- ++stats.total;
- if (!isStopped) ++stats.active;
- if (isStopped) ++stats.paused;
- if (isSelected) ++stats.sel;
- if (isSelected && !isStopped) ++stats.activeSel;
- if (isSelected && isStopped) ++stats.pausedSel;
- if (isSelected && isQueued) ++stats.queuedSel;
- }
-
- callback(stats);
- },
-
- updateButtonStates: function()
- {
- var tr = this,
- e = this.elements;
- this.calculateTorrentStates(function(s) {
- tr.setEnabled(e.toolbar_pause_button, s.activeSel > 0);
- tr.setEnabled(e.toolbar_start_button, s.pausedSel > 0);
- tr.setEnabled(e.toolbar_remove_button, s.sel > 0);
- });
- },
-
- /****
- *****
- ***** INSPECTOR
- *****
- ****/
-
- inspectorIsVisible: function()
- {
- return $('#torrent_inspector').is(':visible');
- },
- toggleInspector: function()
- {
- this.setInspectorVisible(!this.inspectorIsVisible());
- },
- setInspectorVisible: function(visible)
- {
- if (visible)
- this.inspector.setTorrents(this.getSelectedTorrents());
-
- // update the ui widgetry
- $('#torrent_inspector').toggle(visible);
- $('#toolbar-inspector').toggleClass('selected',visible);
- this.hideMobileAddressbar();
- if (isMobileDevice) {
- $('body').toggleClass('inspector_showing',visible);
- } else {
- var w = visible ? $('#torrent_inspector').outerWidth() + 1 + 'px' : '0px';
- $('#torrent_container')[0].style.right = w;
- }
- },
-
- /****
- *****
- ***** FILTER
- *****
- ****/
-
- refilterSoon: function()
- {
- if (!this.refilterTimer) {
- var tr = this,
- callback = function(){tr.refilter(false);},
- msec = 100;
- this.refilterTimer = setTimeout(callback, msec);
- }
- },
-
- sortRows: function(rows)
- {
- var i, tor, row,
- id2row = {},
- torrents = [];
-
- for (i=0; row=rows[i]; ++i) {
- tor = row.getTorrent();
- torrents.push(tor);
- id2row[ tor.getId() ] = row;
- }
-
- Torrent.sortTorrents(torrents, this[Prefs._SortMethod],
- this[Prefs._SortDirection]);
-
- for (i=0; tor=torrents[i]; ++i)
- rows[i] = id2row[ tor.getId() ];
- },
-
- refilter: function(rebuildEverything)
- {
- var i, e, id, t, row, tmp, rows, clean_rows, dirty_rows, frag,
- sort_mode = this[Prefs._SortMethod],
- sort_direction = this[Prefs._SortDirection],
- filter_mode = this[Prefs._FilterMode],
- filter_text = this.filterText,
- filter_tracker = this.filterTracker,
- renderer = this.torrentRenderer,
- list = this.elements.torrent_list,
- old_sel_count = $(list).children('.selected').length;
-
- this.updateFilterSelect();
-
- clearTimeout(this.refilterTimer);
- delete this.refilterTimer;
-
- if (rebuildEverything) {
- $(list).empty();
- this._rows = [];
- for (id in this._torrents)
- this.dirtyTorrents[id] = true;
- }
-
- // rows that overlap with dirtyTorrents need to be refiltered.
- // those that don't are 'clean' and don't need refiltering.
- clean_rows = [];
- dirty_rows = [];
- for (i=0; row=this._rows[i]; ++i) {
- if(row.getTorrentId() in this.dirtyTorrents)
- dirty_rows.push(row);
- else
- clean_rows.push(row);
- }
-
- // remove the dirty rows from the dom
- e = [];
- for (i=0; row=dirty_rows[i]; ++i)
- e.push (row.getElement());
- $(e).detach();
-
- // drop any dirty rows that don't pass the filter test
- tmp = [];
- for (i=0; row=dirty_rows[i]; ++i) {
- id = row.getTorrentId();
- t = this._torrents[ id ];
- if (t && t.test(filter_mode, filter_text, filter_tracker))
- tmp.push(row);
- delete this.dirtyTorrents[id];
- }
- dirty_rows = tmp;
-
- // make new rows for dirty torrents that pass the filter test
- // but don't already have a row
- for (id in this.dirtyTorrents) {
- t = this._torrents[id];
- if (t && t.test(filter_mode, filter_text, filter_tracker)) {
- row = new TorrentRow(renderer, this, t);
- e = row.getElement();
- e.row = row;
- dirty_rows.push(row);
- $(e).click($.proxy(this.onRowClicked,this));
- $(e).dblclick($.proxy(this.toggleInspector,this));
- }
- }
-
- // sort the dirty rows
- this.sortRows (dirty_rows);
-
- // now we have two sorted arrays of rows
- // and can do a simple two-way sorted merge.
- rows = [];
- var ci=0, cmax=clean_rows.length;
- var di=0, dmax=dirty_rows.length;
- frag = document.createDocumentFragment();
- while (ci!=cmax || di!=dmax)
- {
- var push_clean;
-
- if (ci==cmax)
- push_clean = false;
- else if (di==dmax)
- push_clean = true;
- else {
- var c = Torrent.compareTorrents(
- clean_rows[ci].getTorrent(),
- dirty_rows[di].getTorrent(),
- sort_mode, sort_direction);
- push_clean = (c < 0);
- }
-
- if (push_clean)
- rows.push(clean_rows[ci++]);
- else {
- row = dirty_rows[di++];
- e = row.getElement();
- if (ci !== cmax)
- list.insertBefore(e, clean_rows[ci].getElement());
- else
- frag.appendChild(e);
- rows.push(row);
- }
- }
- list.appendChild(frag);
-
- // update our implementation fields
- this._rows = rows;
- this.dirtyTorrents = {};
-
- // jquery's even/odd starts with 1 not 0, so invert its logic
- e = []
- for (i=0; row=rows[i]; ++i)
- e.push(row.getElement());
- $(e).filter(":odd").addClass('even');
- $(e).filter(":even").removeClass('even');
-
- // sync gui
- this.updateStatusbar();
- if (old_sel_count !== $(list).children('.selected').length)
- this.selectionChanged();
- },
-
- setFilterMode: function(mode)
- {
- // set the state
- this.setPref(Prefs._FilterMode, mode);
-
- // refilter
- this.refilter(true);
- },
-
- onFilterModeClicked: function(ev)
- {
- this.setFilterMode($('#filter-mode').val());
- },
-
- onFilterTrackerClicked: function(ev)
- {
- var tracker = $('#filter-tracker').val();
- this.setFilterTracker(tracker==='all' ? null : tracker);
- },
-
- setFilterTracker: function(domain)
- {
- // update which tracker is selected in the popup
- var key = domain ? this.getReadableDomain(domain) : 'all',
- id = '#show-tracker-' + key;
- $(id).addClass('selected').siblings().removeClass('selected');
-
- this.filterTracker = domain;
- this.refilter(true);
- },
-
- // example: "tracker.ubuntu.com" returns "ubuntu.com"
- getDomainName: function(host)
- {
- var dot = host.indexOf('.');
- if (dot !== host.lastIndexOf('.'))
- host = host.slice(dot+1);
- return host;
- },
-
- // example: "ubuntu.com" returns "Ubuntu"
- getReadableDomain: function(name)
- {
- if (name.length)
- name = name.charAt(0).toUpperCase() + name.slice(1);
- var dot = name.indexOf('.');
- if (dot !== -1)
- name = name.slice(0, dot);
- return name;
- },
-
- getTrackers: function()
- {
- var ret = {};
-
- var torrents = this.getAllTorrents();
- for (var i=0, torrent; torrent=torrents[i]; ++i)
- {
- var names = [];
- var trackers = torrent.getTrackers();
- for (var j=0, tracker; tracker=trackers[j]; ++j)
- {
- var uri, announce = tracker.announce;
-
- if (announce in this.uriCache)
- uri = this.uriCache[announce];
- else {
- uri = this.uriCache[announce] = parseUri (announce);
- uri.domain = this.getDomainName (uri.host);
- uri.name = this.getReadableDomain (uri.domain);
- }
-
- if (!(uri.name in ret))
- ret[uri.name] = { 'uri': uri,
- 'domain': uri.domain,
- 'count': 0 };
-
- if (names.indexOf(uri.name) === -1)
- names.push(uri.name);
- }
- for (var j=0, name; name=names[j]; ++j)
- ret[name].count++;
- }
-
- return ret;
- },
-
- /***
- ****
- **** Compact Mode
- ****
- ***/
-
- toggleCompactClicked: function()
- {
- this.setCompactMode(!this[Prefs._CompactDisplayState]);
- },
- setCompactMode: function(is_compact)
- {
- var key = Prefs._CompactDisplayState,
- was_compact = this[key];
-
- if (was_compact !== is_compact) {
- this.setPref(key, is_compact);
- this.onCompactModeChanged();
- }
- },
- initCompactMode: function()
- {
- this.onCompactModeChanged();
- },
- onCompactModeChanged: function()
- {
- var compact = this[Prefs._CompactDisplayState];
-
- // update the ui: footer button
- $("#compact-button").toggleClass('selected',compact);
-
- // update the ui: torrent list
- this.torrentRenderer = compact ? new TorrentRendererCompact()
- : new TorrentRendererFull();
- this.refilter(true);
- },
-
- /***
- ****
- **** Statistics
- ****
- ***/
-
- // turn the periodic ajax stats refresh on & off
- togglePeriodicStatsRefresh: function(enabled) {
- clearInterval(this.statsInterval);
- delete this.statsInterval;
- if (enabled) {
- var callback = $.proxy(this.loadDaemonStats,this),
- msec = 5000;
- this.statsInterval = setInterval(callback, msec);
- }
- },
-
- loadDaemonStats: function(async) {
- this.remote.loadDaemonStats(function(data) {
- this.updateStats(data['arguments']);
- }, this, async);
- },
-
- // Process new session stats from the server
- updateStats: function(stats)
- {
- var s, ratio,
- fmt = Transmission.fmt;
-
- s = stats["current-stats"];
- ratio = Math.ratio(s.uploadedBytes,s.downloadedBytes);
- $('#stats-session-uploaded').html(fmt.size(s.uploadedBytes));
- $('#stats-session-downloaded').html(fmt.size(s.downloadedBytes));
- $('#stats-session-ratio').html(fmt.ratioString(ratio));
- $('#stats-session-duration').html(fmt.timeInterval(s.secondsActive));
-
- s = stats["cumulative-stats"];
- ratio = Math.ratio(s.uploadedBytes,s.downloadedBytes);
- $('#stats-total-count').html(s.sessionCount + " times");
- $('#stats-total-uploaded').html(fmt.size(s.uploadedBytes));
- $('#stats-total-downloaded').html(fmt.size(s.downloadedBytes));
- $('#stats-total-ratio').html(fmt.ratioString(ratio));
- $('#stats-total-duration').html(fmt.timeInterval(s.secondsActive));
- },
-
-
- showStatsDialog: function() {
- this.loadDaemonStats();
- this.hideMobileAddressbar();
- this.togglePeriodicStatsRefresh(true);
- $('#stats-dialog').dialog({
- close: $.proxy(this.onStatsDialogClosed,this),
- show: 'fade',
- hide: 'fade',
- title: 'Statistics'
- });
- },
-
- onStatsDialogClosed: function() {
- this.hideMobileAddressbar();
- this.togglePeriodicStatsRefresh(false);
- }
+Transmission.prototype = {
+ /****
+ *****
+ ***** STARTUP
+ *****
+ ****/
+
+ initialize: function () {
+ var e;
+
+ // Initialize the helper classes
+ this.remote = new TransmissionRemote(this);
+ this.inspector = new Inspector(this, this.remote);
+ this.prefsDialog = new PrefsDialog(this.remote);
+ $(this.prefsDialog).bind('closed', $.proxy(this.onPrefsDialogClosed, this));
+
+ this.isMenuEnabled = !isMobileDevice;
+
+ // Initialize the implementation fields
+ this.filterText = '';
+ this._torrents = {};
+ this._rows = [];
+ this.dirtyTorrents = {};
+ this.uriCache = {};
+
+ // Initialize the clutch preferences
+ Prefs.getClutchPrefs(this);
+
+ // Set up user events
+ $('#toolbar-pause').click($.proxy(this.stopSelectedClicked, this));
+ $('#toolbar-start').click($.proxy(this.startSelectedClicked, this));
+ $('#toolbar-pause-all').click($.proxy(this.stopAllClicked, this));
+ $('#toolbar-start-all').click($.proxy(this.startAllClicked, this));
+ $('#toolbar-remove').click($.proxy(this.removeClicked, this));
+ $('#toolbar-open').click($.proxy(this.openTorrentClicked, this));
+
+ $('#prefs-button').click($.proxy(this.togglePrefsDialogClicked, this));
+
+ $('#upload_confirm_button').click($.proxy(this.confirmUploadClicked, this));
+ $('#upload_cancel_button').click($.proxy(this.hideUploadDialog, this));
+
+ $('#rename_confirm_button').click($.proxy(this.confirmRenameClicked, this));
+ $('#rename_cancel_button').click($.proxy(this.hideRenameDialog, this));
+
+ $('#move_confirm_button').click($.proxy(this.confirmMoveClicked, this));
+ $('#move_cancel_button').click($.proxy(this.hideMoveDialog, this));
+
+ $('#turtle-button').click($.proxy(this.toggleTurtleClicked, this));
+ $('#compact-button').click($.proxy(this.toggleCompactClicked, this));
+
+ // tell jQuery to copy the dataTransfer property from events over if it exists
+ jQuery.event.props.push("dataTransfer");
+
+ $('#torrent_upload_form').submit(function () {
+ $('#upload_confirm_button').click();
+ return false;
+ });
+
+ $('#toolbar-inspector').click($.proxy(this.toggleInspector, this));
+
+ e = $('#filter-mode');
+ e.val(this[Prefs._FilterMode]);
+ e.change($.proxy(this.onFilterModeClicked, this));
+ $('#filter-tracker').change($.proxy(this.onFilterTrackerClicked, this));
+
+ if (!isMobileDevice) {
+ $(document).bind('keydown', $.proxy(this.keyDown, this));
+ $(document).bind('keyup', $.proxy(this.keyUp, this));
+ $('#torrent_container').click($.proxy(this.deselectAll, this));
+ $('#torrent_container').bind('dragover', $.proxy(this.dragenter, this));
+ $('#torrent_container').bind('dragenter', $.proxy(this.dragenter, this));
+ $('#torrent_container').bind('drop', $.proxy(this.drop, this));
+ $('#inspector_link').click($.proxy(this.toggleInspector, this));
+
+ this.setupSearchBox();
+ this.createContextMenu();
+ };
+
+ if (this.isMenuEnabled) {
+ this.createSettingsMenu();
+ };
+
+ e = {};
+ e.torrent_list = $('#torrent_list')[0];
+ e.toolbar_buttons = $('#toolbar ul li');
+ e.toolbar_pause_button = $('#toolbar-pause')[0];
+ e.toolbar_start_button = $('#toolbar-start')[0];
+ e.toolbar_remove_button = $('#toolbar-remove')[0];
+ this.elements = e;
+
+ // Apply the prefs settings to the gui
+ this.initializeSettings();
+
+ // Get preferences & torrents from the daemon
+ var async = false;
+ this.loadDaemonPrefs(async);
+ this.loadDaemonStats(async);
+ this.initializeTorrents();
+ this.refreshTorrents();
+ this.togglePeriodicSessionRefresh(true);
+
+ this.updateButtonsSoon();
+ },
+
+ loadDaemonPrefs: function (async) {
+ this.remote.loadDaemonPrefs(function (data) {
+ var o = data['arguments'];
+ Prefs.getClutchPrefs(o);
+ this.updateGuiFromSession(o);
+ this.sessionProperties = o;
+ }, this, async);
+ },
+
+ loadImages: function () {
+ for (var i = 0, row; row = arguments[i]; ++i) {
+ jQuery("<img>").attr("src", row);
+ };
+ },
+
+ /*
+ * Load the clutch prefs and init the GUI according to those prefs
+ */
+ initializeSettings: function () {
+ Prefs.getClutchPrefs(this);
+
+ if (this.isMenuEnabled) {
+ $('#sort_by_' + this[Prefs._SortMethod]).selectMenuItem();
+
+ if (this[Prefs._SortDirection] === Prefs._SortDescending) {
+ $('#reverse_sort_order').selectMenuItem();
+ };
+ }
+
+ this.initCompactMode();
+ },
+
+ /*
+ * Set up the search box
+ */
+ setupSearchBox: function () {
+ var tr = this;
+ var search_box = $('#torrent_search');
+ search_box.bind('keyup click', function () {
+ tr.setFilterText(this.value);
+ });
+ if (!$.browser.safari) {
+ search_box.addClass('blur');
+ search_box[0].value = 'Filter';
+ search_box.bind('blur', function () {
+ if (this.value === '') {
+ $(this).addClass('blur');
+ this.value = 'Filter';
+ tr.setFilterText(null);
+ };
+ }).bind('focus', function () {
+ if ($(this).is('.blur')) {
+ this.value = '';
+ $(this).removeClass('blur');
+ }
+ });
+ }
+ },
+
+ /**
+ * Create the torrent right-click menu
+ */
+ createContextMenu: function () {
+ var tr = this;
+ var bindings = {
+ pause_selected: function () {
+ tr.stopSelectedTorrents();
+ },
+ resume_selected: function () {
+ tr.startSelectedTorrents(false);
+ },
+ resume_now_selected: function () {
+ tr.startSelectedTorrents(true);
+ },
+ move: function () {
+ tr.moveSelectedTorrents(false);
+ },
+ remove: function () {
+ tr.removeSelectedTorrents();
+ },
+ remove_data: function () {
+ tr.removeSelectedTorrentsAndData();
+ },
+ verify: function () {
+ tr.verifySelectedTorrents();
+ },
+ rename: function () {
+ tr.renameSelectedTorrents();
+ },
+ reannounce: function () {
+ tr.reannounceSelectedTorrents();
+ },
+ move_top: function () {
+ tr.moveTop();
+ },
+ move_up: function () {
+ tr.moveUp();
+ },
+ move_down: function () {
+ tr.moveDown();
+ },
+ move_bottom: function () {
+ tr.moveBottom();
+ },
+ select_all: function () {
+ tr.selectAll();
+ },
+ deselect_all: function () {
+ tr.deselectAll();
+ }
+ };
+
+ // Set up the context menu
+ $("ul#torrent_list").contextmenu({
+ delegate: ".torrent",
+ menu: "#torrent_context_menu",
+ preventSelect: true,
+ taphold: true,
+ show: {
+ effect: "none"
+ },
+ hide: {
+ effect: "none"
+ },
+ select: function (event, ui) {
+ bindings[ui.cmd]();
+ },
+ beforeOpen: $.proxy(function (event, ui) {
+ var element = $(event.currentTarget);
+ var i = $('#torrent_list > li').index(element);
+ if ((i !== -1) && !this._rows[i].isSelected()) {
+ this.setSelectedRow(this._rows[i]);
+ };
+
+ this.calculateTorrentStates(function (s) {
+ var tl = $(event.target);
+ tl.contextmenu("enableEntry", "pause_selected", s.activeSel > 0);
+ tl.contextmenu("enableEntry", "resume_selected", s.pausedSel > 0);
+ tl.contextmenu("enableEntry", "resume_now_selected", s.pausedSel > 0 || s.queuedSel > 0);
+ tl.contextmenu("enableEntry", "rename", s.sel == 1);
+ });
+ }, this)
+ });
+ },
+
+ createSettingsMenu: function () {
+ $("#footer_super_menu").transMenu({
+ open: function () {
+ $("#settings_menu").addClass("selected");
+ },
+ close: function () {
+ $("#settings_menu").removeClass("selected");
+ },
+ select: $.proxy(this.onMenuClicked, this)
+ });
+ $("#settings_menu").click(function (event) {
+ $("#footer_super_menu").transMenu("open");
+ });
+ },
+
+ /****
+ *****
+ ****/
+
+ updateFreeSpaceInAddDialog: function () {
+ var formdir = $('input#add-dialog-folder-input').val();
+ this.remote.getFreeSpace(formdir, this.onFreeSpaceResponse, this);
+ },
+
+ onFreeSpaceResponse: function (dir, bytes) {
+ var e, str, formdir;
+
+ formdir = $('input#add-dialog-folder-input').val();
+ if (formdir == dir) {
+ e = $('label#add-dialog-folder-label');
+ if (bytes > 0) {
+ str = ' <i>(' + Transmission.fmt.size(bytes) + ' Free)</i>';
+ } else {
+ str = '';
+ };
+ e.html('Destination folder' + str + ':');
+ }
+ },
+
+ /****
+ *****
+ ***** UTILITIES
+ *****
+ ****/
+
+ getAllTorrents: function () {
+ var torrents = [];
+ for (var key in this._torrents) {
+ torrents.push(this._torrents[key]);
+ };
+ return torrents;
+ },
+
+ getTorrentIds: function (torrents) {
+ return $.map(torrents.slice(0), function (t) {
+ return t.getId();
+ });
+ },
+
+ scrollToRow: function (row) {
+ if (isMobileDevice) {
+ // FIXME: why? return
+ var list = $('#torrent_container');
+ var scrollTop = list.scrollTop();
+ var innerHeight = list.innerHeight();
+ var offsetTop = row.getElement().offsetTop;
+ var offsetHeight = $(row.getElement()).outerHeight();
+
+ if (offsetTop < scrollTop) {
+ list.scrollTop(offsetTop);
+ } else if (innerHeight + scrollTop < offsetTop + offsetHeight) {
+ list.scrollTop(offsetTop + offsetHeight - innerHeight);
+ };
+ };
+ },
+
+ seedRatioLimit: function () {
+ var p = this.sessionProperties;
+ if (p && p.seedRatioLimited) {
+ return p.seedRatioLimit;
+ };
+ return -1;
+ },
+
+ setPref: function (key, val) {
+ this[key] = val;
+ Prefs.setValue(key, val);
+ },
+
+ /****
+ *****
+ ***** SELECTION
+ *****
+ ****/
+
+ getSelectedRows: function () {
+ return $.grep(this._rows, function (r) {
+ return r.isSelected();
+ });
+ },
+
+ getSelectedTorrents: function () {
+ return $.map(this.getSelectedRows(), function (r) {
+ return r.getTorrent();
+ });
+ },
+
+ getSelectedTorrentIds: function () {
+ return this.getTorrentIds(this.getSelectedTorrents());
+ },
+
+ setSelectedRow: function (row) {
+ $(this.elements.torrent_list).children('.selected').removeClass('selected');
+ this.selectRow(row);
+ },
+
+ selectRow: function (row) {
+ $(row.getElement()).addClass('selected');
+ this.callSelectionChangedSoon();
+ },
+
+ deselectRow: function (row) {
+ $(row.getElement()).removeClass('selected');
+ this.callSelectionChangedSoon();
+ },
+
+ selectAll: function () {
+ $(this.elements.torrent_list).children().addClass('selected');
+ this.callSelectionChangedSoon();
+ },
+ deselectAll: function () {
+ $(this.elements.torrent_list).children('.selected').removeClass('selected');
+ this.callSelectionChangedSoon();
+ delete this._last_torrent_clicked;
+ },
+
+ indexOfLastTorrent: function () {
+ for (var i = 0, r; r = this._rows[i]; ++i) {
+ if (r.getTorrentId() === this._last_torrent_clicked) {
+ return i;
+ };
+ };
+ return -1;
+ },
+
+ // Select a range from this row to the last clicked torrent
+ selectRange: function (row) {
+ var last = this.indexOfLastTorrent();
+
+ if (last === -1) {
+ this.selectRow(row);
+ } else { // select the range between the prevous & current
+ var next = this._rows.indexOf(row);
+ var min = Math.min(last, next);
+ var max = Math.max(last, next);
+ for (var i = min; i <= max; ++i) {
+ this.selectRow(this._rows[i]);
+ };
+ }
+
+ this.callSelectionChangedSoon();
+ },
+
+ selectionChanged: function () {
+ this.updateButtonStates();
+
+ this.inspector.setTorrents(this.inspectorIsVisible() ? this.getSelectedTorrents() : []);
+
+ clearTimeout(this.selectionChangedTimer);
+ delete this.selectionChangedTimer;
+
+ },
+
+ callSelectionChangedSoon: function () {
+ if (!this.selectionChangedTimer) {
+ var callback = $.proxy(this.selectionChanged, this),
+ msec = 200;
+ this.selectionChangedTimer = setTimeout(callback, msec);
+ }
+ },
+
+ /*--------------------------------------------
+ *
+ * E V E N T F U N C T I O N S
+ *
+ *--------------------------------------------*/
+
+ /*
+ * Process key event
+ */
+ keyDown: function (ev) {
+ var handled = false;
+ var rows = this._rows;
+ var up = ev.keyCode === 38; // up key pressed
+ var dn = ev.keyCode === 40; // down key pressed
+ var shift = ev.keyCode === 16; // shift key pressed
+
+ if ((up || dn) && rows.length) {
+ var last = this.indexOfLastTorrent(),
+ i = last,
+ anchor = this._shift_index,
+ r,
+ min = 0,
+ max = rows.length - 1;
+
+ if (dn && (i + 1 <= max)) {
+ ++i;
+ } else if (up && (i - 1 >= min)) {
+ --i;
+ };
+
+ var r = rows[i];
+
+ if (anchor >= 0) {
+ // user is extending the selection
+ // with the shift + arrow keys...
+ if (((anchor <= last) && (last < i)) || ((anchor >= last) && (last > i))) {
+ this.selectRow(r);
+ } else if (((anchor >= last) && (i > last)) || ((anchor <= last) && (last > i))) {
+ this.deselectRow(rows[last]);
+ }
+ } else {
+ if (ev.shiftKey) {
+ this.selectRange(r);
+ } else {
+ this.setSelectedRow(r);
+ };
+ }
+ this._last_torrent_clicked = r.getTorrentId();
+ this.scrollToRow(r);
+ handled = true;
+ } else if (shift) {
+ this._shift_index = this.indexOfLastTorrent();
+ }
+
+ return !handled;
+ },
+
+ keyUp: function (ev) {
+ if (ev.keyCode === 16) { // shift key pressed
+ delete this._shift_index;
+ };
+ },
+
+ isButtonEnabled: function (ev) {
+ var p = (ev.target || ev.srcElement).parentNode;
+ return p.className !== 'disabled' && p.parentNode.className !== 'disabled';
+ },
+
+ stopSelectedClicked: function (ev) {
+ if (this.isButtonEnabled(ev)) {
+ this.stopSelectedTorrents();
+ this.hideMobileAddressbar();
+ }
+ },
+
+ startSelectedClicked: function (ev) {
+ if (this.isButtonEnabled(ev)) {
+ this.startSelectedTorrents(false);
+ this.hideMobileAddressbar();
+ }
+ },
+
+ stopAllClicked: function (ev) {
+ if (this.isButtonEnabled(ev)) {
+ this.stopAllTorrents();
+ this.hideMobileAddressbar();
+ }
+ },
+
+ startAllClicked: function (ev) {
+ if (this.isButtonEnabled(ev)) {
+ this.startAllTorrents(false);
+ this.hideMobileAddressbar();
+ }
+ },
+
+ openTorrentClicked: function (ev) {
+ if (this.isButtonEnabled(ev)) {
+ $('body').addClass('open_showing');
+ this.uploadTorrentFile();
+ this.updateButtonStates();
+ }
+ },
+
+ dragenter: function (ev) {
+ if (ev.dataTransfer && ev.dataTransfer.types) {
+ var types = ["text/uri-list", "text/plain"];
+ for (var i = 0; i < types.length; ++i) {
+ // it would be better to look at the links here;
+ // sadly, with Firefox, trying would throw.
+ if (ev.dataTransfer.types.contains(types[i])) {
+ ev.stopPropagation();
+ ev.preventDefault();
+ ev.dropEffect = "copy";
+ return false;
+ }
+ }
+ } else if (ev.dataTransfer) {
+ ev.dataTransfer.dropEffect = "none";
+ }
+ return true;
+ },
+
+ drop: function (ev) {
+ var i, uri;
+ var uris = null;
+ var types = ["text/uri-list", "text/plain"];
+ var paused = this.shouldAddedTorrentsStart();
+
+ if (!ev.dataTransfer || !ev.dataTransfer.types) {
+ return true;
+ };
+
+ for (i = 0; !uris && i < types.length; ++i) {
+ if (ev.dataTransfer.types.contains(types[i])) {
+ uris = ev.dataTransfer.getData(types[i]).split("\n");
+ };
+ };
+
+ for (i = 0; uri = uris[i]; ++i) {
+ if (/^#/.test(uri)) { // lines which start with "#" are comments
+ continue;
+ };
+ if (/^[a-z-]+:/i.test(uri)) { // close enough to a url
+ this.remote.addTorrentByUrl(uri, paused);
+ };
+ };
+
+ ev.preventDefault();
+ return false;
+ },
+
+ hideUploadDialog: function () {
+ $('body.open_showing').removeClass('open_showing');
+ $('#upload_container').hide();
+ this.updateButtonStates();
+ },
+
+ confirmUploadClicked: function () {
+ this.uploadTorrentFile(true);
+ this.hideUploadDialog();
+ },
+
+ hideMoveDialog: function () {
+ $('#move_container').hide();
+ this.updateButtonStates();
+ },
+
+ confirmMoveClicked: function () {
+ this.moveSelectedTorrents(true);
+ this.hideUploadDialog();
+ },
+
+ hideRenameDialog: function () {
+ $('body.open_showing').removeClass('open_showing');
+ $('#rename_container').hide();
+ },
+
+ confirmRenameClicked: function () {
+ var torrents = this.getSelectedTorrents();
+ this.renameTorrent(torrents[0], $('input#torrent_rename_name').attr('value'));
+ this.hideRenameDialog();
+ },
+
+ removeClicked: function (ev) {
+ if (this.isButtonEnabled(ev)) {
+ this.removeSelectedTorrents();
+ this.hideMobileAddressbar();
+ };
+ },
+
+ // turn the periodic ajax session refresh on & off
+ togglePeriodicSessionRefresh: function (enabled) {
+ clearInterval(this.sessionInterval);
+ delete this.sessionInterval;
+ if (enabled) {
+ var callback = $.proxy(this.loadDaemonPrefs, this);
+ var msec = 8000;
+
+ this.sessionInterval = setInterval(callback, msec);
+ };
+ },
+
+ toggleTurtleClicked: function () {
+ var o = {};
+ o[RPC._TurtleState] = !$('#turtle-button').hasClass('selected');
+ this.remote.savePrefs(o);
+ },
+
+ /*--------------------------------------------
+ *
+ * I N T E R F A C E F U N C T I O N S
+ *
+ *--------------------------------------------*/
+
+ onPrefsDialogClosed: function () {
+ $('#prefs-button').removeClass('selected');
+ },
+
+ togglePrefsDialogClicked: function (ev) {
+ var e = $('#prefs-button');
+
+ if (e.hasClass('selected'))
+ this.prefsDialog.close();
+ else {
+ e.addClass('selected');
+ this.prefsDialog.show();
+ }
+ },
+
+ setFilterText: function (search) {
+ this.filterText = search ? search.trim() : null;
+ this.refilter(true);
+ },
+
+ setSortMethod: function (sort_method) {
+ this.setPref(Prefs._SortMethod, sort_method);
+ this.refilter(true);
+ },
+
+ setSortDirection: function (direction) {
+ this.setPref(Prefs._SortDirection, direction);
+ this.refilter(true);
+ },
+
+ onMenuClicked: function (event, ui) {
+ var o, dir;
+ var id = ui.id;
+ var remote = this.remote;
+ var element = ui.target;
+
+ if (ui.group == 'sort-mode') {
+ element.selectMenuItem();
+ this.setSortMethod(id.replace(/sort_by_/, ''));
+ } else if (element.hasClass('upload-speed')) {
+ o = {};
+ o[RPC._UpSpeedLimit] = parseInt(element.text());
+ o[RPC._UpSpeedLimited] = true;
+ remote.savePrefs(o);
+ } else if (element.hasClass('download-speed')) {
+ o = {};
+ o[RPC._DownSpeedLimit] = parseInt(element.text());
+ o[RPC._DownSpeedLimited] = true;
+ remote.savePrefs(o);
+ } else {
+ switch (id) {
+ case 'statistics':
+ this.showStatsDialog();
+ break;
+
+ case 'about-button':
+ o = 'Transmission ' + this.serverVersion;
+ $('#about-dialog #about-title').html(o);
+ $('#about-dialog').dialog({
+ title: 'About',
+ show: 'fade',
+ hide: 'fade'
+ });
+ break;
+
+ case 'homepage':
+ window.open('http://www.transmissionbt.com/');
+ break;
+
+ case 'tipjar':
+ window.open('http://www.transmissionbt.com/donate.php');
+ break;
+
+ case 'unlimited_download_rate':
+ o = {};
+ o[RPC._DownSpeedLimited] = false;
+ remote.savePrefs(o);
+ break;
+
+ case 'limited_download_rate':
+ o = {};
+ o[RPC._DownSpeedLimited] = true;
+ remote.savePrefs(o);
+ break;
+
+ case 'unlimited_upload_rate':
+ o = {};
+ o[RPC._UpSpeedLimited] = false;
+ remote.savePrefs(o);
+ break;
+
+ case 'limited_upload_rate':
+ o = {};
+ o[RPC._UpSpeedLimited] = true;
+ remote.savePrefs(o);
+ break;
+
+ case 'reverse_sort_order':
+ if (element.menuItemIsSelected()) {
+ dir = Prefs._SortAscending;
+ element.deselectMenuItem();
+ } else {
+ dir = Prefs._SortDescending;
+ element.selectMenuItem();
+ }
+ this.setSortDirection(dir);
+ break;
+
+ case 'toggle_notifications':
+ Notifications && Notifications.toggle();
+ break;
+
+ default:
+ console.log('unhandled: ' + id);
+ break;
+ };
+ };
+ },
+
+ onTorrentChanged: function (ev, tor) {
+ // update our dirty fields
+ this.dirtyTorrents[tor.getId()] = true;
+
+ // enqueue ui refreshes
+ this.refilterSoon();
+ this.updateButtonsSoon();
+ },
+
+ updateFromTorrentGet: function (updates, removed_ids) {
+ var i, o, t, id, needed, callback, fields;
+ var needinfo = [];
+
+ for (i = 0; o = updates[i]; ++i) {
+ id = o.id;
+ if ((t = this._torrents[id])) {
+ needed = t.needsMetaData();
+ t.refresh(o);
+ if (needed && !t.needsMetaData()) {
+ needinfo.push(id);
+ };
+ } else {
+ t = this._torrents[id] = new Torrent(o);
+ this.dirtyTorrents[id] = true;
+ callback = $.proxy(this.onTorrentChanged, this);
+ $(t).bind('dataChanged', callback);
+ // do we need more info for this torrent?
+ if (!('name' in t.fields) || !('status' in t.fields))
+ needinfo.push(id);
+
+ t.notifyOnFieldChange('status', $.proxy(function (newValue, oldValue) {
+ if (oldValue === Torrent._StatusDownload && (newValue == Torrent._StatusSeed || newValue == Torrent._StatusSeedWait)) {
+ $(this).trigger('downloadComplete', [t]);
+ } else if (oldValue === Torrent._StatusSeed && newValue === Torrent._StatusStopped && t.isFinished()) {
+ $(this).trigger('seedingComplete', [t]);
+ } else {
+ $(this).trigger('statusChange', [t]);
+ }
+ }, this));
+ }
+ }
+
+ if (needinfo.length) {
+ // whee, new torrents! get their initial information.
+ fields = ['id'].concat(Torrent.Fields.Metadata,
+ Torrent.Fields.Stats);
+ this.updateTorrents(needinfo, fields);
+ this.refilterSoon();
+ }
+
+ if (removed_ids) {
+ this.deleteTorrents(removed_ids);
+ this.refilterSoon();
+ }
+ },
+
+ updateTorrents: function (ids, fields) {
+ this.remote.updateTorrents(ids, fields, this.updateFromTorrentGet, this);
+ },
+
+ refreshTorrents: function () {
+ var callback = $.proxy(this.refreshTorrents, this);
+ var msec = this[Prefs._RefreshRate] * 1000;
+ var fields = ['id'].concat(Torrent.Fields.Stats);
+
+ // send a request right now
+ this.updateTorrents('recently-active', fields);
+
+ // schedule the next request
+ clearTimeout(this.refreshTorrentsTimeout);
+ this.refreshTorrentsTimeout = setTimeout(callback, msec);
+ },
+
+ initializeTorrents: function () {
+ var fields = ['id'].concat(Torrent.Fields.Metadata, Torrent.Fields.Stats);
+ this.updateTorrents(null, fields);
+ },
+
+ onRowClicked: function (ev) {
+ var meta_key = ev.metaKey || ev.ctrlKey,
+ row = ev.currentTarget.row;
+
+ // handle the per-row "torrent_resume" button
+ if (ev.target.className === 'torrent_resume') {
+ this.startTorrent(row.getTorrent());
+ return;
+ }
+
+ // handle the per-row "torrent_pause" button
+ if (ev.target.className === 'torrent_pause') {
+ this.stopTorrent(row.getTorrent());
+ return;
+ }
+
+ // Prevents click carrying to parent element
+ // which deselects all on click
+ ev.stopPropagation();
+
+ if (isMobileDevice) {
+ if (row.isSelected())
+ this.setInspectorVisible(true);
+ this.setSelectedRow(row);
+
+ } else if (ev.shiftKey) {
+ this.selectRange(row);
+ // Need to deselect any selected text
+ window.focus();
+
+ // Apple-Click, not selected
+ } else if (!row.isSelected() && meta_key) {
+ this.selectRow(row);
+
+ // Regular Click, not selected
+ } else if (!row.isSelected()) {
+ this.setSelectedRow(row);
+
+ // Apple-Click, selected
+ } else if (row.isSelected() && meta_key) {
+ this.deselectRow(row);
+
+ // Regular Click, selected
+ } else if (row.isSelected()) {
+ this.setSelectedRow(row);
+ }
+
+ this._last_torrent_clicked = row.getTorrentId();
+ },
+
+ deleteTorrents: function (ids) {
+ var i, id;
+
+ if (ids && ids.length) {
+ for (i = 0; id = ids[i]; ++i) {
+ this.dirtyTorrents[id] = true;
+ delete this._torrents[id];
+ };
+ this.refilter();
+ };
+ },
+
+ shouldAddedTorrentsStart: function () {
+ return this.prefsDialog.shouldAddedTorrentsStart();
+ },
+
+ /*
+ * Select a torrent file to upload
+ */
+ uploadTorrentFile: function (confirmed) {
+ var i, file, reader;
+ var fileInput = $('input#torrent_upload_file');
+ var folderInput = $('input#add-dialog-folder-input');
+ var startInput = $('input#torrent_auto_start');
+ var urlInput = $('input#torrent_upload_url');
+
+ if (!confirmed) {
+ // update the upload dialog's fields
+ fileInput.attr('value', '');
+ urlInput.attr('value', '');
+ startInput.attr('checked', this.shouldAddedTorrentsStart());
+ folderInput.attr('value', $("#download-dir").val());
+ folderInput.change($.proxy(this.updateFreeSpaceInAddDialog, this));
+ this.updateFreeSpaceInAddDialog();
+
+ // show the dialog
+ $('#upload_container').show();
+ urlInput.focus();
+ } else {
+ var paused = !startInput.is(':checked');
+ var destination = folderInput.val();
+ var remote = this.remote;
+
+ jQuery.each(fileInput[0].files, function (i, file) {
+ var reader = new FileReader();
+ reader.onload = function (e) {
+ var contents = e.target.result;
+ var key = "base64,"
+ var index = contents.indexOf(key);
+ if (index > -1) {
+ var metainfo = contents.substring(index + key.length);
+ var o = {
+ method: 'torrent-add',
+ arguments: {
+ 'paused': paused,
+ 'download-dir': destination,
+ 'metainfo': metainfo
+ }
+ };
+ remote.sendRequest(o, function (response) {
+ if (response.result != 'success')
+ alert('Error adding "' + file.name + '": ' + response.result);
+ });
+ }
+ };
+ reader.readAsDataURL(file);
+ });
+
+ var url = $('#torrent_upload_url').val();
+ if (url != '') {
+ if (url.match(/^[0-9a-f]{40}$/i)) {
+ url = 'magnet:?xt=urn:btih:' + url;
+ };
+ var o = {
+ 'method': 'torrent-add',
+ arguments: {
+ 'paused': paused,
+ 'download-dir': destination,
+ 'filename': url
+ }
+ };
+ remote.sendRequest(o, function (response) {
+ if (response.result != 'success') {
+ alert('Error adding "' + url + '": ' + response.result);
+ };
+ });
+ }
+ }
+ },
+
+ promptSetLocation: function (confirmed, torrents) {
+ if (!confirmed) {
+ var path;
+ if (torrents.length === 1) {
+ path = torrents[0].getDownloadDir();
+ } else {
+ path = $("#download-dir").val();
+ }
+ $('input#torrent_path').attr('value', path);
+ $('#move_container').show();
+ $('#torrent_path').focus();
+ } else {
+ var ids = this.getTorrentIds(torrents);
+ this.remote.moveTorrents(ids, $("input#torrent_path").val(), this.refreshTorrents, this);
+ $('#move_container').hide();
+ }
+ },
+
+ moveSelectedTorrents: function (confirmed) {
+ var torrents = this.getSelectedTorrents();
+ if (torrents.length) {
+ this.promptSetLocation(confirmed, torrents);
+ };
+ },
+
+ removeSelectedTorrents: function () {
+ var torrents = this.getSelectedTorrents();
+ if (torrents.length) {
+ this.promptToRemoveTorrents(torrents);
+ };
+ },
+
+ removeSelectedTorrentsAndData: function () {
+ var torrents = this.getSelectedTorrents();
+ if (torrents.length) {
+ this.promptToRemoveTorrentsAndData(torrents);
+ };
+ },
+
+ promptToRemoveTorrents: function (torrents) {
+ if (torrents.length === 1) {
+ var torrent = torrents[0];
+ var header = 'Remove ' + torrent.getName() + '?';
+ var message = 'Once removed, continuing the transfer will require the torrent file. Are you sure you want to remove it?';
+
+ dialog.confirm(header, message, 'Remove', function () {
+ transmission.removeTorrents(torrents);
+ });
+ } else {
+ var header = 'Remove ' + torrents.length + ' transfers?';
+ var message = 'Once removed, continuing the transfers will require the torrent files. Are you sure you want to remove them?';
+
+ dialog.confirm(header, message, 'Remove', function () {
+ transmission.removeTorrents(torrents);
+ });
+ }
+ },
+
+ promptToRemoveTorrentsAndData: function (torrents) {
+ if (torrents.length === 1) {
+ var torrent = torrents[0];
+ var header = 'Remove ' + torrent.getName() + ' and delete data?';
+ var message = 'All data downloaded for this torrent will be deleted. Are you sure you want to remove it?';
+
+ dialog.confirm(header, message, 'Remove', function () {
+ transmission.removeTorrentsAndData(torrents);
+ });
+ } else {
+ var header = 'Remove ' + torrents.length + ' transfers and delete data?';
+ var message = 'All data downloaded for these torrents will be deleted. Are you sure you want to remove them?';
+
+ dialog.confirm(header, message, 'Remove', function () {
+ transmission.removeTorrentsAndData(torrents);
+ });
+ }
+ },
+
+ removeTorrents: function (torrents) {
+ var ids = this.getTorrentIds(torrents);
+ this.remote.removeTorrents(ids, this.refreshTorrents, this);
+ },
+
+ removeTorrentsAndData: function (torrents) {
+ this.remote.removeTorrentsAndData(torrents);
+ },
+
+ promptToRenameTorrent: function (torrent) {
+ $('body').addClass('open_showing');
+ $('input#torrent_rename_name').attr('value', torrent.getName());
+ $('#rename_container').show();
+ $('#torrent_rename_name').focus();
+ },
+
+ renameSelectedTorrents: function () {
+ var torrents = this.getSelectedTorrents();
+ if (torrents.length != 1) {
+ dialog.alert("Renaming", "You can rename only one torrent at a time.", "Ok");
+ } else {
+ this.promptToRenameTorrent(torrents[0]);
+ };
+ },
+
+ onTorrentRenamed: function (response) {
+ var torrent;
+ if ((response.result === 'success') && (response.arguments) && ((torrent = this._torrents[response.arguments.id]))) {
+ torrent.refresh(response.arguments);
+ }
+ },
+
+ renameTorrent: function (torrent, newname) {
+ var oldpath = torrent.getName();
+ this.remote.renameTorrent([torrent.getId()], oldpath, newname, this.onTorrentRenamed, this);
+ },
+
+ verifySelectedTorrents: function () {
+ this.verifyTorrents(this.getSelectedTorrents());
+ },
+
+ reannounceSelectedTorrents: function () {
+ this.reannounceTorrents(this.getSelectedTorrents());
+ },
+
+ startAllTorrents: function (force) {
+ this.startTorrents(this.getAllTorrents(), force);
+ },
+ startSelectedTorrents: function (force) {
+ this.startTorrents(this.getSelectedTorrents(), force);
+ },
+ startTorrent: function (torrent) {
+ this.startTorrents([torrent], false);
+ },
+
+ startTorrents: function (torrents, force) {
+ this.remote.startTorrents(this.getTorrentIds(torrents), force, this.refreshTorrents, this);
+ },
+ verifyTorrent: function (torrent) {
+ this.verifyTorrents([torrent]);
+ },
+ verifyTorrents: function (torrents) {
+ this.remote.verifyTorrents(this.getTorrentIds(torrents), this.refreshTorrents, this);
+ },
+
+ reannounceTorrent: function (torrent) {
+ this.reannounceTorrents([torrent]);
+ },
+ reannounceTorrents: function (torrents) {
+ this.remote.reannounceTorrents(this.getTorrentIds(torrents), this.refreshTorrents, this);
+ },
+
+ stopAllTorrents: function () {
+ this.stopTorrents(this.getAllTorrents());
+ },
+ stopSelectedTorrents: function () {
+ this.stopTorrents(this.getSelectedTorrents());
+ },
+ stopTorrent: function (torrent) {
+ this.stopTorrents([torrent]);
+ },
+ stopTorrents: function (torrents) {
+ this.remote.stopTorrents(this.getTorrentIds(torrents), this.refreshTorrents, this);
+ },
+ changeFileCommand: function (torrentId, rowIndices, command) {
+ this.remote.changeFileCommand(torrentId, rowIndices, command);
+ },
+
+ hideMobileAddressbar: function (delaySecs) {
+ if (isMobileDevice && !scroll_timeout) {
+ var callback = $.proxy(this.doToolbarHide, this);
+ var msec = delaySecs * 1000 || 150;
+ scroll_timeout = setTimeout(callback, msec);
+ };
+ },
+ doToolbarHide: function () {
+ window.scrollTo(0, 1);
+ scroll_timeout = null;
+ },
+
+ // Queue
+ moveTop: function () {
+ this.remote.moveTorrentsToTop(this.getSelectedTorrentIds(), this.refreshTorrents, this);
+ },
+ moveUp: function () {
+ this.remote.moveTorrentsUp(this.getSelectedTorrentIds(), this.refreshTorrents, this);
+ },
+ moveDown: function () {
+ this.remote.moveTorrentsDown(this.getSelectedTorrentIds(), this.refreshTorrents, this);
+ },
+ moveBottom: function () {
+ this.remote.moveTorrentsToBottom(this.getSelectedTorrentIds(), this.refreshTorrents, this);
+ },
+
+ /***
+ ****
+ ***/
+
+ updateGuiFromSession: function (o) {
+ var limit, limited, e, b, text;
+ var fmt = Transmission.fmt;
+ var menu = $('#footer_super_menu');
+
+ this.serverVersion = o.version;
+
+ this.prefsDialog.set(o);
+
+ if (RPC._TurtleState in o) {
+ b = o[RPC._TurtleState];
+ e = $('#turtle-button');
+ text = ['Click to ', (b ? 'disable' : 'enable'), ' Temporary Speed Limits (', fmt.speed(o[RPC._TurtleUpSpeedLimit]), ' up,', fmt.speed(o[RPC._TurtleDownSpeedLimit]), ' down)'].join('');
+ e.toggleClass('selected', b);
+ e.attr('title', text);
+ }
+
+ if (this.isMenuEnabled && (RPC._DownSpeedLimited in o) && (RPC._DownSpeedLimit in o)) {
+ limit = o[RPC._DownSpeedLimit];
+ limited = o[RPC._DownSpeedLimited];
+
+ e = menu.find('#limited_download_rate');
+ e.html('Limit (' + fmt.speed(limit) + ')');
+
+ if (!limited) {
+ e = menu.find('#unlimited_download_rate');
+ };
+ e.selectMenuItem();
+ }
+
+ if (this.isMenuEnabled && (RPC._UpSpeedLimited in o) && (RPC._UpSpeedLimit in o)) {
+ limit = o[RPC._UpSpeedLimit];
+ limited = o[RPC._UpSpeedLimited];
+
+ e = menu.find('#limited_upload_rate');
+ e.html('Limit (' + fmt.speed(limit) + ')');
+
+ if (!limited) {
+ e = menu.find('#unlimited_upload_rate');
+ };
+ e.selectMenuItem();
+ }
+ },
+
+ updateStatusbar: function () {
+ var i, row;
+ var u = 0;
+ var d = 0;
+ var fmt = Transmission.fmt;
+ var torrents = this.getAllTorrents();
+
+ // up/down speed
+ for (i = 0; row = torrents[i]; ++i) {
+ u += row.getUploadSpeed();
+ d += row.getDownloadSpeed();
+ }
+
+ $('#speed-up-container').toggleClass('active', u > 0);
+ $('#speed-up-label').text(fmt.speedBps(u));
+
+ $('#speed-dn-container').toggleClass('active', d > 0);
+ $('#speed-dn-label').text(fmt.speedBps(d));
+
+ // visible torrents
+ $('#filter-count').text(fmt.countString('Transfer', 'Transfers', this._rows.length));
+ },
+
+ setEnabled: function (key, flag) {
+ $(key).toggleClass('disabled', !flag);
+ },
+
+ updateFilterSelect: function () {
+ var i, names, name, str, o;
+ var e = $('#filter-tracker');
+ var trackers = this.getTrackers();
+
+ // build a sorted list of names
+ names = [];
+ for (name in trackers) {
+ names.push(name);
+ };
+ names.sort();
+
+ // build the new html
+ if (!this.filterTracker) {
+ str = '<option value="all" selected="selected">All</option>';
+ } else {
+ str = '<option value="all">All</option>';
+ };
+ for (i = 0; name = names[i]; ++i) {
+ o = trackers[name];
+ str += '<option value="' + o.domain + '"';
+ if (trackers[name].domain === this.filterTracker) {
+ str += ' selected="selected"';
+ };
+ str += '>' + name + '</option>';
+ }
+
+ if (!this.filterTrackersStr || (this.filterTrackersStr !== str)) {
+ this.filterTrackersStr = str;
+ $('#filter-tracker').html(str);
+ }
+ },
+
+ updateButtonsSoon: function () {
+ if (!this.buttonRefreshTimer) {
+ var callback = $.proxy(this.updateButtonStates, this);
+ var msec = 100;
+
+ this.buttonRefreshTimer = setTimeout(callback, msec);
+ }
+ },
+
+ calculateTorrentStates: function (callback) {
+ var stats = {
+ total: 0,
+ active: 0,
+ paused: 0,
+ sel: 0,
+ activeSel: 0,
+ pausedSel: 0,
+ queuedSel: 0
+ };
+
+ clearTimeout(this.buttonRefreshTimer);
+ delete this.buttonRefreshTimer;
+
+ for (var i = 0, row; row = this._rows[i]; ++i) {
+ var isStopped = row.getTorrent().isStopped();
+ var isSelected = row.isSelected();
+ var isQueued = row.getTorrent().isQueued();
+ ++stats.total;
+ if (!isStopped) {
+ ++stats.active;
+ };
+ if (isStopped) {
+ ++stats.paused;
+ };
+ if (isSelected) {
+ ++stats.sel;
+ };
+ if (isSelected && !isStopped) {
+ ++stats.activeSel;
+ };
+ if (isSelected && isStopped) {
+ ++stats.pausedSel;
+ };
+ if (isSelected && isQueued) {
+ ++stats.queuedSel;
+ };
+ };
+
+ callback(stats);
+ },
+
+ updateButtonStates: function () {
+ var tr = this;
+ var e = this.elements;
+
+ this.calculateTorrentStates(function (s) {
+ tr.setEnabled(e.toolbar_pause_button, s.activeSel > 0);
+ tr.setEnabled(e.toolbar_start_button, s.pausedSel > 0);
+ tr.setEnabled(e.toolbar_remove_button, s.sel > 0);
+ });
+ },
+
+ /****
+ *****
+ ***** INSPECTOR
+ *****
+ ****/
+
+ inspectorIsVisible: function () {
+ return $('#torrent_inspector').is(':visible');
+ },
+ toggleInspector: function () {
+ this.setInspectorVisible(!this.inspectorIsVisible());
+ },
+ setInspectorVisible: function (visible) {
+ if (visible) {
+ this.inspector.setTorrents(this.getSelectedTorrents());
+ };
+
+ // update the ui widgetry
+ $('#torrent_inspector').toggle(visible);
+ $('#toolbar-inspector').toggleClass('selected', visible);
+ this.hideMobileAddressbar();
+ if (isMobileDevice) {
+ $('body').toggleClass('inspector_showing', visible);
+ } else {
+ var w = visible ? $('#torrent_inspector').outerWidth() + 1 + 'px' : '0px';
+ $('#torrent_container')[0].style.right = w;
+ }
+ },
+
+ /****
+ *****
+ ***** FILTER
+ *****
+ ****/
+
+ refilterSoon: function () {
+ if (!this.refilterTimer) {
+ var tr = this,
+ callback = function () {
+ tr.refilter(false);
+ },
+ msec = 100;
+ this.refilterTimer = setTimeout(callback, msec);
+ }
+ },
+
+ sortRows: function (rows) {
+ var i, tor, row,
+ id2row = {},
+ torrents = [];
+
+ for (i = 0; row = rows[i]; ++i) {
+ tor = row.getTorrent();
+ torrents.push(tor);
+ id2row[tor.getId()] = row;
+ }
+
+ Torrent.sortTorrents(torrents, this[Prefs._SortMethod],
+ this[Prefs._SortDirection]);
+
+ for (i = 0; tor = torrents[i]; ++i) {
+ rows[i] = id2row[tor.getId()];
+ };
+ },
+
+ refilter: function (rebuildEverything) {
+ var i, e, id, t, row, tmp, rows, clean_rows, dirty_rows, frag;
+ var sort_mode = this[Prefs._SortMethod];
+ var sort_direction = this[Prefs._SortDirection];
+ var filter_mode = this[Prefs._FilterMode];
+ var filter_text = this.filterText;
+ var filter_tracker = this.filterTracker;
+ var renderer = this.torrentRenderer;
+ var list = this.elements.torrent_list;
+
+ old_sel_count = $(list).children('.selected').length;
+
+ this.updateFilterSelect();
+
+ clearTimeout(this.refilterTimer);
+ delete this.refilterTimer;
+
+ if (rebuildEverything) {
+ $(list).empty();
+ this._rows = [];
+ for (id in this._torrents) {
+ this.dirtyTorrents[id] = true;
+ };
+ }
+
+ // rows that overlap with dirtyTorrents need to be refiltered.
+ // those that don't are 'clean' and don't need refiltering.
+ clean_rows = [];
+ dirty_rows = [];
+ for (i = 0; row = this._rows[i]; ++i) {
+ if (row.getTorrentId() in this.dirtyTorrents) {
+ dirty_rows.push(row);
+ } else {
+ clean_rows.push(row);
+ };
+ }
+
+ // remove the dirty rows from the dom
+ e = [];
+ for (i = 0; row = dirty_rows[i]; ++i) {
+ e.push(row.getElement());
+ };
+ $(e).detach();
+
+ // drop any dirty rows that don't pass the filter test
+ tmp = [];
+ for (i = 0; row = dirty_rows[i]; ++i) {
+ id = row.getTorrentId();
+ t = this._torrents[id];
+ if (t && t.test(filter_mode, filter_text, filter_tracker)) {
+ tmp.push(row);
+ };
+ delete this.dirtyTorrents[id];
+ }
+ dirty_rows = tmp;
+
+ // make new rows for dirty torrents that pass the filter test
+ // but don't already have a row
+ for (id in this.dirtyTorrents) {
+ t = this._torrents[id];
+ if (t && t.test(filter_mode, filter_text, filter_tracker)) {
+ row = new TorrentRow(renderer, this, t);
+ e = row.getElement();
+ e.row = row;
+ dirty_rows.push(row);
+ $(e).click($.proxy(this.onRowClicked, this));
+ $(e).dblclick($.proxy(this.toggleInspector, this));
+ }
+ }
+
+ // sort the dirty rows
+ this.sortRows(dirty_rows);
+
+ // now we have two sorted arrays of rows
+ // and can do a simple two-way sorted merge.
+ rows = [];
+ var ci = 0,
+ cmax = clean_rows.length;
+ var di = 0,
+ dmax = dirty_rows.length;
+ frag = document.createDocumentFragment();
+ while (ci != cmax || di != dmax) {
+ var push_clean;
+
+ if (ci == cmax) {
+ push_clean = false;
+ } else if (di == dmax) {
+ push_clean = true;
+ } else {
+ var c = Torrent.compareTorrents(clean_rows[ci].getTorrent(), dirty_rows[di].getTorrent(), sort_mode, sort_direction);
+ push_clean = (c < 0);
+ }
+
+ if (push_clean) {
+ rows.push(clean_rows[ci++]);
+ } else {
+ row = dirty_rows[di++];
+ e = row.getElement();
+
+ if (ci !== cmax) {
+ list.insertBefore(e, clean_rows[ci].getElement());
+ } else {
+ frag.appendChild(e);
+ };
+
+ rows.push(row);
+ }
+ }
+ list.appendChild(frag);
+
+ // update our implementation fields
+ this._rows = rows;
+ this.dirtyTorrents = {};
+
+ // jquery's even/odd starts with 1 not 0, so invert its logic
+ e = []
+ for (i = 0; row = rows[i]; ++i) {
+ e.push(row.getElement());
+ };
+ $(e).filter(":odd").addClass('even');
+ $(e).filter(":even").removeClass('even');
+
+ // sync gui
+ this.updateStatusbar();
+ if (old_sel_count !== $(list).children('.selected').length) {
+ this.selectionChanged();
+ };
+ },
+
+ setFilterMode: function (mode) {
+ // set the state
+ this.setPref(Prefs._FilterMode, mode);
+
+ // refilter
+ this.refilter(true);
+ },
+
+ onFilterModeClicked: function (ev) {
+ this.setFilterMode($('#filter-mode').val());
+ },
+
+ onFilterTrackerClicked: function (ev) {
+ var tracker = $('#filter-tracker').val();
+ this.setFilterTracker(tracker === 'all' ? null : tracker);
+ },
+
+ setFilterTracker: function (domain) {
+ // update which tracker is selected in the popup
+ var key = domain ? this.getReadableDomain(domain) : 'all';
+ var id = '#show-tracker-' + key;
+
+ $(id).addClass('selected').siblings().removeClass('selected');
+
+ this.filterTracker = domain;
+ this.refilter(true);
+ },
+
+ // example: "tracker.ubuntu.com" returns "ubuntu.com"
+ getDomainName: function (host) {
+ var dot = host.indexOf('.');
+ if (dot !== host.lastIndexOf('.')) {
+ host = host.slice(dot + 1);
+ };
+
+ return host;
+ },
+
+ // example: "ubuntu.com" returns "Ubuntu"
+ getReadableDomain: function (name) {
+ if (name.length) {
+ name = name.charAt(0).toUpperCase() + name.slice(1);
+ };
+ var dot = name.indexOf('.');
+ if (dot !== -1) {
+ name = name.slice(0, dot);
+ };
+ return name;
+ },
+
+ getTrackers: function () {
+ var ret = {};
+
+ var torrents = this.getAllTorrents();
+ for (var i = 0, torrent; torrent = torrents[i]; ++i) {
+ var names = [];
+ var trackers = torrent.getTrackers();
+
+ for (var j = 0, tracker; tracker = trackers[j]; ++j) {
+ var uri, announce = tracker.announce;
+
+ if (announce in this.uriCache) {
+ uri = this.uriCache[announce];
+ } else {
+ uri = this.uriCache[announce] = parseUri(announce);
+ uri.domain = this.getDomainName(uri.host);
+ uri.name = this.getReadableDomain(uri.domain);
+ };
+
+ if (!(uri.name in ret)) {
+ ret[uri.name] = {
+ 'uri': uri,
+ 'domain': uri.domain,
+ 'count': 0
+ };
+ };
+
+ if (names.indexOf(uri.name) === -1) {
+ names.push(uri.name);
+ };
+ }
+
+ for (var j = 0, name; name = names[j]; ++j) {
+ ret[name].count++;
+ };
+ }
+
+ return ret;
+ },
+
+ /***
+ ****
+ **** Compact Mode
+ ****
+ ***/
+
+ toggleCompactClicked: function () {
+ this.setCompactMode(!this[Prefs._CompactDisplayState]);
+ },
+ setCompactMode: function (is_compact) {
+ var key = Prefs._CompactDisplayState;
+ var was_compact = this[key];
+
+ if (was_compact !== is_compact) {
+ this.setPref(key, is_compact);
+ this.onCompactModeChanged();
+ };
+ },
+ initCompactMode: function () {
+ this.onCompactModeChanged();
+ },
+ onCompactModeChanged: function () {
+ var compact = this[Prefs._CompactDisplayState];
+
+ // update the ui: footer button
+ $("#compact-button").toggleClass('selected', compact);
+
+ // update the ui: torrent list
+ this.torrentRenderer = compact ? new TorrentRendererCompact() : new TorrentRendererFull();
+ this.refilter(true);
+ },
+
+ /***
+ ****
+ **** Statistics
+ ****
+ ***/
+
+ // turn the periodic ajax stats refresh on & off
+ togglePeriodicStatsRefresh: function (enabled) {
+ clearInterval(this.statsInterval);
+ delete this.statsInterval;
+
+ if (enabled) {
+ var callback = $.proxy(this.loadDaemonStats, this);
+ var msec = 5000;
+
+ this.statsInterval = setInterval(callback, msec);
+ };
+ },
+
+ loadDaemonStats: function (async) {
+ this.remote.loadDaemonStats(function (data) {
+ this.updateStats(data['arguments']);
+ }, this, async);
+ },
+
+ // Process new session stats from the server
+ updateStats: function (stats) {
+ var s, ratio;
+ var fmt = Transmission.fmt;
+
+ s = stats["current-stats"];
+ ratio = Math.ratio(s.uploadedBytes, s.downloadedBytes);
+ $('#stats-session-uploaded').html(fmt.size(s.uploadedBytes));
+ $('#stats-session-downloaded').html(fmt.size(s.downloadedBytes));
+ $('#stats-session-ratio').html(fmt.ratioString(ratio));
+ $('#stats-session-duration').html(fmt.timeInterval(s.secondsActive));
+
+ s = stats["cumulative-stats"];
+ ratio = Math.ratio(s.uploadedBytes, s.downloadedBytes);
+ $('#stats-total-count').html(s.sessionCount + " times");
+ $('#stats-total-uploaded').html(fmt.size(s.uploadedBytes));
+ $('#stats-total-downloaded').html(fmt.size(s.downloadedBytes));
+ $('#stats-total-ratio').html(fmt.ratioString(ratio));
+ $('#stats-total-duration').html(fmt.timeInterval(s.secondsActive));
+ },
+
+ showStatsDialog: function () {
+ this.loadDaemonStats();
+ this.hideMobileAddressbar();
+ this.togglePeriodicStatsRefresh(true);
+ $('#stats-dialog').dialog({
+ close: $.proxy(this.onStatsDialogClosed, this),
+ show: 'fade',
+ hide: 'fade',
+ title: 'Statistics'
+ });
+ },
+
+ onStatsDialogClosed: function () {
+ this.hideMobileAddressbar();
+ this.togglePeriodicStatsRefresh(false);
+ }
};